Merge "Bug-1152:Wrapping PopVlanAction within PopVlanActionCase"
authorEd Warnicke <eaw@cisco.com>
Wed, 11 Jun 2014 23:21:51 +0000 (23:21 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Wed, 11 Jun 2014 23:21:51 +0000 (23:21 +0000)
28 files changed:
opendaylight/commons/filter-valve/pom.xml [new file with mode: 0644]
opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/FilterValve.java [new file with mode: 0644]
opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/Context.java [new file with mode: 0644]
opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/Filter.java [new file with mode: 0644]
opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/FilterMapping.java [new file with mode: 0644]
opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/Host.java [new file with mode: 0644]
opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/InitParam.java [new file with mode: 0644]
opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/Parser.java [new file with mode: 0644]
opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/model/FilterProcessor.java [new file with mode: 0644]
opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/model/UrlMatcher.java [new file with mode: 0644]
opendaylight/commons/filter-valve/src/test/java/org/opendaylight/controller/filtervalve/cors/jaxb/DummyFilter.java [new file with mode: 0644]
opendaylight/commons/filter-valve/src/test/java/org/opendaylight/controller/filtervalve/cors/jaxb/MockedFilter.java [new file with mode: 0644]
opendaylight/commons/filter-valve/src/test/java/org/opendaylight/controller/filtervalve/cors/jaxb/ParserTest.java [new file with mode: 0644]
opendaylight/commons/filter-valve/src/test/java/org/opendaylight/controller/filtervalve/cors/model/UrlMatcherTest.java [new file with mode: 0644]
opendaylight/commons/filter-valve/src/test/resources/conflicting-class.xml [new file with mode: 0644]
opendaylight/commons/filter-valve/src/test/resources/no-filter-defined.xml [new file with mode: 0644]
opendaylight/commons/filter-valve/src/test/resources/sample-cors-config.xml [new file with mode: 0644]
opendaylight/commons/opendaylight/pom.xml
opendaylight/distribution/opendaylight-karaf/pom.xml
opendaylight/distribution/opendaylight-karaf/src/main/resources/etc/custom.properties
opendaylight/distribution/opendaylight/pom.xml
opendaylight/distribution/opendaylight/src/main/resources/configuration/cors-config.xml [new file with mode: 0644]
opendaylight/distribution/opendaylight/src/main/resources/configuration/tomcat-server.xml
opendaylight/karaf-branding/pom.xml [new file with mode: 0644]
opendaylight/karaf-branding/src/main/resources/org/apache/karaf/branding/branding.properties [new file with mode: 0644]
opendaylight/md-sal/feature/src/main/resources/features.xml
opendaylight/md-sal/sal-rest-connector/src/main/resources/WEB-INF/web.xml
pom.xml

diff --git a/opendaylight/commons/filter-valve/pom.xml b/opendaylight/commons/filter-valve/pom.xml
new file mode 100644 (file)
index 0000000..7b5be02
--- /dev/null
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+  ~
+  ~ This program and the accompanying materials are made available under the
+  ~ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+  ~ and is available at http://www.eclipse.org/legal/epl-v10.html
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.2-SNAPSHOT</version>
+    <relativePath>../opendaylight</relativePath>
+  </parent>
+  <artifactId>filter-valve</artifactId>
+  <packaging>bundle</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>equinoxSDK381</groupId>
+      <artifactId>javax.servlet</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>orbit</groupId>
+      <artifactId>org.apache.catalina</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <configuration>
+          <instructions>
+            <Fragment-Host>org.eclipse.gemini.web.tomcat</Fragment-Host>
+            <Import-Package>javax.servlet,
+                            org.apache.catalina,
+                            org.apache.catalina.connector,
+                            org.apache.catalina.valves,
+                            org.slf4j,
+                            javax.xml.bind,
+                            javax.xml.bind.annotation,
+                            org.apache.commons.io,
+                            com.google.common.base,
+                            com.google.common.collect</Import-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.opendaylight.yangtools</groupId>
+        <artifactId>yang-maven-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
diff --git a/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/FilterValve.java b/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/FilterValve.java
new file mode 100644 (file)
index 0000000..54d8be1
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.filtervalve.cors;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Objects;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.commons.io.FileUtils;
+import org.opendaylight.controller.filtervalve.cors.jaxb.Host;
+import org.opendaylight.controller.filtervalve.cors.jaxb.Parser;
+import org.opendaylight.controller.filtervalve.cors.model.FilterProcessor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Valve that allows adding filters per context. Each context can have its own filter definitions.
+ * Main purpose is to allow externalizing security filters from application bundles to a single
+ * file per OSGi distribution.
+ */
+public class FilterValve extends ValveBase {
+    private static final Logger logger = LoggerFactory.getLogger(FilterValve.class);
+    private FilterProcessor filterProcessor;
+
+    public void invoke(final Request request, final Response response) throws IOException, ServletException {
+        if (filterProcessor == null) {
+            throw new IllegalStateException("Initialization error");
+        }
+
+        FilterChain nextValveFilterChain = new FilterChain() {
+            @Override
+            public void doFilter(ServletRequest req, ServletResponse resp) throws IOException, ServletException {
+                boolean reqEquals = Objects.equals(request, req);
+                boolean respEquals = Objects.equals(response, resp);
+                if (reqEquals == false || respEquals == false) {
+                    logger.error("Illegal change was detected by valve - request {} or " +
+                            "response {} was replaced by a filter. This is not supported by this valve",
+                            reqEquals, respEquals);
+                    throw new IllegalStateException("Request or response was replaced in a filter");
+                }
+                getNext().invoke(request, response);
+            }
+        };
+        filterProcessor.process(request, response, nextValveFilterChain);
+    }
+
+    /**
+     * Called by Tomcat when configurationFile attribute is set.
+     * @param fileName path to xml file containing valve configuration
+     * @throws Exception
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public void setConfigurationFile(String fileName) throws Exception {
+        File configurationFile = new File(fileName);
+        if (configurationFile.exists() == false || configurationFile.canRead() == false) {
+            throw new IllegalArgumentException(
+                    "Cannot read 'configurationFile' of this valve defined in tomcat-server.xml: " + fileName);
+        }
+        String xmlContent;
+        try {
+            xmlContent = FileUtils.readFileToString(configurationFile);
+        } catch (IOException e) {
+            logger.error("Cannot read {} of this valve defined in tomcat-server.xml", fileName, e);
+            throw new IllegalStateException("Cannot read " + fileName, e);
+        }
+        Host host;
+        try {
+            host = Parser.parse(xmlContent, fileName);
+        } catch (Exception e) {
+            logger.error("Cannot parse {} of this valve defined in tomcat-server.xml", fileName, e);
+            throw new IllegalStateException("Error while parsing " + fileName, e);
+        }
+        filterProcessor = new FilterProcessor(host);
+    }
+
+    /**
+     * @see org.apache.catalina.valves.ValveBase#getInfo()
+     */
+    public String getInfo() {
+        return getClass() + "/1.0";
+    }
+}
diff --git a/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/Context.java b/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/Context.java
new file mode 100644 (file)
index 0000000..dbe0745
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.filtervalve.cors.jaxb;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
+
+import com.google.common.base.Optional;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import org.opendaylight.controller.filtervalve.cors.model.UrlMatcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@XmlRootElement
+public class Context {
+    private static final Logger logger = LoggerFactory.getLogger(Context.class);
+
+    private String path;
+    private List<Filter> filters = new ArrayList<>();
+    private List<FilterMapping> filterMappings = new ArrayList<>();
+    private boolean initialized;
+    private UrlMatcher<Filter> urlMatcher;
+
+
+    public synchronized void initialize(String fileName, Map<String, Filter> namesToTemplates) {
+        checkState(initialized == false, "Already initialized");
+        Map<String, Filter> namesToFilters = new HashMap<>();
+        for (Filter filter : filters) {
+            try {
+                filter.initialize(fileName, Optional.fromNullable(namesToTemplates.get(filter.getFilterName())));
+            } catch (Exception e) {
+                throw new IllegalStateException(format("Error while processing filter %s of context %s, defined in %s",
+                        filter.getFilterName(), path, fileName), e);
+            }
+            namesToFilters.put(filter.getFilterName(), filter);
+        }
+        filters = Collections.unmodifiableList(new ArrayList<>(filters));
+        LinkedHashMap<String, Filter> patternMap = new LinkedHashMap<>();
+        for (FilterMapping filterMapping : filterMappings) {
+            filterMapping.initialize();
+            Filter found = namesToFilters.get(filterMapping.getFilterName());
+            if (found != null) {
+                patternMap.put(filterMapping.getUrlPattern(), found);
+            } else {
+                logger.error("Cannot find matching filter for filter-mapping {} of context {}, defined in {}",
+                        filterMapping.getFilterName(), path, fileName);
+                throw new IllegalStateException(format(
+                        "Cannot find filter for filter-mapping %s of context %s, defined in %s",
+                        filterMapping.getFilterName(), path, fileName));
+            }
+        }
+        filterMappings = Collections.unmodifiableList(new ArrayList<>(filterMappings));
+        urlMatcher = new UrlMatcher<>(patternMap);
+        initialized = true;
+    }
+
+    public List<Filter> findMatchingFilters(String pathInfo) {
+        checkState(initialized, "Not initialized");
+        return urlMatcher.findMatchingFilters(pathInfo);
+    }
+
+    @XmlAttribute(name = "path")
+    public String getPath() {
+        return path;
+    }
+
+    public void setPath(String path) {
+        checkArgument(initialized == false, "Already initialized");
+        this.path = path;
+    }
+
+    @XmlElement(name = "filter")
+    public List<Filter> getFilters() {
+        return filters;
+    }
+
+    public void setFilters(List<Filter> filters) {
+        checkArgument(initialized == false, "Already initialized");
+        this.filters = filters;
+    }
+
+    @XmlElement(name = "filter-mapping")
+    public List<FilterMapping> getFilterMappings() {
+        return filterMappings;
+    }
+
+    public void setFilterMappings(List<FilterMapping> filterMappings) {
+        checkArgument(initialized == false, "Already initialized");
+        this.filterMappings = filterMappings;
+    }
+
+    @Override
+    public String toString() {
+        return "Context{" +
+                "path='" + path + '\'' +
+                '}';
+    }
+}
diff --git a/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/Filter.java b/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/Filter.java
new file mode 100644 (file)
index 0000000..3dde5b1
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.filtervalve.cors.jaxb;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.MapDifference;
+import com.google.common.collect.Maps;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@XmlRootElement
+public class Filter implements FilterConfig {
+    private static final Logger logger = LoggerFactory.getLogger(Filter.class);
+
+    private String filterName;
+    private String filterClass;
+    private List<InitParam> initParams = new ArrayList<>();
+    private javax.servlet.Filter actualFilter;
+    private boolean initialized, isTemplate;
+
+
+    /**
+     * Called in filter-template nodes defined in <Host/> node - do not actually initialize the filter.
+     * In this case filter is only used to hold values of init params to be merged with
+     * filter defined in <Context/>
+     */
+    public synchronized void initializeTemplate(){
+        checkState(initialized == false, "Already initialized");
+        for (InitParam initParam : initParams) {
+            initParam.inititialize();
+        }
+        isTemplate = true;
+        initialized = true;
+    }
+
+
+    public synchronized void initialize(String fileName, Optional<Filter> maybeTemplate) {
+        checkState(initialized == false, "Already initialized");
+        logger.trace("Initializing filter {} : {}", filterName, filterClass);
+        for (InitParam initParam : initParams) {
+            initParam.inititialize();
+        }
+        if (maybeTemplate.isPresent()) {
+            // merge non conflicting init params
+            Filter template = maybeTemplate.get();
+            checkArgument(template.isTemplate);
+            Map<String, InitParam> templateParams = template.getInitParamsMap();
+            Map<String, InitParam> currentParams = getInitParamsMap();
+            // add values of template that are not present in current
+            MapDifference<String, InitParam> difference = Maps.difference(templateParams, currentParams);
+            for (Entry<String, InitParam> templateUnique : difference.entriesOnlyOnLeft().entrySet()) {
+                initParams.add(templateUnique.getValue());
+            }
+            // merge filterClass
+            if (filterClass == null) {
+                filterClass = template.filterClass;
+            } else if (Objects.equals(filterClass, template.filterClass) == false) {
+                logger.error("Conflict detected in filter-class of {} defined in {}, template class {}, child class {}" ,
+                        filterName, fileName, template.filterClass, filterClass);
+                throw new IllegalStateException("Conflict detected in template/filter filter-class definitions," +
+                        " filter name: " + filterName + " in file " + fileName);
+            }
+        }
+        initParams = Collections.unmodifiableList(new ArrayList<>(initParams));
+        Class<?> clazz;
+        try {
+            clazz = Class.forName(filterClass);
+        } catch (Exception e) {
+            throw new IllegalStateException("Cannot instantiate class defined in filter " + filterName
+                    + " in file " + fileName, e);
+        }
+        try {
+            actualFilter = (javax.servlet.Filter) clazz.newInstance();
+        } catch (Exception e) {
+            throw new IllegalStateException("Cannot instantiate class defined in filter " + filterName
+                    + " in file " + fileName, e);
+        }
+        logger.trace("Initializing {} with following init-params:{}", filterName, getInitParams());
+        try {
+            actualFilter.init(this);
+        } catch (Exception e) {
+            throw new IllegalStateException("Cannot initialize filter " + filterName
+                    + " in file " + fileName, e);
+        }
+        initialized = true;
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        throw new UnsupportedOperationException("Getting ServletContext is currently not supported");
+    }
+
+    @Override
+    public String getInitParameter(String name) {
+        for (InitParam initParam : initParams) {
+            if (Objects.equals(name, initParam.getParamName())) {
+                return initParam.getParamValue();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+        final Iterator<InitParam> iterator = initParams.iterator();
+        return new Enumeration<String>() {
+            @Override
+            public boolean hasMoreElements() {
+                return iterator.hasNext();
+            }
+
+            @Override
+            public String nextElement() {
+                return iterator.next().getParamName();
+            }
+        };
+    }
+
+    public javax.servlet.Filter getActualFilter() {
+        checkState(initialized, "Not initialized");
+        return actualFilter;
+    }
+
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+
+    @XmlElement(name = "filter-name")
+    public String getFilterName() {
+        return filterName;
+    }
+
+    public void setFilterName(String filterName) {
+        this.filterName = filterName;
+    }
+
+    @XmlElement(name = "filter-class")
+    public String getFilterClass() {
+        return filterClass;
+    }
+
+    public void setFilterClass(String filterClass) {
+        this.filterClass = filterClass;
+    }
+
+    @XmlElement(name = "init-param")
+    public List<InitParam> getInitParams() {
+        return initParams;
+    }
+
+    public void setInitParams(List<InitParam> initParams) {
+        this.initParams = initParams;
+    }
+
+
+    @Override
+    public String toString() {
+        return "Filter{" +
+                "filterName='" + filterName + '\'' +
+                '}';
+    }
+
+    public Map<String, InitParam> getInitParamsMap() {
+        Map<String, InitParam> result = new HashMap<>();
+        for (InitParam initParam : initParams) {
+            checkState(initParam.isInitialized());
+            result.put(initParam.getParamName(), initParam);
+        }
+        return result;
+    }
+}
diff --git a/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/FilterMapping.java b/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/FilterMapping.java
new file mode 100644 (file)
index 0000000..03fcbf2
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.filtervalve.cors.jaxb;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class FilterMapping {
+    private String filterName;
+    private String urlPattern;
+    private boolean initialized;
+
+    @XmlElement(name = "filter-name")
+    public String getFilterName() {
+        return filterName;
+    }
+
+    public void setFilterName(String filterName) {
+        checkArgument(initialized == false, "Already initialized");
+        this.filterName = filterName;
+    }
+
+    @XmlElement(name = "url-pattern")
+    public String getUrlPattern() {
+        return urlPattern;
+    }
+
+    public void setUrlPattern(String urlPattern) {
+        checkArgument(initialized == false, "Already initialized");
+        this.urlPattern = urlPattern;
+    }
+
+    public synchronized void initialize() {
+        checkArgument(initialized == false, "Already initialized");
+        initialized = true;
+    }
+
+    public boolean isInitialized() {
+        return initialized;
+    }
+}
diff --git a/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/Host.java b/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/Host.java
new file mode 100644 (file)
index 0000000..4e3c3ba
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.filtervalve.cors.jaxb;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.Optional;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+
+/**
+ * Root element, arbitrarily named Host to match tomcat-server.xml, but does not allow specifying which host
+ * name to be matched.
+ */
+@XmlRootElement(name = "Host")
+public class Host {
+    private List<Context> contexts = new ArrayList<>();
+    private List<Filter> filterTemplates = new ArrayList<>();
+    private boolean initialized;
+    private Map<String, Context> contextMap;
+
+
+    public synchronized void initialize(String fileName) {
+        checkState(initialized == false, "Already initialized");
+        Map<String, Filter> namesToTemplates = new HashMap<>();
+        for (Filter template : filterTemplates) {
+            template.initializeTemplate();
+            namesToTemplates.put(template.getFilterName(), template);
+        }
+        contextMap = new HashMap<>();
+        for (Context context : getContexts()) {
+            checkState(contextMap.containsKey(context.getPath()) == false,
+                    "Context {} already defined in {}", context.getPath(), fileName);
+            context.initialize(fileName, namesToTemplates);
+            contextMap.put(context.getPath(), context);
+        }
+        contextMap = Collections.unmodifiableMap(new HashMap<>(contextMap));
+        contexts = Collections.unmodifiableList(new ArrayList<>(contexts));
+        initialized = true;
+    }
+
+    public Optional<Context> findContext(String contextPath) {
+        checkState(initialized, "Not initialized");
+        Context context = contextMap.get(contextPath);
+        return Optional.fromNullable(context);
+    }
+
+    @XmlElement(name = "Context")
+    public List<Context> getContexts() {
+        return contexts;
+    }
+
+    public void setContexts(List<Context> contexts) {
+        checkArgument(initialized == false, "Already initialized");
+        this.contexts = contexts;
+    }
+
+    @XmlElement(name = "filter-template")
+    public List<Filter> getFilterTemplates() {
+        return filterTemplates;
+    }
+
+    public void setFilterTemplates(List<Filter> filterTemplates) {
+        checkArgument(initialized == false, "Already initialized");
+        this.filterTemplates = filterTemplates;
+    }
+}
diff --git a/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/InitParam.java b/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/InitParam.java
new file mode 100644 (file)
index 0000000..edc9e45
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.filtervalve.cors.jaxb;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class InitParam {
+    private String paramName;
+    private String paramValue;
+    private boolean initialized;
+
+    public synchronized void inititialize() {
+        checkState(initialized == false, "Already initialized");
+        initialized = true;
+    }
+
+    @XmlElement(name = "param-name")
+    public String getParamName() {
+        return paramName;
+    }
+
+    public void setParamName(String paramName) {
+        this.paramName = paramName;
+    }
+
+    @XmlElement(name = "param-value")
+    public String getParamValue() {
+        return paramValue;
+    }
+
+    public void setParamValue(String paramValue) {
+        this.paramValue = paramValue;
+    }
+
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+    @Override
+    public String toString() {
+        return "{" + paramName + '=' + paramValue + "}";
+    }
+}
diff --git a/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/Parser.java b/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/jaxb/Parser.java
new file mode 100644 (file)
index 0000000..bc4f12e
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.filtervalve.cors.jaxb;
+
+import java.io.StringReader;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+
+public class Parser {
+
+    public static Host parse(String xmlFileContent, String fileName) throws JAXBException {
+        JAXBContext context = JAXBContext.newInstance(Host.class);
+        javax.xml.bind.Unmarshaller um = context.createUnmarshaller();
+        Host host = (Host) um.unmarshal(new StringReader(xmlFileContent));
+        host.initialize(fileName);
+        return host;
+    }
+
+}
diff --git a/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/model/FilterProcessor.java b/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/model/FilterProcessor.java
new file mode 100644 (file)
index 0000000..dc3e9dc
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.filtervalve.cors.model;
+
+import com.google.common.base.Optional;
+import java.io.IOException;
+import java.util.List;
+import java.util.ListIterator;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.opendaylight.controller.filtervalve.cors.jaxb.Context;
+import org.opendaylight.controller.filtervalve.cors.jaxb.Filter;
+import org.opendaylight.controller.filtervalve.cors.jaxb.Host;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FilterProcessor {
+    private static final Logger logger = LoggerFactory.getLogger(FilterProcessor.class);
+
+    private final Host host;
+
+    public FilterProcessor(Host host) {
+        this.host = host;
+    }
+
+    public void process(Request request, Response response, FilterChain nextValveFilterChain)
+            throws IOException, ServletException {
+
+        String contextPath = request.getContext().getPath();
+        String pathInfo = request.getPathInfo();
+
+        Optional<Context> maybeContext = host.findContext(contextPath);
+        logger.trace("Processing context {} path {}, found {}", contextPath, pathInfo, maybeContext);
+        if (maybeContext.isPresent()) {
+            // process filters
+            Context context = maybeContext.get();
+            List<Filter> matchingFilters = context.findMatchingFilters(pathInfo);
+            FilterChain fromLast = nextValveFilterChain;
+            ListIterator<Filter> it = matchingFilters.listIterator(matchingFilters.size());
+            final boolean trace = logger.isTraceEnabled();
+            while (it.hasPrevious()) {
+                final Filter currentFilter = it.previous();
+                final FilterChain copy = fromLast;
+                fromLast = new FilterChain() {
+                    @Override
+                    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
+                        if (trace) {
+                            logger.trace("Applying {}", currentFilter);
+                        }
+                        javax.servlet.Filter actualFilter = currentFilter.getActualFilter();
+                        actualFilter.doFilter(request, response, copy);
+                    }
+                };
+            }
+            // call first filter
+            fromLast.doFilter(request, response);
+        } else {
+            // move to next valve
+            nextValveFilterChain.doFilter(request, response);
+        }
+    }
+}
diff --git a/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/model/UrlMatcher.java b/opendaylight/commons/filter-valve/src/main/java/org/opendaylight/controller/filtervalve/cors/model/UrlMatcher.java
new file mode 100644 (file)
index 0000000..9535fb1
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.filtervalve.cors.model;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Maps.immutableEntry;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Match incoming URL with user defined patterns according to servlet specification.
+ * In the Web application deployment descriptor, the following syntax is used to define mappings:
+ * <ul>
+ * <li>A string beginning with a â€˜/’ character and ending with a â€˜/*’ suffix is used for path mapping.</li>
+ * <li>A string beginning with a â€˜*.’ prefix is used as an extension mapping.</li>
+ * <li>All other strings are used for exact matches only.</li>
+ * </ul>
+ */
+public class UrlMatcher<FILTER> {
+    private static final Logger logger = LoggerFactory.getLogger(UrlMatcher.class);
+    // order index for each FILTER is kept as Entry.value
+    private final Map<String, Entry<FILTER, Integer>> prefixMap = new HashMap<>(); // contains patterns ending with '/*', '*' is stripped from each key
+    private final Map<String, Entry<FILTER, Integer>> suffixMap = new HashMap<>(); // contains patterns starting with '*.' prefix, '*' is stripped from each key
+    private final Map<String, Entry<FILTER, Integer>> exactMatchMap = new HashMap<>(); // contains exact matches only
+
+    /**
+     * @param patternMap order preserving map containing path info pattern as key
+     */
+    public UrlMatcher(LinkedHashMap<String, FILTER> patternMap) {
+        int idx = 0;
+        for (Entry<String, FILTER> entry : patternMap.entrySet()) {
+            idx++;
+            String pattern = checkNotNull(entry.getKey());
+            FILTER value = entry.getValue();
+            Entry<FILTER, Integer> valueWithIdx = immutableEntry(value, idx);
+            if (pattern.startsWith("/") && pattern.endsWith("/*")) {
+                pattern = pattern.substring(0, pattern.length() - 1);
+                prefixMap.put(pattern, valueWithIdx);
+            } else if (pattern.startsWith("*.")) {
+                pattern = pattern.substring(1);
+                suffixMap.put(pattern, valueWithIdx);
+            } else {
+                exactMatchMap.put(pattern, valueWithIdx);
+            }
+        }
+    }
+
+    /**
+     * Find filters matching path
+     *
+     * @param pathInfo as returned by request.getPathInfo()
+     * @return list of matching filters
+     */
+    public List<FILTER> findMatchingFilters(String pathInfo) {
+        checkNotNull(pathInfo);
+        TreeMap<Integer, FILTER> sortedMap = new TreeMap<>();
+        // add matching prefixes
+        for (Entry<String, Entry<FILTER, Integer>> prefixEntry : prefixMap.entrySet()) {
+            if (pathInfo.startsWith(prefixEntry.getKey())) {
+                put(sortedMap, prefixEntry.getValue());
+            }
+        }
+        // add matching suffixes
+        for (Entry<String, Entry<FILTER, Integer>> suffixEntry : suffixMap.entrySet()) {
+            if (pathInfo.endsWith(suffixEntry.getKey())) {
+                put(sortedMap, suffixEntry.getValue());
+            }
+        }
+        // add exact match
+        Entry<FILTER, Integer> exactMatch = exactMatchMap.get(pathInfo);
+        if (exactMatch != null) {
+            put(sortedMap, exactMatch);
+        }
+        ArrayList<FILTER> filters = new ArrayList<>(sortedMap.values());
+        logger.trace("Matching filters for path {} are {}", pathInfo, filters);
+        return filters;
+    }
+
+    private void put(TreeMap<Integer, FILTER> sortedMap, Entry<FILTER, Integer> entry) {
+        sortedMap.put(entry.getValue(), entry.getKey());
+    }
+}
diff --git a/opendaylight/commons/filter-valve/src/test/java/org/opendaylight/controller/filtervalve/cors/jaxb/DummyFilter.java b/opendaylight/commons/filter-valve/src/test/java/org/opendaylight/controller/filtervalve/cors/jaxb/DummyFilter.java
new file mode 100644 (file)
index 0000000..d14caf9
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.filtervalve.cors.jaxb;
+
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+public class DummyFilter implements javax.servlet.Filter {
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void destroy() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/opendaylight/commons/filter-valve/src/test/java/org/opendaylight/controller/filtervalve/cors/jaxb/MockedFilter.java b/opendaylight/commons/filter-valve/src/test/java/org/opendaylight/controller/filtervalve/cors/jaxb/MockedFilter.java
new file mode 100644 (file)
index 0000000..56d851b
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.filtervalve.cors.jaxb;
+
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+public class MockedFilter implements javax.servlet.Filter {
+    private FilterConfig filterConfig;
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        this.filterConfig = filterConfig;
+    }
+
+    public FilterConfig getFilterConfig() {
+        return filterConfig;
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void destroy() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/opendaylight/commons/filter-valve/src/test/java/org/opendaylight/controller/filtervalve/cors/jaxb/ParserTest.java b/opendaylight/commons/filter-valve/src/test/java/org/opendaylight/controller/filtervalve/cors/jaxb/ParserTest.java
new file mode 100644 (file)
index 0000000..fc6c01b
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.filtervalve.cors.jaxb;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.matchers.JUnitMatchers.containsString;
+
+import com.google.common.base.Optional;
+import java.io.File;
+import javax.servlet.FilterConfig;
+import org.apache.commons.io.FileUtils;
+import org.junit.Test;
+
+public class ParserTest {
+
+    @Test
+    public void testParsing() throws Exception {
+        File xmlFile = new File(getClass().getResource("/sample-cors-config.xml").getFile());
+        assertThat(xmlFile.canRead(), is(true));
+        String xmlFileContent = FileUtils.readFileToString(xmlFile);
+        Host host = Parser.parse(xmlFileContent, "fileName");
+        assertEquals(1, host.getContexts().size());
+        // check that MockedFilter has init params merged/replaced
+        Optional<Context> context = host.findContext("/restconf");
+        assertTrue(context.isPresent());
+        assertEquals(1, context.get().getFilters().size());
+        MockedFilter filter = (MockedFilter) context.get().getFilters().get(0).getActualFilter();
+        FilterConfig filterConfig = filter.getFilterConfig();
+        assertEquals("*", filterConfig.getInitParameter("cors.allowed.origins"));
+        assertEquals("11", filterConfig.getInitParameter("cors.preflight.maxage"));
+    }
+
+
+    @Test
+    public void testParsing_NoFilterDefined() throws Exception {
+        File xmlFile = new File(getClass().getResource("/no-filter-defined.xml").getFile());
+        assertThat(xmlFile.canRead(), is(true));
+        String xmlFileContent = FileUtils.readFileToString(xmlFile);
+        try {
+            Parser.parse(xmlFileContent, "fileName");
+            fail();
+        }catch(Exception e){
+            assertThat(e.getMessage(), containsString("Cannot find filter for filter-mapping CorsFilter"));
+        }
+    }
+
+    @Test
+    public void testConflictingClass() throws Exception {
+        File xmlFile = new File(getClass().getResource("/conflicting-class.xml").getFile());
+        assertThat(xmlFile.canRead(), is(true));
+        String xmlFileContent = FileUtils.readFileToString(xmlFile);
+        try {
+            Parser.parse(xmlFileContent, "fileName");
+            fail();
+        } catch (RuntimeException e) {
+            assertThat(e.getMessage(), containsString("Error while processing filter CorsFilter of context /restconf"));
+            assertThat(e.getCause().getMessage(), containsString("Conflict detected in template/filter filter-class definitions, filter name: CorsFilter"));
+        }
+    }
+}
diff --git a/opendaylight/commons/filter-valve/src/test/java/org/opendaylight/controller/filtervalve/cors/model/UrlMatcherTest.java b/opendaylight/commons/filter-valve/src/test/java/org/opendaylight/controller/filtervalve/cors/model/UrlMatcherTest.java
new file mode 100644 (file)
index 0000000..07f6354
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.filtervalve.cors.model;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+
+import java.util.LinkedHashMap;
+import org.junit.Test;
+
+public class UrlMatcherTest {
+    UrlMatcher<String> urlMatcher;
+
+    @Test
+    public void test() throws Exception {
+        final String defaultFilter = "default";
+        final String exactMatchFilter = "someFilter";
+        final String jspFilter = "jspFilter";
+        final String exactMatch = "/somePath";
+        final String prefixFilter = "prefixFilter";
+        LinkedHashMap<String, String> patternMap = new LinkedHashMap<String, String>() {
+            {
+                put(exactMatch, exactMatchFilter);
+                put("/*", defaultFilter);
+                put("*.jsp", jspFilter);
+                put("/foo/*", prefixFilter);
+            }
+        };
+        urlMatcher = new UrlMatcher<>(patternMap);
+        assertMatches("/abc", defaultFilter);
+        assertMatches(exactMatch, exactMatchFilter, defaultFilter);
+        assertMatches("/some.jsp", defaultFilter, jspFilter);
+        assertMatches("/foo/bar", defaultFilter, prefixFilter);
+        assertMatches("/foo/bar.jsp", defaultFilter, jspFilter, prefixFilter);
+    }
+
+    public void assertMatches(String testedPath, String... filters) {
+        assertEquals(asList(filters), urlMatcher.findMatchingFilters(testedPath));
+    }
+
+}
diff --git a/opendaylight/commons/filter-valve/src/test/resources/conflicting-class.xml b/opendaylight/commons/filter-valve/src/test/resources/conflicting-class.xml
new file mode 100644 (file)
index 0000000..c1faf34
--- /dev/null
@@ -0,0 +1,34 @@
+<!--
+  ~ Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+  ~
+  ~ This program and the accompanying materials are made available under the
+  ~ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+  ~ and is available at http://www.eclipse.org/legal/epl-v10.html
+  -->
+
+<Host>
+    <filter-template>
+        <filter-name>CorsFilter</filter-name>
+        <filter-class>org.opendaylight.controller.filtervalve.cors.jaxb.MockedFilter</filter-class>
+        <init-param>
+            <param-name>cors.preflight.maxage</param-name>
+            <param-value>10</param-value>
+        </init-param>
+        <init-param>
+            <param-name>cors.allowed.origins</param-name>
+            <param-value>*</param-value>
+        </init-param>
+    </filter-template>
+
+    <Context path="/restconf">
+        <filter>
+            <filter-name>CorsFilter</filter-name>
+            <!-- conflict -->
+            <filter-class>org.opendaylight.controller.filtervalve.cors.jaxb.DummyFilter</filter-class>
+        </filter>
+        <filter-mapping>
+            <filter-name>CorsFilter</filter-name>
+            <url-pattern>/*</url-pattern>
+        </filter-mapping>
+    </Context>
+</Host>
diff --git a/opendaylight/commons/filter-valve/src/test/resources/no-filter-defined.xml b/opendaylight/commons/filter-valve/src/test/resources/no-filter-defined.xml
new file mode 100644 (file)
index 0000000..521d578
--- /dev/null
@@ -0,0 +1,61 @@
+<!--
+  ~ Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+  ~
+  ~ This program and the accompanying materials are made available under the
+  ~ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+  ~ and is available at http://www.eclipse.org/legal/epl-v10.html
+  -->
+
+<Host>
+    <!-- Filters are allowed here, only serving as a template -->
+    <filter-template>
+        <filter-name>CorsFilter</filter-name>
+        <filter-class>org.opendaylight.controller.filtervalve.cors.jaxb.MockedFilter</filter-class>
+        <init-param>
+            <param-name>cors.allowed.origins</param-name>
+            <param-value>*</param-value>
+        </init-param>
+        <init-param>
+            <param-name>cors.allowed.methods</param-name>
+            <param-value>GET,POST,HEAD,OPTIONS,PUT,DELETE</param-value>
+        </init-param>
+        <init-param>
+            <param-name>cors.allowed.headers</param-name>
+            <param-value>Content-Type,X-Requested-With,accept,authorization,
+                origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers
+            </param-value>
+        </init-param>
+        <init-param>
+            <param-name>cors.exposed.headers</param-name>
+            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
+        </init-param>
+        <init-param>
+            <param-name>cors.support.credentials</param-name>
+            <param-value>true</param-value>
+        </init-param>
+        <init-param>
+            <param-name>cors.preflight.maxage</param-name>
+            <param-value>10</param-value>
+        </init-param>
+    </filter-template>
+
+    <Context path="/restconf">
+        <!-- Filters are also allowed here. -->
+        <filter>
+            <filter-name>CorsFilter</filter-name>
+            <!-- init params can be added/overriden if template is used -->
+        </filter>
+        <!-- only local references are allowed -->
+        <filter-mapping>
+            <filter-name>CorsFilter</filter-name>
+            <url-pattern>/*</url-pattern>
+        </filter-mapping>
+    </Context>
+
+    <Context path="/controller/nb/v2/connectionmanager">
+        <filter-mapping>
+            <filter-name>CorsFilter</filter-name>
+            <url-pattern>/*</url-pattern>
+        </filter-mapping>
+    </Context>
+</Host>
diff --git a/opendaylight/commons/filter-valve/src/test/resources/sample-cors-config.xml b/opendaylight/commons/filter-valve/src/test/resources/sample-cors-config.xml
new file mode 100644 (file)
index 0000000..613dc82
--- /dev/null
@@ -0,0 +1,37 @@
+<!--
+  ~ Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+  ~
+  ~ This program and the accompanying materials are made available under the
+  ~ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+  ~ and is available at http://www.eclipse.org/legal/epl-v10.html
+  -->
+
+<Host>
+    <filter-template>
+        <filter-name>CorsFilter</filter-name>
+        <filter-class>org.opendaylight.controller.filtervalve.cors.jaxb.MockedFilter</filter-class>
+        <init-param>
+            <param-name>cors.preflight.maxage</param-name>
+            <param-value>10</param-value>
+        </init-param>
+        <init-param>
+            <param-name>cors.allowed.origins</param-name>
+            <param-value>*</param-value>
+        </init-param>
+    </filter-template>
+
+    <Context path="/restconf">
+        <filter>
+            <filter-name>CorsFilter</filter-name>
+            <!-- override value -->
+            <init-param>
+                <param-name>cors.preflight.maxage</param-name>
+                <param-value>11</param-value>
+            </init-param>
+        </filter>
+        <filter-mapping>
+            <filter-name>CorsFilter</filter-name>
+            <url-pattern>/*</url-pattern>
+        </filter-mapping>
+    </Context>
+</Host>
index 077b452f0ac96623c4b9f445d91445f9cae57cd1..c3c8168bd77abcf00d84baeabad33576984c54e5 100644 (file)
@@ -65,6 +65,7 @@
     <felix.dependencymanager.version>3.1.0</felix.dependencymanager.version>
     <felix.fileinstall.version>3.1.6</felix.fileinstall.version>
     <felix.webconsole.version>4.2.0</felix.webconsole.version>
+    <filtervalve.version>1.4.2-SNAPSHOT</filtervalve.version>
     <flowprogrammer.northbound.version>0.4.2-SNAPSHOT</flowprogrammer.northbound.version>
     <flows.web.version>0.4.2-SNAPSHOT</flows.web.version>
     <forwarding.staticrouting>0.5.2-SNAPSHOT</forwarding.staticrouting>
     <jsr311.api.version>1.1.1</jsr311.api.version>
     <jsr311.v2.api.version>2.0</jsr311.v2.api.version>
     <junit.version>4.8.1</junit.version>
+    <karaf.branding.version>1.0.0-SNAPSHOT</karaf.branding.version>
     <karaf.version>3.0.1</karaf.version>
     <logback.version>1.0.9</logback.version>
     <logging.bridge.version>0.4.2-SNAPSHOT</logging.bridge.version>
         <artifactId>devices.web</artifactId>
         <version>${devices.web.version}</version>
       </dependency>
+      <dependency>
+        <groupId>org.opendaylight.controller</groupId>
+        <artifactId>filter-valve</artifactId>
+        <version>${filtervalve.version}</version>
+      </dependency>
       <dependency>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>flowprogrammer.northbound</artifactId>
         <artifactId>jolokia-bridge</artifactId>
         <version>0.0.2-SNAPSHOT</version>
       </dependency>
+      <!-- Karaf Dependencies -->
+      <dependency>
+        <groupId>org.opendaylight.controller</groupId>
+        <artifactId>karaf.branding</artifactId>
+        <version>${karaf.branding.version}</version>
+      </dependency>
       <dependency>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>logback-config</artifactId>
         <artifactId>osgi-brandfragment.web</artifactId>
         <version>${osgi-brandfragment.web.version}</version>
       </dependency>
-
+      <!-- Southbound bundles -->
       <dependency>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>protocol-framework</artifactId>
         <version>${protocol-framework.version}</version>
       </dependency>
-
-      <!-- Southbound bundles -->
       <dependency>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>protocol_plugins.openflow</artifactId>
index 31cdedd5724f89a0da51f2880219b765562b1605..67c30454f86cc7b640c871b2e89955dda452262e 100644 (file)
       <version>${karaf.version}</version>
       <type>kar</type>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>karaf.branding</artifactId>
+      <scope>compile</scope>
+    </dependency>
     <!-- scope is runtime so the feature repo is listed in the features
       service config file, and features may be installed using the
       karaf-maven-plugin configuration -->
@@ -34,8 +39,6 @@
       <scope>runtime</scope>
     </dependency>
     <dependency>
-      <!-- scope is compile so all features (there is only one) are installed
-            into startup.properties and the feature repo itself is not installed -->
       <groupId>org.opendaylight.controller</groupId>
       <artifactId>base-features</artifactId>
       <version>${project.version}</version>
         </configuration>
       </plugin>
       <plugin>
-        <artifactId>maven-resources-plugin</artifactId>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
         <version>2.6</version>
         <executions>
           <execution>
-            <id>copy-resources</id>
+            <id>copy</id>
             <goals>
-              <goal>copy-resources</goal>
+              <goal>copy</goal>
             </goals>
             <!-- here the phase you need -->
-            <phase>process-resources</phase>
+            <phase>generate-resources</phase>
             <configuration>
-              <outputDirectory>${basedir}/target/assembly</outputDirectory>
-              <overwrite>true</overwrite>
-              <resources>
-                <resource>
-                  <directory>${basedir}/src/main/resources</directory>
-                </resource>
-              </resources>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.opendaylight.controller</groupId>
+                  <artifactId>karaf.branding</artifactId>
+                  <version>${karaf.branding.version}</version>
+                  <outputDirectory>target/assembly/lib</outputDirectory>
+                  <destFileName>karaf.branding-${branding.version}.jar</destFileName>
+                </artifactItem>
+              </artifactItems>
             </configuration>
           </execution>
         </executions>
index 9a868660809fded6cc6026c721ff2e83f9df9f78..6c1ca421c257ab81807c20f4a939d556cb5338c6 100644 (file)
@@ -1,5 +1,5 @@
 # Extra packages to import from the boot class loader
-org.osgi.framework.system.packages.extra=sun.reflect,sun.reflect.misc,sun.misc,sun.nio.ch
+org.osgi.framework.system.packages.extra=org.apache.karaf.branding,sun.reflect,sun.reflect.misc,sun.misc,sun.nio.ch
 
 # https://bugs.eclipse.org/bugs/show_bug.cgi?id=325578
 # Extend the framework to avoid the resources to be presented with
@@ -111,4 +111,4 @@ org.jolokia.listenForHttpService=false
 java.util.logging.config.file=configuration/tomcat-logging.properties
 
 #Hosttracker hostsdb key scheme setting
-hosttracker.keyscheme=IP
\ No newline at end of file
+hosttracker.keyscheme=IP
index 3802370aca3e8a7726d27d7fab1ca67d6eeb1944..3916e05496ab2a40fc34e2adb71fd53839172ceb 100644 (file)
           <groupId>org.opendaylight.controller</groupId>
           <artifactId>config-persister-impl</artifactId>
         </dependency>
+        <dependency>
+          <groupId>org.opendaylight.controller</groupId>
+          <artifactId>filter-valve</artifactId>
+        </dependency>
         <dependency>
           <groupId>org.opendaylight.controller</groupId>
           <artifactId>logback-config</artifactId>
diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/cors-config.xml b/opendaylight/distribution/opendaylight/src/main/resources/configuration/cors-config.xml
new file mode 100644 (file)
index 0000000..00abf6c
--- /dev/null
@@ -0,0 +1,54 @@
+<!--
+  ~ Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+  ~
+  ~ This program and the accompanying materials are made available under the
+  ~ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+  ~ and is available at http://www.eclipse.org/legal/epl-v10.html
+  -->
+
+<Host>
+    <!-- Filters are allowed here, only serving as a template -->
+    <filter-template>
+        <filter-name>CorsFilter</filter-name>
+        <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
+        <init-param>
+            <param-name>cors.allowed.origins</param-name>
+            <param-value>*</param-value>
+        </init-param>
+        <init-param>
+            <param-name>cors.allowed.methods</param-name>
+            <param-value>GET,POST,HEAD,OPTIONS,PUT,DELETE</param-value>
+        </init-param>
+        <init-param>
+            <param-name>cors.allowed.headers</param-name>
+            <param-value>Content-Type,X-Requested-With,accept,authorization,
+                origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers
+            </param-value>
+        </init-param>
+        <init-param>
+            <param-name>cors.exposed.headers</param-name>
+            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
+        </init-param>
+        <init-param>
+            <param-name>cors.support.credentials</param-name>
+            <param-value>true</param-value>
+        </init-param>
+        <init-param>
+            <param-name>cors.preflight.maxage</param-name>
+            <param-value>10</param-value>
+        </init-param>
+    </filter-template>
+
+    <Context path="/restconf">
+        <filter>
+            <filter-name>CorsFilter</filter-name>
+            <!-- init params can be added/overriden if template is used -->
+        </filter>
+        <!-- references to templates without <filter> declaration are not allowed -->
+        <filter-mapping>
+            <filter-name>CorsFilter</filter-name>
+            <url-pattern>/*</url-pattern>
+        </filter-mapping>
+    </Context>
+
+</Host>
index 56d469b59932f591b93cd91eb7e491ab3c683085..da2500be622e48cf735f5ac349de8d96c4e39c96 100644 (file)
@@ -56,6 +56,9 @@
                         rotatable="true" fileDateFormat="yyyy-MM"
                         pattern="%{yyyy-MM-dd HH:mm:ss.SSS z}t - [%a] - %r"/>
 
+          <Valve className="org.opendaylight.controller.filtervalve.cors.FilterValve"
+                 configurationFile="configuration/cors-config.xml"
+                  />
       </Host>
     </Engine>
   </Service>
diff --git a/opendaylight/karaf-branding/pom.xml b/opendaylight/karaf-branding/pom.xml
new file mode 100644 (file)
index 0000000..727f224
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>karaf.branding</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+    <name>OpenDaylight :: Karaf :: Branding</name>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <version>2.4.0</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+                        <Import-Package>*</Import-Package>
+                        <Private-Package>!*</Private-Package>
+                        <Export-Package>
+                            org.apache.karaf.branding
+                        </Export-Package>
+                        <Spring-Context>*;public-context:=false</Spring-Context>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/opendaylight/karaf-branding/src/main/resources/org/apache/karaf/branding/branding.properties b/opendaylight/karaf-branding/src/main/resources/org/apache/karaf/branding/branding.properties
new file mode 100644 (file)
index 0000000..cf081c6
--- /dev/null
@@ -0,0 +1,14 @@
+welcome = \
+\u001B[33m                                                                                           \r\n\
+\u001B[33m    ________                       ________                .__  .__       .__     __       \r\n\
+\u001B[33m    \\_____  \\ ______   ____   ____ \\______ \\ _____  ___.__.|  | |__| ____ |  |___/  |_     \r\n\
+\u001B[33m     /   |   \\\\____ \\_/ __ \\ /    \\ |    |  \\\\__  \\<   |  ||  | |  |/ ___\\|  |  \\   __\\    \r\n\
+\u001B[33m    /    |    \\  |_> >  ___/|   |  \\|    `   \\/ __ \\\\___  ||  |_|  / /_/  >   Y  \\  |      \r\n\
+\u001B[33m    \\_______  /   __/ \\___  >___|  /_______  (____  / ____||____/__\\___  /|___|  /__|      \r\n\
+\u001B[33m            \\/|__|        \\/     \\/        \\/     \\/\\/            /_____/      \\/          \r\n\
+\u001B[33m                                                                                           \r\n\
+\r\n\
+Hit '\u001B[1m<tab>\u001B[0m' for a list of available commands\r\n\
+   and '\u001B[1m[cmd] --help\u001B[0m' for help on a specific command.\r\n\
+Hit '\u001B[1m<ctrl-d>\u001B[0m' or type '\u001B[1msystem:shutdown\u001B[0m' or '\u001B[1mlogout\u001B[0m' to shutdown OpenDaylight.\r\n
+prompt = \u001B[36mopendaylight-user\u001B[0m\u001B[1m@\u001B[0m\u001B[34m${APPLICATION}\u001B[0m>
index f816018519d61761c3f2c2a81048260bdfee012e..16b457403740b7cac4f3fc137e1a1a9d00db4521 100644 (file)
@@ -3,7 +3,7 @@
 <features name="mdsal-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.2.0 http://karaf.apache.org/xmlns/features/v1.2.0">
-    <feature name='mdsal-all' version='${project.version}'>
+    <feature name='odl-mdsal-all' version='${project.version}'>
         <feature version='${project.version}'>odl-mdsal-commons</feature>
         <feature version='${project.version}'>odl-mdsal-broker</feature>
         <feature version='${project.version}'>odl-mdsal-restconf</feature>
@@ -46,4 +46,4 @@
         <bundle>wrap:mvn:io.netty/netty-handler/${netty.version}</bundle>
         <bundle>wrap:mvn:io.netty/netty-transport/${netty.version}</bundle>
     </feature>
-</features>
\ No newline at end of file
+</features>
index 4b62bf7c2fcb2a1c219c7936063f721e1e7a2f9b..f39eae45424941355b2d9a7291d62eea744048a4 100644 (file)
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 
-    <filter>
-        <filter-name>CorsFilter</filter-name>
-        <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
-        <init-param>
-            <param-name>cors.allowed.origins</param-name>
-            <param-value>*</param-value>
-        </init-param>
-        <init-param>
-            <param-name>cors.allowed.methods</param-name>
-            <param-value>GET,POST,HEAD,OPTIONS,PUT,DELETE</param-value>
-        </init-param>
-        <init-param>
-            <param-name>cors.allowed.headers</param-name>
-            <param-value>Content-Type,X-Requested-With,accept,authorization,
-        origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
-        </init-param>
-        <init-param>
-            <param-name>cors.exposed.headers</param-name>
-            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
-        </init-param>
-        <init-param>
-            <param-name>cors.support.credentials</param-name>
-            <param-value>true</param-value>
-        </init-param>
-        <init-param>
-            <param-name>cors.preflight.maxage</param-name>
-            <param-value>10</param-value>
-        </init-param>
-    </filter>
-    <filter-mapping>
-        <filter-name>CorsFilter</filter-name>
-        <url-pattern>/*</url-pattern>
-    </filter-mapping>
     <security-constraint>
         <web-resource-collection>
             <web-resource-name>NB api</web-resource-name>
diff --git a/pom.xml b/pom.xml
index 3a3c3dcfb462b2d2a56341799760e9074added56..8ad038763c8f2909ff95974b888c281adbcf44ae 100644 (file)
--- a/pom.xml
+++ b/pom.xml
     <module>opendaylight/commons/opendaylight</module>
     <module>opendaylight/commons/parent</module>
     <module>opendaylight/commons/logback_settings</module>
+    <module>opendaylight/commons/filter-valve</module>
 
     <!-- Karaf Distribution -->
     <module>features/base</module>
     <module>opendaylight/dummy-console</module>
+    <module>opendaylight/karaf-branding</module>
     <module>opendaylight/distribution/opendaylight-karaf</module>
   </modules>
   <scm>