[DOSGI-242] Extract common module, use DS
Project: http://git-wip-us.apache.org/repos/asf/cxf-dosgi/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf-dosgi/commit/d2a3c75f Tree: http://git-wip-us.apache.org/repos/asf/cxf-dosgi/tree/d2a3c75f Diff: http://git-wip-us.apache.org/repos/asf/cxf-dosgi/diff/d2a3c75f Branch: refs/heads/master Commit: d2a3c75f3a6496ec52961ee7238fcf519bd9a8a5 Parents: 79bb9a4 Author: Christian Schneider <[email protected]> Authored: Fri Jul 1 21:56:50 2016 +0200 Committer: Christian Schneider <[email protected]> Committed: Fri Jul 1 21:56:50 2016 +0200 ---------------------------------------------------------------------- common/bnd.bnd | 6 + common/pom.xml | 78 +++++ .../common/httpservice/HttpServiceManager.java | 179 ++++++++++++ .../dosgi/common/httpservice/LocalHostUtil.java | 92 ++++++ .../SecurityDelegatingHttpContext.java | 133 +++++++++ .../common/intent/DefaultIntentMapFactory.java | 52 ++++ .../cxf/dosgi/common/intent/IntentManager.java | 35 +++ .../dosgi/common/intent/IntentManagerImpl.java | 214 ++++++++++++++ .../cxf/dosgi/common/intent/IntentMap.java | 62 ++++ .../cxf/dosgi/common/intent/IntentTracker.java | 61 ++++ .../cxf/dosgi/common/util/ClassUtils.java | 86 ++++++ .../apache/cxf/dosgi/common/util/OsgiUtils.java | 78 +++++ .../cxf/dosgi/common/util/ServerWrapper.java | 49 ++++ .../cxf/dosgi/common/util/StringPlus.java | 72 +++++ .../httpservice/HttpServiceManagerTest.java | 118 ++++++++ .../SecurityDelegatingHttpContextTest.java | 267 ++++++++++++++++++ .../dosgi/common/qos/IntentManagerImplTest.java | 282 +++++++++++++++++++ .../cxf/dosgi/common/qos/IntentMapTest.java | 42 +++ .../cxf/dosgi/common/qos/IntentTrackerTest.java | 82 ++++++ .../cxf/dosgi/common/util/ClassUtilsTest.java | 114 ++++++++ .../cxf/dosgi/common/util/OsgiUtilsTest.java | 70 +++++ .../apache/cxf/dosgi/common/util/Provider.java | 23 ++ cxf-dsw/bnd.bnd | 3 +- cxf-dsw/pom.xml | 6 + .../dsw/handlers/CXFDistributionProvider.java | 71 ++++- .../AbstractPojoConfigurationTypeHandler.java | 36 ++- .../pojo/PojoConfigurationTypeHandler.java | 4 +- .../pojo/WsdlConfigurationTypeHandler.java | 6 +- .../rest/JaxRSPojoConfigurationTypeHandler.java | 8 +- .../cxf/dosgi/dsw/handlers/rest/JaxRSUtils.java | 4 +- .../dsw/httpservice/HttpServiceManager.java | 176 ------------ .../dosgi/dsw/httpservice/LocalHostUtil.java | 92 ------ .../SecurityDelegatingHttpContext.java | 133 --------- .../apache/cxf/dosgi/dsw/osgi/Activator.java | 153 ---------- .../apache/cxf/dosgi/dsw/osgi/Constants.java | 70 +---- .../dosgi/dsw/qos/DefaultIntentMapFactory.java | 52 ---- .../apache/cxf/dosgi/dsw/qos/IntentManager.java | 31 -- .../cxf/dosgi/dsw/qos/IntentManagerImpl.java | 155 ---------- .../org/apache/cxf/dosgi/dsw/qos/IntentMap.java | 62 ---- .../apache/cxf/dosgi/dsw/qos/IntentTracker.java | 62 ---- .../apache/cxf/dosgi/dsw/qos/IntentUtils.java | 86 ------ .../apache/cxf/dosgi/dsw/util/ClassUtils.java | 86 ------ .../apache/cxf/dosgi/dsw/util/OsgiUtils.java | 78 ----- .../cxf/dosgi/dsw/util/ServerWrapper.java | 49 ---- .../apache/cxf/dosgi/dsw/util/StringPlus.java | 72 ----- .../org/apache/cxf/dosgi/dsw/ActivatorTest.java | 67 ----- .../org/apache/cxf/dosgi/dsw/TestUtils.java | 37 --- .../handlers/CXFDistributionProviderTest.java | 22 +- .../cxf/dosgi/dsw/handlers/ClassUtilsTest.java | 116 -------- .../pojo/PojoConfigurationTypeHandlerTest.java | 35 ++- .../dsw/httpservice/HttpServiceManagerTest.java | 125 -------- .../SecurityDelegatingHttpContextTest.java | 267 ------------------ .../dosgi/dsw/qos/IntentManagerImplTest.java | 278 ------------------ .../apache/cxf/dosgi/dsw/qos/IntentMapTest.java | 41 --- .../cxf/dosgi/dsw/qos/IntentTrackerTest.java | 80 ------ .../cxf/dosgi/dsw/qos/IntentUtilsTest.java | 70 ----- .../cxf/dosgi/dsw/util/OsgiUtilsTest.java | 70 ----- .../org/apache/cxf/dosgi/dsw/util/Provider.java | 23 -- .../features/src/main/resources/features.xml | 6 +- pom.xml | 1 + 60 files changed, 2346 insertions(+), 2582 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/bnd.bnd ---------------------------------------------------------------------- diff --git a/common/bnd.bnd b/common/bnd.bnd new file mode 100644 index 0000000..9fc9a8c --- /dev/null +++ b/common/bnd.bnd @@ -0,0 +1,6 @@ +Import-Package: javax.servlet;version='[2,4)', javax.servlet.http;version='[2,4)', * +Export-Package: \ + org.apache.cxf.dosgi.common.httpservice,\ + org.apache.cxf.dosgi.common.util,\ + org.apache.cxf.dosgi.common.intent +Private-Package: org.apache.cxf.dosgi.common.osgi http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/pom.xml ---------------------------------------------------------------------- diff --git a/common/pom.xml b/common/pom.xml new file mode 100644 index 0000000..dd5ea46 --- /dev/null +++ b/common/pom.xml @@ -0,0 +1,78 @@ +<?xml version='1.0' encoding='UTF-8' ?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<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/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + <artifactId>cxf-dosgi-ri-common</artifactId> + <packaging>bundle</packaging> + <name>CXF dOSGi Common</name> + + <parent> + <groupId>org.apache.cxf.dosgi</groupId> + <artifactId>cxf-dosgi-ri-parent</artifactId> + <version>1.9-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> + </parent> + + <properties> + <topDirectoryLocation>..</topDirectoryLocation> + </properties> + + <dependencies> + + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-core</artifactId> + <version>${cxf.version}</version> + </dependency> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-frontend-jaxws</artifactId> + <version>${cxf.version}</version> + </dependency> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-frontend-jaxrs</artifactId> + <version>${cxf.version}</version> + </dependency> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-rs-client</artifactId> + <version>${cxf.version}</version> + </dependency> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-databinding-aegis</artifactId> + <version>${cxf.version}</version> + </dependency> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-rs-extension-providers</artifactId> + <version>${cxf.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.geronimo.specs</groupId> + <artifactId>geronimo-servlet_${servlet.version}_spec</artifactId> + <version>1.0</version> + </dependency> + </dependencies> + +</project> http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/main/java/org/apache/cxf/dosgi/common/httpservice/HttpServiceManager.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/cxf/dosgi/common/httpservice/HttpServiceManager.java b/common/src/main/java/org/apache/cxf/dosgi/common/httpservice/HttpServiceManager.java new file mode 100644 index 0000000..b129514 --- /dev/null +++ b/common/src/main/java/org/apache/cxf/dosgi/common/httpservice/HttpServiceManager.java @@ -0,0 +1,179 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.httpservice; + +import java.util.Collections; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +import org.apache.cxf.Bus; +import org.apache.cxf.transport.http.DestinationRegistry; +import org.apache.cxf.transport.http.DestinationRegistryImpl; +import org.apache.cxf.transport.servlet.CXFNonSpringServlet; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Filter; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceEvent; +import org.osgi.framework.ServiceException; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.http.HttpContext; +import org.osgi.service.http.HttpService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component( + configurationPid = "cxf-dsw", + service = HttpServiceManager.class + ) +public class HttpServiceManager { + /** + * Prefix to create an absolute URL from a relative URL. + * See HttpServiceManager.getAbsoluteAddress + * + * Defaults to: http://<host name>:8181 + */ + public static final String KEY_HTTP_BASE = "httpBase"; + public static final String KEY_CXF_SERVLET_ALIAS = "cxfServletAlias"; + public static final String DEFAULT_CXF_SERVLET_ALIAS = "/cxf"; + private static final Logger LOG = LoggerFactory.getLogger(HttpServiceManager.class); + + private Map<Long, String> exportedAliases = Collections.synchronizedMap(new HashMap<Long, String>()); + private String httpBase; + private String cxfServletAlias; + private HttpService httpService; + private BundleContext context; + + @SuppressWarnings("unchecked") + @Activate + public void activate(ComponentContext compContext) { + Dictionary<String, String> config = compContext.getProperties(); + initFromConfig(config); + this.context = compContext.getBundleContext(); + } + + void initFromConfig(Dictionary<String, String> config) { + if (config == null) { + config = new Hashtable<String, String>(); + } + this.httpBase = getWithDefault(config.get(KEY_HTTP_BASE), "http://" + LocalHostUtil.getLocalIp() + ":8181"); + this.cxfServletAlias = getWithDefault(config.get(KEY_CXF_SERVLET_ALIAS), "/cxf"); + } + + private String getWithDefault(String value, String defaultValue) { + return value == null ? defaultValue : value; + } + + public Bus registerServlet(Bus bus, String contextRoot, BundleContext callingContext, Long sid) { + bus.setExtension(new DestinationRegistryImpl(), DestinationRegistry.class); + CXFNonSpringServlet cxf = new CXFNonSpringServlet(); + cxf.setBus(bus); + try { + HttpContext httpContext1 = httpService.createDefaultHttpContext(); + HttpContext httpContext = new SecurityDelegatingHttpContext(callingContext, httpContext1); + httpService.registerServlet(contextRoot, cxf, new Hashtable<String, String>(), + httpContext); + + registerUnexportHook(sid, contextRoot); + + LOG.info("Successfully registered CXF DOSGi servlet at " + contextRoot); + } catch (Exception e) { + throw new ServiceException("CXF DOSGi: problem registering CXF HTTP Servlet", e); + } + return bus; + } + + /** + * This listens for service removal events and "un-exports" the service + * from the HttpService. + * + * @param sref the service reference to track + * @param alias the HTTP servlet context alias + */ + private void registerUnexportHook(Long sid, String alias) { + LOG.debug("Registering service listener for service with ID {}", sid); + + String previous = exportedAliases.put(sid, alias); + if (previous != null) { + LOG.warn("Overwriting service export for service with ID {}", sid); + } + + try { + Filter f = context.createFilter("(" + org.osgi.framework.Constants.SERVICE_ID + "=" + sid + ")"); + if (f != null) { + context.addServiceListener(new UnregisterListener(), f.toString()); + } else { + LOG.warn("Service listener could not be started. The service will not be automatically unexported."); + } + } catch (InvalidSyntaxException e) { + LOG.warn("Service listener could not be started. The service will not be automatically unexported.", e); + } + } + + public String getDefaultAddress(Class<?> type) { + return "/" + type.getName().replace('.', '/'); + } + + public String getAbsoluteAddress(String contextRoot, String relativeEndpointAddress) { + if (relativeEndpointAddress.startsWith("http")) { + return relativeEndpointAddress; + } + String effContextRoot = contextRoot == null ? cxfServletAlias : contextRoot; + return this.httpBase + effContextRoot + relativeEndpointAddress; + } + + private final class UnregisterListener implements ServiceListener { + + public void serviceChanged(ServiceEvent event) { + if (!(event.getType() == ServiceEvent.UNREGISTERING)) { + return; + } + final ServiceReference<?> sref = event.getServiceReference(); + final Long sid = (Long) sref.getProperty(org.osgi.framework.Constants.SERVICE_ID); + final String alias = exportedAliases.remove(sid); + if (alias == null) { + LOG.error("Unable to unexport HTTP servlet for service class '{}'," + + " service-id {}: no servlet alias found", + sref.getProperty(org.osgi.framework.Constants.OBJECTCLASS), sid); + return; + } + LOG.debug("Unexporting HTTP servlet for alias '{}'", alias); + try { + httpService.unregister(alias); + } catch (Exception e) { + LOG.warn("An exception occurred while unregistering service for HTTP servlet alias '{}'", alias, e); + } + } + } + + public void setContext(BundleContext context) { + this.context = context; + } + + @Reference + public void setHttpService(HttpService httpService) { + this.httpService = httpService; + } +} http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/main/java/org/apache/cxf/dosgi/common/httpservice/LocalHostUtil.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/cxf/dosgi/common/httpservice/LocalHostUtil.java b/common/src/main/java/org/apache/cxf/dosgi/common/httpservice/LocalHostUtil.java new file mode 100644 index 0000000..0ac245d --- /dev/null +++ b/common/src/main/java/org/apache/cxf/dosgi/common/httpservice/LocalHostUtil.java @@ -0,0 +1,92 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.httpservice; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +/** + * Utility methods to get the local address even on a linux host. + */ +final class LocalHostUtil { + + private LocalHostUtil() { + // Util Class + } + + /** + * Returns an InetAddress representing the address of the localhost. Every + * attempt is made to find an address for this host that is not the loopback + * address. If no other address can be found, the loopback will be returned. + * + * @return InetAddress the address of localhost + * @throws UnknownHostException if there is a problem determining the address + */ + public static InetAddress getLocalHost() throws UnknownHostException { + InetAddress localHost = InetAddress.getLocalHost(); + if (!localHost.isLoopbackAddress()) { + return localHost; + } + InetAddress[] addrs = getAllLocalUsingNetworkInterface(); + for (InetAddress addr : addrs) { + if (!addr.isLoopbackAddress() && !addr.getHostAddress().contains(":")) { + return addr; + } + } + return localHost; + } + + /** + * Utility method that delegates to the methods of NetworkInterface to + * determine addresses for this machine. + * + * @return all addresses found from the NetworkInterfaces + * @throws UnknownHostException if there is a problem determining addresses + */ + private static InetAddress[] getAllLocalUsingNetworkInterface() throws UnknownHostException { + try { + List<InetAddress> addresses = new ArrayList<InetAddress>(); + Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); + while (e.hasMoreElements()) { + NetworkInterface ni = e.nextElement(); + for (Enumeration<InetAddress> e2 = ni.getInetAddresses(); e2.hasMoreElements();) { + addresses.add(e2.nextElement()); + } + } + return addresses.toArray(new InetAddress[] {}); + } catch (SocketException ex) { + throw new UnknownHostException("127.0.0.1"); + } + } + + public static String getLocalIp() { + String localIP; + try { + localIP = getLocalHost().getHostAddress(); + } catch (Exception e) { + localIP = "localhost"; + } + return localIP; + } +} http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/main/java/org/apache/cxf/dosgi/common/httpservice/SecurityDelegatingHttpContext.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/cxf/dosgi/common/httpservice/SecurityDelegatingHttpContext.java b/common/src/main/java/org/apache/cxf/dosgi/common/httpservice/SecurityDelegatingHttpContext.java new file mode 100644 index 0000000..4eba758 --- /dev/null +++ b/common/src/main/java/org/apache/cxf/dosgi/common/httpservice/SecurityDelegatingHttpContext.java @@ -0,0 +1,133 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.httpservice; + +import java.io.IOException; +import java.net.URL; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.service.http.HttpContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * <p> + * An HttpContext that delegates to another HttpContext for all things other than security. This implementation handles + * security by delegating to a {@link FilterChain} based on the set of {@link Filter}s registered with a + * {@link #FILTER_PROP} property. + * </p> + * <p> + * If the {@link BundleContext} contains a {@link #FILTER_REQUIRED_PROP} property with value "true", requests will not + * be allowed until at least one {@link Filter} with a {@link #FILTER_PROP} property is registered. + * </p> + */ +class SecurityDelegatingHttpContext implements HttpContext { + + public static final String FILTER_PROP = "org.apache.cxf.httpservice.filter"; + public static final String FILTER_REQUIRED_PROP = "org.apache.cxf.httpservice.requirefilter"; + private static final Logger LOG = LoggerFactory.getLogger(SecurityDelegatingHttpContext.class); + private static final String FILTER_FILTER = "(" + FILTER_PROP + "=*)"; + + BundleContext bundleContext; + HttpContext delegate; + boolean requireFilter; + + SecurityDelegatingHttpContext(BundleContext bundleContext, HttpContext delegate) { + this.bundleContext = bundleContext; + this.delegate = delegate; + requireFilter = Boolean.TRUE.toString().equalsIgnoreCase(bundleContext.getProperty(FILTER_REQUIRED_PROP)); + } + + public String getMimeType(String name) { + return delegate.getMimeType(name); + } + + public URL getResource(String name) { + return delegate.getResource(name); + } + + @SuppressWarnings({ + "unchecked", "rawtypes" + }) + public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException { + ServiceReference[] refs; + try { + refs = bundleContext.getServiceReferences(Filter.class.getName(), FILTER_FILTER); + } catch (InvalidSyntaxException e) { + LOG.warn(e.getMessage(), e); + return false; + } + if (refs == null || refs.length == 0) { + LOG.info("No filter registered."); + return !requireFilter; + } + Filter[] filters = new Filter[refs.length]; + try { + for (int i = 0; i < refs.length; i++) { + filters[i] = (Filter)bundleContext.getService(refs[i]); + } + try { + new Chain(filters).doFilter(request, response); + return !response.isCommitted(); + } catch (ServletException e) { + LOG.warn(e.getMessage(), e); + return false; + } + } finally { + for (int i = 0; i < refs.length; i++) { + if (filters[i] != null) { + bundleContext.ungetService(refs[i]); + } + } + } + } +} + +/** + * A {@link FilterChain} composed of {@link Filter}s with the + */ +class Chain implements FilterChain { + + private static final Logger LOG = LoggerFactory.getLogger(Chain.class); + + int current; + final Filter[] filters; + + Chain(Filter[] filters) { + this.filters = filters; + } + + public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { + if (current < filters.length && !response.isCommitted()) { + Filter filter = filters[current++]; + LOG.info("doFilter() on {}", filter); + filter.doFilter(request, response, this); + } + } +} http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/main/java/org/apache/cxf/dosgi/common/intent/DefaultIntentMapFactory.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/cxf/dosgi/common/intent/DefaultIntentMapFactory.java b/common/src/main/java/org/apache/cxf/dosgi/common/intent/DefaultIntentMapFactory.java new file mode 100644 index 0000000..395fe46 --- /dev/null +++ b/common/src/main/java/org/apache/cxf/dosgi/common/intent/DefaultIntentMapFactory.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.intent; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.cxf.binding.soap.Soap11; +import org.apache.cxf.binding.soap.Soap12; +import org.apache.cxf.binding.soap.SoapBindingConfiguration; +import org.apache.cxf.binding.soap.SoapVersion; +import org.apache.cxf.feature.LoggingFeature; + +public class DefaultIntentMapFactory { + + public Map<String, Object> create() { + Map<String, Object> intentMap = new HashMap<String, Object>(); + intentMap.put("logging", getLoggingFeature()); + Object soap11 = getSoapBinding(Soap11.getInstance()); + intentMap.put("SOAP", soap11); + intentMap.put("SOAP.1_1", soap11); + intentMap.put("SOAP.1_2", getSoapBinding(Soap12.getInstance())); + intentMap.put("HTTP", "PROVIDED"); + return intentMap; + } + + private Object getLoggingFeature() { + return new LoggingFeature(); + } + + private Object getSoapBinding(SoapVersion soapVersion) { + SoapBindingConfiguration soapBindingConfig = new SoapBindingConfiguration(); + soapBindingConfig.setVersion(soapVersion); + return soapBindingConfig; + } +} http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentManager.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentManager.java b/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentManager.java new file mode 100644 index 0000000..32eedab --- /dev/null +++ b/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentManager.java @@ -0,0 +1,35 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.intent; + +import java.util.List; +import java.util.Map; + +import org.apache.cxf.endpoint.AbstractEndpointFactory; +import org.apache.cxf.feature.Feature; + +public interface IntentManager { + @Deprecated + String EXPORTED_INTENTS_OLD = "osgi.remote.requires.intents"; + + String INTENT_NAME_PROP = "org.apache.cxf.dosgi.IntentName"; + + String[] applyIntents(List<Feature> features, AbstractEndpointFactory factory, Map<String, Object> props); + void assertAllIntentsSupported(Map<String, Object> serviceProperties); +} http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentManagerImpl.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentManagerImpl.java b/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentManagerImpl.java new file mode 100644 index 0000000..2775086 --- /dev/null +++ b/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentManagerImpl.java @@ -0,0 +1,214 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.intent; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.aries.rsa.spi.IntentUnsatisfiedException; +import org.apache.cxf.binding.BindingConfiguration; +import org.apache.cxf.dosgi.common.util.OsgiUtils; +import org.apache.cxf.endpoint.AbstractEndpointFactory; +import org.apache.cxf.feature.Feature; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.remoteserviceadmin.RemoteConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(service = IntentManager.class) +public class IntentManagerImpl implements IntentManager { + + static final Logger LOG = LoggerFactory.getLogger(IntentManagerImpl.class); + private static final String PROVIDED_INTENT_VALUE = "PROVIDED"; + private static final int DEFAULT_INTENT_TIMEOUT = 30000; + + private final IntentMap intentMap; + private final long maxIntentWaitTime; + private IntentTracker tracker; + + public IntentManagerImpl() { + this(DEFAULT_INTENT_TIMEOUT); + } + + public IntentManagerImpl(int maxIntentWaitTime) { + this(new IntentMap(new DefaultIntentMapFactory().create()), maxIntentWaitTime); + } + + public IntentManagerImpl(IntentMap intentMap) { + this(intentMap, DEFAULT_INTENT_TIMEOUT); + } + + // For test + public IntentManagerImpl(IntentMap intentMap, int maxIntentWaitTime) { + this.maxIntentWaitTime = maxIntentWaitTime; + this.intentMap = intentMap; + } + + + @Activate + public void activate(BundleContext context) { + tracker = new IntentTracker(context, this.intentMap); + tracker.open(); + } + + @Deactivate + public void deactivate() { + tracker.close(); + } + + public String[] applyIntents(List<Feature> features, AbstractEndpointFactory factory, + Map<String, Object> props) throws IntentUnsatisfiedException { + Set<String> requestedIntents = IntentManagerImpl.getRequestedIntents(props); + Set<String> appliedIntents = new HashSet<String>(); + appliedIntents.addAll(reverseLookup(intentMap, PROVIDED_INTENT_VALUE)); + boolean bindingApplied = false; + for (String intentName : requestedIntents) { + bindingApplied |= processIntent(features, factory, intentName, intentMap.get(intentName)); + appliedIntents.add(intentName); + } + if (!bindingApplied) { + String defaultBindingName = "SOAP"; + processIntent(features, factory, defaultBindingName, intentMap.get(defaultBindingName)); + appliedIntents.add(defaultBindingName); + } + appliedIntents.addAll(addSynonymIntents(appliedIntents, intentMap)); + return appliedIntents.toArray(new String[appliedIntents.size()]); + } + + private static Set<String> getRequestedIntents(Map<String, Object> sd) { + Collection<String> intents = OsgiUtils.getMultiValueProperty(sd.get(RemoteConstants.SERVICE_EXPORTED_INTENTS)); + Collection<String> intents2 + = OsgiUtils.getMultiValueProperty(sd.get(RemoteConstants.SERVICE_EXPORTED_INTENTS_EXTRA)); + @SuppressWarnings("deprecation") + Collection<String> oldIntents = OsgiUtils.getMultiValueProperty(sd.get(IntentManager.EXPORTED_INTENTS_OLD)); + Set<String> allIntents = new HashSet<String>(); + if (intents != null) { + allIntents.addAll(parseIntents(intents)); + } + if (intents2 != null) { + allIntents.addAll(parseIntents(intents2)); + } + if (oldIntents != null) { + allIntents.addAll(parseIntents(oldIntents)); + } + + return allIntents; + } + + private static Collection<String> parseIntents(Collection<String> intents) { + List<String> parsed = new ArrayList<String>(); + for (String intent : intents) { + parsed.addAll(Arrays.asList(intent.split("[ ]"))); + } + return parsed; + } + + private boolean processIntent(List<Feature> features, AbstractEndpointFactory factory, + String intentName, Object intent) throws IntentUnsatisfiedException { + if (intent instanceof String) { + if (PROVIDED_INTENT_VALUE.equalsIgnoreCase((String) intent)) { + return false; + } + } else if (intent instanceof BindingConfiguration) { + BindingConfiguration bindingCfg = (BindingConfiguration)intent; + LOG.info("Applying intent: " + intentName + " via binding config: " + bindingCfg); + factory.setBindingConfig(bindingCfg); + return true; + } else if (intent instanceof Feature) { + Feature feature = (Feature) intent; + LOG.info("Applying intent: " + intentName + " via feature: " + feature); + features.add(feature); + return false; + } else { + LOG.info("No mapping for intent: " + intentName); + throw new IntentUnsatisfiedException(intentName); + } + return false; + } + + private static Collection<String> addSynonymIntents(Collection<String> appliedIntents, + IntentMap map) { + // E.g. SOAP and SOAP.1_1 are synonyms + List<Object> values = new ArrayList<Object>(); + for (String key : appliedIntents) { + values.add(map.get(key)); + } + return reverseLookup(map, values); + } + + private static Collection<String> reverseLookup(IntentMap im, Object obj) { + return reverseLookup(im, Collections.singleton(obj)); + } + + /** + * Retrieves all keys whose mapped values are found in the given collection. + * + * @param im an intent map + * @param values a collection of potential values + * @return all keys whose mapped values are found in the given collection + */ + private static Collection<String> reverseLookup(IntentMap im, Collection<?> values) { + Set<String> intentsFound = new HashSet<String>(); + for (Map.Entry<String, Object> entry : im.entrySet()) { + if (values.contains(entry.getValue())) { + intentsFound.add(entry.getKey()); + } + } + return intentsFound; + } + + public void assertAllIntentsSupported(Map<String, Object> serviceProperties) { + long endTime = System.currentTimeMillis() + maxIntentWaitTime; + Set<String> requiredIntents = IntentManagerImpl.getRequestedIntents(serviceProperties); + List<String> unsupportedIntents = new ArrayList<String>(); + do { + unsupportedIntents.clear(); + for (String ri : requiredIntents) { + if (!intentMap.containsKey(ri)) { + unsupportedIntents.add(ri); + } + } + long remainingSeconds = (endTime - System.currentTimeMillis()) / 1000; + if (!unsupportedIntents.isEmpty() && remainingSeconds > 0) { + LOG.debug("Waiting for custom intents " + unsupportedIntents + " timeout in " + remainingSeconds); + try { + synchronized (intentMap) { + intentMap.wait(1000); + } + } catch (InterruptedException e) { + LOG.warn(e.getMessage(), e); + } + } + } while (!unsupportedIntents.isEmpty() && System.currentTimeMillis() < endTime); + + if (!unsupportedIntents.isEmpty()) { + throw new RuntimeException("service cannot be exported because the following " + + "intents are not supported by this RSA: " + unsupportedIntents); + } + } +} http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentMap.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentMap.java b/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentMap.java new file mode 100644 index 0000000..9539c95 --- /dev/null +++ b/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentMap.java @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.intent; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Maps intent names to intent objects + * An intent object can be a Feature, a BindingConfiguration or a String + * + * Also supports a default intent map. Custom intents can override the defaults + */ +public class IntentMap extends ConcurrentHashMap<String, Object> { + + private static final long serialVersionUID = 2606460607920520767L; + private Map<String, Object> defaultMap; + + public IntentMap() { + this(new HashMap<String, Object>()); + } + + public IntentMap(Map<String, Object> defaultMap) { + this.defaultMap = defaultMap; + putAll(defaultMap); + } + + @Override + public Object put(String key, Object value) { + Object result = super.put(key, value); + synchronized (this) { + notifyAll(); + } + return result; + } + + @Override + public Object remove(Object key) { + Object old = super.remove(key); + if (defaultMap.containsKey(key)) { + put((String)key, defaultMap.get(key)); + } + return old; + } +} http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentTracker.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentTracker.java b/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentTracker.java new file mode 100644 index 0000000..678e9e5 --- /dev/null +++ b/common/src/main/java/org/apache/cxf/dosgi/common/intent/IntentTracker.java @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.intent; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.Filter; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class IntentTracker extends ServiceTracker { + + private final IntentMap intentMap; + + + public IntentTracker(BundleContext context, IntentMap intentMap) { + super(context, getFilter(context), null); + this.intentMap = intentMap; + } + + static Filter getFilter(BundleContext context) { + try { + return context.createFilter("(" + IntentManager.INTENT_NAME_PROP + "=*)"); + } catch (InvalidSyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + public Object addingService(ServiceReference reference) { + String intentName = (String) reference.getProperty(IntentManager.INTENT_NAME_PROP); + Object intent = super.addingService(reference); + IntentManagerImpl.LOG.info("Adding custom intent " + intentName); + intentMap.put(intentName, intent); + return intent; + } + + @Override + public void removedService(ServiceReference reference, Object service) { + String intentName = (String) reference.getProperty(IntentManager.INTENT_NAME_PROP); + intentMap.remove(intentName); + super.removedService(reference, service); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/main/java/org/apache/cxf/dosgi/common/util/ClassUtils.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/cxf/dosgi/common/util/ClassUtils.java b/common/src/main/java/org/apache/cxf/dosgi/common/util/ClassUtils.java new file mode 100644 index 0000000..9190b6c --- /dev/null +++ b/common/src/main/java/org/apache/cxf/dosgi/common/util/ClassUtils.java @@ -0,0 +1,86 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.cxf.helpers.CastUtils; +import org.osgi.framework.BundleContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class ClassUtils { + + private static final Logger LOG = LoggerFactory.getLogger(ClassUtils.class); + + private ClassUtils() { + } + + public static List<Object> loadProviderClasses(BundleContext callingContext, + Map<String, Object> sd, String propName) { + Object serviceProviders = sd.get(propName); + if (serviceProviders != null) { + if (serviceProviders.getClass().isArray()) { + if (serviceProviders.getClass().getComponentType() == String.class) { + return loadProviders(callingContext, (String[])serviceProviders); + } else { + return Arrays.asList((Object[])serviceProviders); + } + } else if (serviceProviders.getClass() == String.class) { + String[] classNames = serviceProviders.toString().split(","); + return loadProviders(callingContext, classNames); + } else if (serviceProviders instanceof List) { + List<Object> list = CastUtils.cast((List<?>)serviceProviders); + if (!list.isEmpty()) { + List<Object> providers; + if (list.get(0).getClass() == String.class) { + providers = loadProviders(callingContext, list.toArray(new String[]{})); + } else { + providers = list; + } + return providers; + } + } else { + return Arrays.asList(serviceProviders); + } + } + return Collections.emptyList(); + + } + + private static List<Object> loadProviders(BundleContext callingContext, String[] classNames) { + List<Object> providers = new ArrayList<Object>(); + for (String className : classNames) { + try { + String realName = className.trim(); + if (!realName.isEmpty()) { + Class<?> pClass = callingContext.getBundle().loadClass(realName); + providers.add(pClass.newInstance()); + } + } catch (Exception ex) { + LOG.warn("Provider " + className.trim() + " can not be loaded or created " + ex.getMessage(), ex); + } + } + return providers; + } +} http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/main/java/org/apache/cxf/dosgi/common/util/OsgiUtils.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/cxf/dosgi/common/util/OsgiUtils.java b/common/src/main/java/org/apache/cxf/dosgi/common/util/OsgiUtils.java new file mode 100644 index 0000000..aa24903 --- /dev/null +++ b/common/src/main/java/org/apache/cxf/dosgi/common/util/OsgiUtils.java @@ -0,0 +1,78 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.util; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class OsgiUtils { + + public static final Logger LOG = LoggerFactory.getLogger(OsgiUtils.class); + + private OsgiUtils() { + } + + public static boolean getBooleanProperty(Map<String, Object> sd, String name) { + return toBoolean(sd.get(name)); + } + + public static boolean toBoolean(Object value) { + return value instanceof Boolean && (Boolean) value + || value instanceof String && Boolean.parseBoolean((String)value); + } + + @SuppressWarnings("unchecked") + public static Collection<String> getMultiValueProperty(Object property) { + if (property == null) { + return null; + } else if (property instanceof Collection) { + return (Collection<String>)property; + } else if (property instanceof String[]) { + return Arrays.asList((String[])property); + } else { + return Collections.singleton(property.toString()); + } + } + + public static String getProperty(EndpointDescription endpoint, String name) { + return getProperty(endpoint.getProperties(), name); + } + + public static String getProperty(Map<String, Object> dict, String name) { + Object value = dict.get(name); + return value instanceof String ? (String) value : null; + } + + public static String getFirstNonEmptyStringProperty(Map<String, Object> dict, String ... keys) { + for (String key : keys) { + String value = getProperty(dict, key); + if (value != null) { + return value; + } + } + return null; + } + +} http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/main/java/org/apache/cxf/dosgi/common/util/ServerWrapper.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/cxf/dosgi/common/util/ServerWrapper.java b/common/src/main/java/org/apache/cxf/dosgi/common/util/ServerWrapper.java new file mode 100644 index 0000000..22144ca --- /dev/null +++ b/common/src/main/java/org/apache/cxf/dosgi/common/util/ServerWrapper.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.util; + +import java.io.IOException; + +import org.apache.aries.rsa.spi.Endpoint; +import org.apache.cxf.endpoint.Server; +import org.osgi.service.remoteserviceadmin.EndpointDescription; + +public class ServerWrapper implements Endpoint { + private EndpointDescription desc; + private Server server; + + public ServerWrapper(EndpointDescription desc, Server server) { + this.desc = desc; + this.server = server; + } + + public Server getServer() { + return this.server; + } + + @Override + public void close() throws IOException { + this.server.destroy(); + } + + @Override + public EndpointDescription description() { + return this.desc; + } +} http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/main/java/org/apache/cxf/dosgi/common/util/StringPlus.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/cxf/dosgi/common/util/StringPlus.java b/common/src/main/java/org/apache/cxf/dosgi/common/util/StringPlus.java new file mode 100644 index 0000000..c7123b2 --- /dev/null +++ b/common/src/main/java/org/apache/cxf/dosgi/common/util/StringPlus.java @@ -0,0 +1,72 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class StringPlus { + + private static final Logger LOG = LoggerFactory.getLogger(StringPlus.class); + + private StringPlus() { + // never constructed + } + + @SuppressWarnings("rawtypes") + public static String[] normalize(Object object) { + if (object instanceof String) { + String s = (String)object; + String[] values = s.split(","); + List<String> list = new ArrayList<String>(); + for (String val : values) { + String actualValue = val.trim(); + if (!actualValue.isEmpty()) { + list.add(actualValue); + } + } + return list.toArray(new String[list.size()]); + } + + if (object instanceof String[]) { + return (String[])object; + } + + if (object instanceof Collection) { + Collection col = (Collection)object; + List<String> ar = new ArrayList<String>(col.size()); + for (Object o : col) { + if (o instanceof String) { + String s = (String)o; + ar.add(s); + } else { + LOG.warn("stringPlus contained non string element in list! Element was skipped"); + } + } + return ar.toArray(new String[ar.size()]); + } + + return null; + } + +} http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/test/java/org/apache/cxf/dosgi/common/httpservice/HttpServiceManagerTest.java ---------------------------------------------------------------------- diff --git a/common/src/test/java/org/apache/cxf/dosgi/common/httpservice/HttpServiceManagerTest.java b/common/src/test/java/org/apache/cxf/dosgi/common/httpservice/HttpServiceManagerTest.java new file mode 100644 index 0000000..c943f79 --- /dev/null +++ b/common/src/test/java/org/apache/cxf/dosgi/common/httpservice/HttpServiceManagerTest.java @@ -0,0 +1,118 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.httpservice; + +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; + +import java.util.Dictionary; + +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.apache.cxf.Bus; +import org.apache.cxf.BusFactory; +import org.easymock.Capture; +import org.easymock.EasyMock; +import org.easymock.IMocksControl; +import org.junit.Assert; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Filter; +import org.osgi.framework.ServiceEvent; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.ServiceReference; +import org.osgi.service.http.HttpContext; +import org.osgi.service.http.HttpService; +import org.osgi.service.http.NamespaceException; + +import junit.framework.TestCase; + +public class HttpServiceManagerTest extends TestCase { + + public void testGetAbsoluteAddress() { + HttpServiceManager manager = new HttpServiceManager(); + manager.initFromConfig(null); + String localIp = LocalHostUtil.getLocalIp(); + + String address1 = manager.getAbsoluteAddress(null, "/myservice"); + assertEquals("http://" + localIp + ":8181/cxf/myservice", address1); + + String address2 = manager.getAbsoluteAddress("/mycontext", "/myservice"); + assertEquals("http://" + localIp + ":8181/mycontext/myservice", address2); + } + + public void testRegisterAndUnregisterServlet() throws Exception { + IMocksControl c = EasyMock.createControl(); + BundleContext dswContext = c.createMock(BundleContext.class); + Filter filter = c.createMock(Filter.class); + expect(dswContext.createFilter(EasyMock.eq("(service.id=12345)"))).andReturn(filter).once(); + Capture<ServiceListener> captured = EasyMock.newCapture(); + dswContext.addServiceListener(EasyMock.capture(captured), EasyMock.<String>anyObject()); + expectLastCall().atLeastOnce(); + expect(dswContext.getProperty("org.apache.cxf.httpservice.requirefilter")).andReturn(null).atLeastOnce(); + ServletConfig config = c.createMock(ServletConfig.class); + expect(config.getInitParameter(EasyMock.<String>anyObject())).andReturn(null).atLeastOnce(); + ServletContext servletContext = c.createMock(ServletContext.class); + expect(config.getServletContext()).andReturn(servletContext); + final HttpService httpService = new DummyHttpService(config); + ServiceReference<?> sr = c.createMock(ServiceReference.class); + expect(sr.getProperty(EasyMock.eq("service.id"))).andReturn(12345L).atLeastOnce(); + expect(servletContext.getResourceAsStream((String)EasyMock.anyObject())).andReturn(null).anyTimes(); + c.replay(); + + HttpServiceManager h = new HttpServiceManager(); + h.setContext(dswContext); + h.setHttpService(httpService); + Bus bus = BusFactory.newInstance().createBus(); + h.registerServlet(bus, "/myService", dswContext, 12345L); + + ServiceEvent event = new ServiceEvent(ServiceEvent.UNREGISTERING, sr); + captured.getValue().serviceChanged(event); + c.verify(); + } + + static class DummyHttpService implements HttpService { + + private ServletConfig config; + + DummyHttpService(ServletConfig config) { + this.config = config; + } + + @SuppressWarnings("rawtypes") + public void registerServlet(String alias, Servlet servlet, Dictionary initparams, HttpContext context) + throws ServletException, NamespaceException { + Assert.assertEquals("/myService", alias); + servlet.init(config); + } + + public void registerResources(String alias, String name, HttpContext context) throws NamespaceException { + throw new RuntimeException("This method should not be called"); + } + + public void unregister(String alias) { + } + + public HttpContext createDefaultHttpContext() { + return EasyMock.createNiceMock(HttpContext.class); + } + } +} http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/test/java/org/apache/cxf/dosgi/common/httpservice/SecurityDelegatingHttpContextTest.java ---------------------------------------------------------------------- diff --git a/common/src/test/java/org/apache/cxf/dosgi/common/httpservice/SecurityDelegatingHttpContextTest.java b/common/src/test/java/org/apache/cxf/dosgi/common/httpservice/SecurityDelegatingHttpContextTest.java new file mode 100644 index 0000000..aac1204 --- /dev/null +++ b/common/src/test/java/org/apache/cxf/dosgi/common/httpservice/SecurityDelegatingHttpContextTest.java @@ -0,0 +1,267 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.httpservice; + +import java.io.PrintWriter; +import java.net.URL; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.easymock.EasyMock; +import org.junit.Assert; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.http.HttpContext; + +import junit.framework.TestCase; + +@SuppressWarnings({ + "unchecked", "rawtypes" + }) +public class SecurityDelegatingHttpContextTest extends TestCase { + + protected HttpContext defaultHttpContext; + protected SecurityDelegatingHttpContext httpContext; + protected CommitResponseFilter commitFilter; + protected DoNothingFilter doNothingFilter; + protected AccessDeniedFilter accessDeniedFilter; + protected String mimeType; + protected URL url; // does not need to exist + + public void setUp() throws Exception { + mimeType = "text/xml"; + url = new URL("file:test.xml"); // does not need to exist + + // Sample filters + commitFilter = new CommitResponseFilter(); + doNothingFilter = new DoNothingFilter(); + accessDeniedFilter = new AccessDeniedFilter(); + + // Mock up the default http context + defaultHttpContext = EasyMock.createNiceMock(HttpContext.class); + EasyMock.expect(defaultHttpContext.getMimeType((String)EasyMock.anyObject())).andReturn(mimeType); + EasyMock.expect(defaultHttpContext.getResource((String)EasyMock.anyObject())).andReturn(url); + EasyMock.replay(defaultHttpContext); + } + + public void testFilterRequired() throws Exception { + // Mock up the service references + ServiceReference[] serviceReferences = new ServiceReference[] {}; + + // Mock up the bundle context + BundleContext bundleContext = EasyMock.createNiceMock(BundleContext.class); + EasyMock.expect(bundleContext.getServiceReferences(Filter.class.getName(), + "(org.apache.cxf.httpservice.filter=true)")) + .andReturn(serviceReferences); + EasyMock.replay(bundleContext); + + // Set up the secure http context + httpContext = new SecurityDelegatingHttpContext(bundleContext, defaultHttpContext); + httpContext.requireFilter = true; + + // Ensure that the httpContext doesn't allow the request to be processed, since there are no registered servlet + // filters + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + EasyMock.replay(request); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.replay(response); + boolean requestAllowed = httpContext.handleSecurity(request, response); + Assert.assertFalse(requestAllowed); + + // Ensure that the httpContext returns true if there is no requirement for registered servlet filters + httpContext.requireFilter = false; + requestAllowed = httpContext.handleSecurity(request, response); + Assert.assertTrue(requestAllowed); + } + + public void testSingleCommitFilter() throws Exception { + // Mock up the service references + ServiceReference filterReference = EasyMock.createNiceMock(ServiceReference.class); + EasyMock.replay(filterReference); + ServiceReference[] serviceReferences = new ServiceReference[] { + filterReference + }; + + // Mock up the bundle context + BundleContext bundleContext = EasyMock.createNiceMock(BundleContext.class); + EasyMock.expect(bundleContext.getServiceReferences((String)EasyMock.anyObject(), (String)EasyMock.anyObject())) + .andReturn(serviceReferences); + EasyMock.expect(bundleContext.getService((ServiceReference)EasyMock.anyObject())).andReturn(commitFilter); + EasyMock.replay(bundleContext); + + // Set up the secure http context + httpContext = new SecurityDelegatingHttpContext(bundleContext, defaultHttpContext); + + // Ensure that the httpContext returns false, since the filter has committed the response + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + EasyMock.replay(request); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.isCommitted()).andReturn(false); // the first call checks to see whether to invoke the + // filter + EasyMock.expect(response.isCommitted()).andReturn(true); // the second is called to determine the handleSecurity + // return value + EasyMock.expect(response.getWriter()).andReturn(new PrintWriter(System.out)); + EasyMock.replay(response); + Assert.assertFalse(httpContext.handleSecurity(request, response)); + + // Ensure that the appropriate filters were called + Assert.assertTrue(commitFilter.called); + Assert.assertFalse(doNothingFilter.called); + Assert.assertFalse(accessDeniedFilter.called); + } + + public void testFilterChain() throws Exception { + // Mock up the service references + ServiceReference filterReference = EasyMock.createNiceMock(ServiceReference.class); + EasyMock.replay(filterReference); + ServiceReference[] serviceReferences = new ServiceReference[] { + filterReference, filterReference + }; + + // Mock up the bundle context + BundleContext bundleContext = EasyMock.createNiceMock(BundleContext.class); + EasyMock.expect(bundleContext.getServiceReferences((String)EasyMock.anyObject(), (String)EasyMock.anyObject())) + .andReturn(serviceReferences); + EasyMock.expect(bundleContext.getService((ServiceReference)EasyMock.anyObject())).andReturn(doNothingFilter); + EasyMock.expect(bundleContext.getService((ServiceReference)EasyMock.anyObject())).andReturn(commitFilter); + EasyMock.replay(bundleContext); + + // Set up the secure http context + httpContext = new SecurityDelegatingHttpContext(bundleContext, defaultHttpContext); + + // Ensure that the httpContext returns false, since the filter has committed the response + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + EasyMock.replay(request); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.isCommitted()).andReturn(false); // doNothingFilter should not commit the response + EasyMock.expect(response.getWriter()).andReturn(new PrintWriter(System.out)); + EasyMock.expect(response.isCommitted()).andReturn(false); + EasyMock.expect(response.isCommitted()).andReturn(true); // the commit filter indicating that it committed the + // response + EasyMock.replay(response); + Assert.assertFalse(httpContext.handleSecurity(request, response)); + + // Ensure that the appropriate filters were called + Assert.assertTrue(doNothingFilter.called); + Assert.assertTrue(commitFilter.called); + Assert.assertFalse(accessDeniedFilter.called); + } + + public void testAllowRequest() throws Exception { + // Mock up the service references + ServiceReference filterReference = EasyMock.createNiceMock(ServiceReference.class); + EasyMock.replay(filterReference); + ServiceReference[] serviceReferences = new ServiceReference[] { + filterReference + }; + + // Mock up the bundle context + BundleContext bundleContext = EasyMock.createNiceMock(BundleContext.class); + EasyMock.expect(bundleContext.getServiceReferences((String)EasyMock.anyObject(), (String)EasyMock.anyObject())) + .andReturn(serviceReferences); + EasyMock.expect(bundleContext.getService((ServiceReference)EasyMock.anyObject())).andReturn(doNothingFilter); + EasyMock.replay(bundleContext); + + // Set up the secure http context + httpContext = new SecurityDelegatingHttpContext(bundleContext, defaultHttpContext); + + // Ensure that the httpContext returns true, since the filter has not committed the response + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + EasyMock.replay(request); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.isCommitted()).andReturn(false); + EasyMock.replay(response); + Assert.assertTrue(httpContext.handleSecurity(request, response)); + + // Ensure that the appropriate filters were called + Assert.assertTrue(doNothingFilter.called); + Assert.assertFalse(commitFilter.called); + Assert.assertFalse(accessDeniedFilter.called); + } + + public void testDelegation() throws Exception { + BundleContext bundleContext = EasyMock.createNiceMock(BundleContext.class); + EasyMock.replay(bundleContext); + + // Set up the secure http context + httpContext = new SecurityDelegatingHttpContext(bundleContext, defaultHttpContext); + + // Ensure that it delegates non-security calls to the wrapped implementation (in this case, the mock) + Assert.assertEquals(mimeType, httpContext.getMimeType("")); + Assert.assertEquals(url, httpContext.getResource("")); + } +} + +class CommitResponseFilter implements Filter { + + boolean called; + + public void init(FilterConfig filterConfig) throws ServletException { + } + + public void destroy() { + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws java.io.IOException, javax.servlet.ServletException { + called = true; + response.getWriter().write("committing the response"); + } +} + +class DoNothingFilter implements Filter { + + boolean called; + + public void init(FilterConfig filterConfig) throws ServletException { + } + + public void destroy() { + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws java.io.IOException, javax.servlet.ServletException { + called = true; + chain.doFilter(request, response); + } +} + +class AccessDeniedFilter implements Filter { + + boolean called; + + public void init(FilterConfig filterConfig) throws ServletException { + } + + public void destroy() { + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws java.io.IOException, javax.servlet.ServletException { + called = true; + ((HttpServletResponse)response).sendError(HttpServletResponse.SC_FORBIDDEN); + } +} http://git-wip-us.apache.org/repos/asf/cxf-dosgi/blob/d2a3c75f/common/src/test/java/org/apache/cxf/dosgi/common/qos/IntentManagerImplTest.java ---------------------------------------------------------------------- diff --git a/common/src/test/java/org/apache/cxf/dosgi/common/qos/IntentManagerImplTest.java b/common/src/test/java/org/apache/cxf/dosgi/common/qos/IntentManagerImplTest.java new file mode 100644 index 0000000..63a2fef --- /dev/null +++ b/common/src/test/java/org/apache/cxf/dosgi/common/qos/IntentManagerImplTest.java @@ -0,0 +1,282 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.dosgi.common.qos; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.aries.rsa.spi.IntentUnsatisfiedException; +import org.apache.cxf.binding.BindingConfiguration; +import org.apache.cxf.dosgi.common.intent.DefaultIntentMapFactory; +import org.apache.cxf.dosgi.common.intent.IntentManager; +import org.apache.cxf.dosgi.common.intent.IntentManagerImpl; +import org.apache.cxf.dosgi.common.intent.IntentMap; +import org.apache.cxf.endpoint.AbstractEndpointFactory; +import org.apache.cxf.feature.AbstractFeature; +import org.apache.cxf.feature.Feature; +import org.easymock.EasyMock; +import org.easymock.IMocksControl; +import org.junit.Assert; +import org.junit.Test; + +public class IntentManagerImplTest extends Assert { + + @Test + public void testIntents() throws Exception { + Map<String, Object> intents = new HashMap<String, Object>(); + intents.put("A", new TestFeature("A")); + intents.put("SOAP", new TestFeature("SOAP")); + final IntentMap intentMap = new IntentMap(intents); + + IMocksControl control = EasyMock.createNiceControl(); + List<Feature> features = new ArrayList<Feature>(); + AbstractEndpointFactory factory = control.createMock(AbstractEndpointFactory.class); + control.replay(); + + IntentManager intentManager = new IntentManagerImpl(intentMap, 10000); + + Map<String, Object> props = new HashMap<String, Object>(); + props.put("osgi.remote.requires.intents", "A"); + + List<String> effectiveIntents = Arrays.asList(intentManager.applyIntents(features, factory, props)); + assertEquals(Arrays.asList("A", "SOAP"), effectiveIntents); + } + + @Test + public void testMultiIntents() { + final IntentMap intentMap = new IntentMap(new DefaultIntentMapFactory().create()); + intentMap.put("confidentiality.message", new TestFeature("confidentiality.message")); + intentMap.put("transactionality", new TestFeature("transactionality")); + + IMocksControl control = EasyMock.createNiceControl(); + List<Feature> features = new ArrayList<Feature>(); + AbstractEndpointFactory factory = control.createMock(AbstractEndpointFactory.class); + control.replay(); + + IntentManager intentManager = new IntentManagerImpl(intentMap); + + Map<String, Object> props = new HashMap<String, Object>(); + props.put("osgi.remote.requires.intents", "transactionality confidentiality.message"); + + List<String> effectiveIntents = Arrays.asList(intentManager.applyIntents(features, factory, props)); + assertTrue(effectiveIntents.contains("transactionality")); + assertTrue(effectiveIntents.contains("confidentiality.message")); + } + + @Test + public void testFailedIntent() { + Map<String, Object> intents = new HashMap<String, Object>(); + intents.put("A", new TestFeature("A")); + final IntentMap intentMap = new IntentMap(intents); + + IMocksControl control = EasyMock.createNiceControl(); + List<Feature> features = new ArrayList<Feature>(); + AbstractEndpointFactory factory = control.createMock(AbstractEndpointFactory.class); + control.replay(); + + IntentManager intentManager = new IntentManagerImpl(intentMap); + + Map<String, Object> props = new HashMap<String, Object>(); + props.put("osgi.remote.requires.intents", "A B"); + // ServiceEndpointDescription sd = + // new ServiceEndpointDescriptionImpl(Arrays.asList(String.class.getName()), props); + + try { + intentManager.applyIntents(features, factory, props); + Assert.fail("applyIntents() should have thrown an exception as there was an unsatisfiable intent"); + } catch (IntentUnsatisfiedException iue) { + assertEquals("B", iue.getIntent()); + } + } + + @Test + public void testInferIntents() { + Map<String, Object> intents = new HashMap<String, Object>(); + intents.put("SOAP", new TestFeature("SOAP")); + intents.put("Prov", "PROVIDED"); + AbstractFeature feat1 = new TestFeature("feat1"); + intents.put("A", feat1); + intents.put("A_alt", feat1); + intents.put("B", new TestFeature("B")); + final IntentMap intentMap = new IntentMap(intents); + + IMocksControl control = EasyMock.createNiceControl(); + List<Feature> features = new ArrayList<Feature>(); + AbstractEndpointFactory factory = control.createMock(AbstractEndpointFactory.class); + control.replay(); + + IntentManager intentManager = new IntentManagerImpl(intentMap); + + Map<String, Object> props = new HashMap<String, Object>(); + props.put("osgi.remote.requires.intents", "A"); + // ServiceEndpointDescription sd = + // new ServiceEndpointDescriptionImpl(Arrays.asList(String.class.getName()), props); + + List<String> effectiveIntents = Arrays.asList(intentManager.applyIntents(features, factory, props)); + assertEquals(4, effectiveIntents.size()); + assertTrue(effectiveIntents.contains("Prov")); + assertTrue(effectiveIntents.contains("A")); + assertTrue(effectiveIntents.contains("A_alt")); + } + + @Test + public void testDefaultBindingIntent() { + IMocksControl control = EasyMock.createNiceControl(); + + Map<String, Object> intents = new HashMap<String, Object>(); + BindingConfiguration feat1 = control.createMock(BindingConfiguration.class); + intents.put("A", new AbstractFeature() { + }); + intents.put("SOAP", feat1); + intents.put("SOAP.1_1", feat1); + intents.put("SOAP.1_2", control.createMock(BindingConfiguration.class)); + final IntentMap intentMap = new IntentMap(intents); + + List<Feature> features = new ArrayList<Feature>(); + AbstractEndpointFactory factory = control.createMock(AbstractEndpointFactory.class); + control.replay(); + IntentManager intentManager = new IntentManagerImpl(intentMap); + + Map<String, Object> props = new HashMap<String, Object>(); + props.put("osgi.remote.requires.intents", "A"); + // ServiceEndpointDescription sd = + // new ServiceEndpointDescriptionImpl(Arrays.asList(String.class.getName()), props); + + List<String> effectiveIntents = Arrays.asList(intentManager.applyIntents(features, factory, props)); + assertEquals(3, effectiveIntents.size()); + assertTrue(effectiveIntents.contains("A")); + assertTrue(effectiveIntents.contains("SOAP")); + assertTrue(effectiveIntents.contains("SOAP.1_1")); + } + + @Test + public void testExplicitBindingIntent() { + IMocksControl control = EasyMock.createNiceControl(); + + Map<String, Object> intents = new HashMap<String, Object>(); + BindingConfiguration feat1 = control.createMock(BindingConfiguration.class); + intents.put("A", new AbstractFeature() { + }); + intents.put("SOAP", feat1); + intents.put("SOAP.1_1", feat1); + intents.put("SOAP.1_2", control.createMock(BindingConfiguration.class)); + final IntentMap intentMap = new IntentMap(intents); + + List<Feature> features = new ArrayList<Feature>(); + AbstractEndpointFactory factory = control.createMock(AbstractEndpointFactory.class); + control.replay(); + + IntentManager intentManager = new IntentManagerImpl(intentMap); + + Map<String, Object> props = new HashMap<String, Object>(); + props.put("osgi.remote.requires.intents", "A SOAP.1_2"); + // ServiceEndpointDescription sd = + // new ServiceEndpointDescriptionImpl(Arrays.asList(String.class.getName()), props); + + List<String> effectiveIntents = Arrays.asList(intentManager.applyIntents(features, factory, props)); + assertEquals(2, effectiveIntents.size()); + assertTrue(effectiveIntents.contains("A")); + assertTrue(effectiveIntents.contains("SOAP.1_2")); + } + + public void testInheritMasterIntentMapDefault() { + List<String> features = runTestInheritMasterIntentMap("A B"); + + assertEquals(2, features.size()); + assertTrue(features.contains("appFeatureA")); + assertTrue(features.contains("masterFeatureB")); + } + + public void testInheritMasterIntentMap() { + List<String> features = runTestInheritMasterIntentMap("A B"); + + assertEquals(2, features.size()); + assertTrue(features.contains("appFeatureA")); + assertTrue(features.contains("masterFeatureB")); + } + + private List<String> runTestInheritMasterIntentMap(String requestedIntents) { + Map<String, Object> masterIntents = new HashMap<String, Object>(); + masterIntents.put("A", new TestFeature("masterFeatureA")); + masterIntents.put("B", new TestFeature("masterFeatureB")); + final IntentMap intentMap = new IntentMap(masterIntents); + intentMap.put("A", new TestFeature("appFeatureA")); + + IMocksControl control = EasyMock.createNiceControl(); + List<Feature> features = new ArrayList<Feature>(); + AbstractEndpointFactory factory = control.createMock(AbstractEndpointFactory.class); + control.replay(); + + Map<String, Object> props = new HashMap<String, Object>(); + props.put("osgi.remote.requires.intents", requestedIntents); + + IntentManagerImpl intentManager = new IntentManagerImpl(intentMap); + intentManager.applyIntents(features, factory, props); + + List<String> featureNames = new ArrayList<String>(); + for (Feature f : features) { + featureNames.add(f.toString()); + } + return featureNames; + } + + @Test + public void testProvidedIntents() { + Map<String, Object> masterIntents = new HashMap<String, Object>(); + masterIntents.put("SOAP", "SOAP"); + masterIntents.put("A", "Provided"); + masterIntents.put("B", "PROVIDED"); + final IntentMap intentMap = new IntentMap(masterIntents); + + IMocksControl control = EasyMock.createNiceControl(); + List<Feature> features = new ArrayList<Feature>(); + AbstractEndpointFactory factory = control.createMock(AbstractEndpointFactory.class); + control.replay(); + + Map<String, Object> props = new HashMap<String, Object>(); + props.put("osgi.remote.requires.intents", "B A"); + + IntentManager intentManager = new IntentManagerImpl(intentMap); + + Set<String> effectiveIntents = new HashSet<String>(Arrays.asList(intentManager.applyIntents(features, + factory, + props))); + Set<String> expectedIntents = new HashSet<String>(Arrays.asList(new String[] {"A", "B", "SOAP"})); + assertEquals(expectedIntents, effectiveIntents); + } + + private static final class TestFeature extends AbstractFeature { + + private final String name; + + private TestFeature(String n) { + name = n; + } + + @Override + public String toString() { + return name; + } + } +}
