--- /dev/null
+<?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>
--- /dev/null
+/*
+ * 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";
+ }
+}
--- /dev/null
+/*
+ * 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 + '\'' +
+ '}';
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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 + "}";
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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());
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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"));
+ }
+ }
+}
--- /dev/null
+/*
+ * 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));
+ }
+
+}
--- /dev/null
+<!--
+ ~ 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>
--- /dev/null
+<!--
+ ~ 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>
--- /dev/null
+<!--
+ ~ 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>
<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>
<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 -->
<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>
# 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
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
<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>
--- /dev/null
+<!--
+ ~ 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>
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>
--- /dev/null
+<?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>
--- /dev/null
+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>
<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>
<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>
<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>
<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>