diff options
Diffstat (limited to 'sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache')
21 files changed, 3785 insertions, 0 deletions
diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/node/osgi/impl/NodeActivator.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/node/osgi/impl/NodeActivator.java new file mode 100644 index 0000000000..36feb6526a --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/node/osgi/impl/NodeActivator.java @@ -0,0 +1,129 @@ +/* + * 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.tuscany.sca.node.osgi.impl; + +import static org.apache.tuscany.sca.node.osgi.impl.NodeManager.isSCABundle; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.RemoteServiceAdminImpl; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.TopologyManagerImpl; +import org.apache.tuscany.sca.osgi.service.discovery.impl.DiscoveryActivator; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.SynchronousBundleListener; + +/** + * Bundle activator to receive the BundleContext + */ +public class NodeActivator implements BundleActivator, SynchronousBundleListener { + private final static Logger logger = Logger.getLogger(NodeActivator.class.getName()); + private static BundleContext bundleContext; + private boolean inited; + private NodeManager manager; + + private DiscoveryActivator discoveryActivator = new DiscoveryActivator(); + private RemoteServiceAdminImpl remoteAdmin; + private TopologyManagerImpl controller; + + private void init() { + synchronized (this) { + if (inited) { + return; + } + manager = new NodeManager(bundleContext); + manager.start(); + bundleContext.addBundleListener(manager); + inited = true; + } + } + + public void start(BundleContext context) throws Exception { + try { + bundleContext = context; + + // FIXME: We should try to avoid aggressive initialization + init(); + + remoteAdmin = new RemoteServiceAdminImpl(context); + remoteAdmin.start(); + + discoveryActivator.start(context); + + controller = new TopologyManagerImpl(context); + controller.start(); + + boolean found = false; + for (Bundle b : context.getBundles()) { + if (isSCABundle(b)) { + found = true; + break; + } + } + + if (found) { + init(); + } else { + context.addBundleListener(this); + } + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + throw e; + } + } + + public void stop(BundleContext context) throws Exception { + if (inited) { + context.removeBundleListener(this); + controller.stop(); + controller = null; + + discoveryActivator.stop(context); + discoveryActivator = null; + + remoteAdmin.stop(); + remoteAdmin = null; + + manager.stop(); + bundleContext.removeBundleListener(manager); + manager = null; + bundleContext = null; + inited = false; + } + } + + public static BundleContext getBundleContext() { + return bundleContext; + } + + public void bundleChanged(BundleEvent event) { + if (event.getType() == BundleEvent.STARTING) { + if (isSCABundle(event.getBundle())) { + bundleContext.removeBundleListener(this); + init(); + } + } + + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/node/osgi/impl/NodeManager.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/node/osgi/impl/NodeManager.java new file mode 100644 index 0000000000..6914e09b8e --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/node/osgi/impl/NodeManager.java @@ -0,0 +1,143 @@ +/* + * 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.tuscany.sca.node.osgi.impl; + +import java.net.URL; +import java.util.Dictionary; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.tuscany.sca.node.Node; +import org.apache.tuscany.sca.node.configuration.NodeConfiguration; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.ServiceEvent; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.SynchronousBundleListener; + +/** + * Managing the mapping between OSGi bundles and SCA implementation.osgi + */ +public class NodeManager implements SynchronousBundleListener, ServiceListener { + private static final Logger logger = Logger.getLogger(NodeManager.class.getName()); + private BundleContext bundleContext; + OSGiNodeFactoryImpl factory; + + public NodeManager(BundleContext bundleContext) { + super(); + this.bundleContext = bundleContext; + this.factory = new OSGiNodeFactoryImpl(this.bundleContext); + } + + public void start() { + for (Bundle b : bundleContext.getBundles()) { + if ((b.getState() & Bundle.ACTIVE) != 0) { + // Process the active bundles + bundleStarted(b); + } + } + } + + public void stop() { + if (factory != null) { + factory.destroy(); + } + } + + public static boolean isSCABundle(Bundle bundle) { + Dictionary<?, ?> headers = bundle.getHeaders(); + // OSGi RFC 119 SCA + if (headers.get("SCA-Composite") != null) { + return true; + } + + URL bundleComposite = bundle.getResource("OSGI-INF/sca/bundle.composite"); + if (bundleComposite != null) { + return true; + } + + /* FIXME: What if there is a META-INF/sca-contribution.xml? There are two cases: + * 1. The file contains deployable elements + * 2. The file doesn't contain deployable elements + */ + + /* + * FIXME: Do we want to use all of the .composite files under OSGI-INF/sca? + */ + + /* + Enumeration<?> entries = bundle.findEntries("OSGI-INF/sca", "bundle.composite", false); + if (entries != null && entries.hasMoreElements()) { + return true; + } + + // OSGi Declarative Services + if (headers.get("Service-Component") != null) { + return true; + } + + // OSGI RFC 124: BluePrint Service + if (headers.get("Bundle-Blueprint") != null) { + return true; + } + + entries = bundle.findEntries("OSGI-INF/blueprint", "*.xml", false); + if (entries != null && entries.hasMoreElements()) { + return true; + } + */ + return false; + } + + private void bundleStarted(Bundle bundle) { + if (!isSCABundle(bundle)) { + return; + } + try { + NodeConfiguration configuration = factory.getConfiguration(bundle, null); + Node node = factory.createNode(configuration); + node.start(); + } catch (Throwable e) { + logger.log(Level.SEVERE, e.getMessage(), e); + } + } + + private void bundleStopping(Bundle bundle) { + Node node = factory.getNodes().get(bundle); + if (node == null) { + return; + } + node.stop(); + } + + public void serviceChanged(ServiceEvent event) { + } + + public void bundleChanged(BundleEvent event) { + int type = event.getType(); + if (type == BundleEvent.STOPPING) { + bundleStopping(event.getBundle()); + } else if (type == BundleEvent.STARTED) { + bundleStarted(event.getBundle()); + } + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/node/osgi/impl/OSGiNodeFactoryImpl.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/node/osgi/impl/OSGiNodeFactoryImpl.java new file mode 100644 index 0000000000..c79fc04c4d --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/node/osgi/impl/OSGiNodeFactoryImpl.java @@ -0,0 +1,142 @@ +/* +* 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.tuscany.sca.node.osgi.impl; + +import java.io.StringReader; +import java.net.URL; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.logging.Level; + +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.core.FactoryExtensionPoint; +import org.apache.tuscany.sca.extensibility.equinox.OSGiExtensionPointRegistry; +import org.apache.tuscany.sca.node.configuration.NodeConfiguration; +import org.apache.tuscany.sca.node.configuration.NodeConfigurationFactory; +import org.apache.tuscany.sca.node.impl.NodeFactoryImpl; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; + +/** + * The OSGi based NodeFactory + * + * @version $Rev$ $Date$ + */ +public class OSGiNodeFactoryImpl extends NodeFactoryImpl { + private ServiceRegistration registration; + private BundleContext bundleContext; + + public OSGiNodeFactoryImpl() { + super(); + Bundle bundle = FrameworkUtil.getBundle(OSGiNodeFactoryImpl.class); + if (bundle != null) { + this.bundleContext = bundle.getBundleContext(); + autoDestroy = false; + setNodeFactory(this); + } else { + throw new IllegalStateException(OSGiNodeFactoryImpl.class + " is not loaded by OSGi"); + } + } + /** + * Constructs a new Node controller + */ + public OSGiNodeFactoryImpl(BundleContext bundleContext) { + super(); + this.bundleContext = bundleContext; + autoDestroy = false; + setNodeFactory(this); + } + + protected NodeConfiguration getConfiguration(Bundle bundle, String compositeContent) { + init(); + + // Create a node configuration + FactoryExtensionPoint modelFactories = registry.getExtensionPoint(FactoryExtensionPoint.class); + NodeConfigurationFactory configurationFactory = modelFactories.getFactory(NodeConfigurationFactory.class); + NodeConfiguration configuration = configurationFactory.createNodeConfiguration(); + + URL location = bundle.getEntry("/"); + String uri = bundle.getSymbolicName(); + configuration.setURI(uri).addContribution(uri, location); + + if (compositeContent != null) { + configuration.addDeploymentComposite(uri, new StringReader(compositeContent)); + } else { + String compositeURI = (String)bundle.getHeaders().get("SCA-Composite"); + if (compositeURI == null) { + compositeURI = "OSGI-INF/sca/bundle.composite"; + } + if (compositeURI != null) { + configuration.addDeploymentComposite(uri, compositeURI); + } + } + // Set the bundle + // configuration.getExtensions().add(bundle); + return configuration; + } + + public synchronized void init() { + if (!inited) { + // Register the ExtensionPointRegistry as an OSGi service + Dictionary<Object, Object> props = new Hashtable<Object, Object>(); + registry = createExtensionPointRegistry(); + registry.start(); + registration = + bundleContext.registerService(ExtensionPointRegistry.class.getName(), registry, props); + + // Call super.init after the extension point registry is registered + super.init(); + } + } + + public synchronized void destroy() { + if (inited) { + if (registration != null) { + try { + registration.unregister(); + } catch (IllegalStateException e) { + // The service has been unregistered, ignore it + } + registration = null; + } + super.destroy(); + } + + } + + @Override + protected Object getNodeKey(NodeConfiguration configuration) { + // each bundle might have more than one nodes started + return super.getNodeKey(configuration); + } + + @Override + protected ExtensionPointRegistry createExtensionPointRegistry() { + return new OSGiExtensionPointRegistry(bundleContext); + } + + @Override + protected boolean isSchemaValidationEnabled() { + return logger.isLoggable(Level.FINE); + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/AbstractOSGiServiceHandler.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/AbstractOSGiServiceHandler.java new file mode 100644 index 0000000000..5a4b5aa836 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/AbstractOSGiServiceHandler.java @@ -0,0 +1,97 @@ +/* + * 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.tuscany.sca.osgi.remoteserviceadmin.impl; + +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.core.LifeCycleListener; +import org.apache.tuscany.sca.node.NodeFactory; +import org.apache.tuscany.sca.node.impl.NodeFactoryImpl; +import org.apache.tuscany.sca.osgi.service.discovery.impl.LocalDiscoveryService; +import org.osgi.framework.BundleContext; +import org.osgi.util.tracker.ServiceTracker; + +/** + * Watching and exporting OSGi services + */ +public class AbstractOSGiServiceHandler implements LifeCycleListener { + protected ExtensionPointRegistry registry; + protected BundleContext context; + protected NodeFactoryImpl nodeFactory; + protected EndpointIntrospector introspector; + protected ServiceTracker discoveryTracker; + protected String domainRegistry; + protected String domainURI; + + /** + * @param context + * @param clazz + * @param customizer + */ + protected AbstractOSGiServiceHandler(BundleContext context) { + this.context = context; + this.domainRegistry = context.getProperty("org.osgi.sca.domain.registry"); + this.domainURI = context.getProperty("org.osgi.sca.domain.uri"); + } + + protected ExtensionPointRegistry getExtensionPointRegistry() { + if (registry == null) { + ServiceTracker tracker = new ServiceTracker(context, ExtensionPointRegistry.class.getName(), null); + tracker.open(); + // tracker.waitForService(1000); + registry = (ExtensionPointRegistry)tracker.getService(); + tracker.close(); + } + return registry; + } + + protected synchronized void init() { + if (nodeFactory == null) { + this.nodeFactory = (NodeFactoryImpl)NodeFactory.getInstance(); + this.nodeFactory.init(); + this.discoveryTracker = LocalDiscoveryService.getTracker(context); + discoveryTracker.open(); + this.introspector = new EndpointIntrospector(context, getExtensionPointRegistry(), discoveryTracker); + } + } + + public void start() { + init(); + } + + public void stop() { + if (nodeFactory != null) { + discoveryTracker.close(); + discoveryTracker = null; + introspector = null; + nodeFactory = null; + registry = null; + context = null; + } + } + + public void setDomainRegistry(String domainRegistry) { + this.domainRegistry = domainRegistry; + } + + public void setDomainURI(String domainURI) { + this.domainURI = domainURI; + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointHelper.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointHelper.java new file mode 100644 index 0000000000..8ecc5f7ea8 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointHelper.java @@ -0,0 +1,109 @@ +/* + * 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.tuscany.sca.osgi.remoteserviceadmin.impl; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.tuscany.sca.assembly.Endpoint; +import org.apache.tuscany.sca.implementation.osgi.OSGiProperty; +import org.apache.tuscany.sca.interfacedef.Interface; +import org.apache.tuscany.sca.interfacedef.java.JavaInterface; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.RemoteConstants; + +/** + * Implementation of {@link EndpointDescription} + */ +public class EndpointHelper { + private EndpointHelper() { + } + + public static EndpointDescription createEndpointDescription(BundleContext bundleContext, Endpoint endpoint) { + return new EndpointDescription(getProperties(bundleContext, endpoint)); + } + + private static List<String> getInterfaces(Endpoint endpoint) { + Interface intf = endpoint.getComponentServiceInterfaceContract().getInterface(); + JavaInterface javaInterface = (JavaInterface)intf; + return Collections.singletonList(javaInterface.getName()); + } + + private static Map<String, Object> getProperties(BundleContext bundleContext, Endpoint endpoint) { + Map<String, Object> props = new HashMap<String, Object>(); + + if (!endpoint.isRemote()) { + String uuid = OSGiHelper.getFrameworkUUID(bundleContext); + props.put(RemoteConstants.ENDPOINT_FRAMEWORK_UUID, uuid); + } + + for (Object ext : endpoint.getService().getExtensions()) { + if (ext instanceof OSGiProperty) { + OSGiProperty prop = (OSGiProperty)ext; + props.put(prop.getName(), prop.getStringValue()); + } + } + + String serviceID = (String)props.get(Constants.SERVICE_ID); + if (serviceID != null) { + props.put(RemoteConstants.ENDPOINT_SERVICE_ID, Long.parseLong(serviceID)); + } + + // FIXME: [rfeng] We need to calculate the intents supported by this endpoint + /* + QName bindingTypeName = endpoint.getBinding().getType(); + Definitions definitions = null; + if(definitions!=null) { + for(BindingType bindingType: definitions.getBindingTypes()) { + if(bindingType.getType().equals(bindingTypeName)) { + bindingType.getAlwaysProvidedIntents(); + } + } + */ + + String intents = (String)props.get(RemoteConstants.SERVICE_EXPORTED_INTENTS); + String extraIntents = (String)props.get(RemoteConstants.SERVICE_EXPORTED_INTENTS_EXTRA); + if (intents == null) { + intents = ""; + } + if (extraIntents != null) { + intents = intents + " " + extraIntents; + } + + props.put(RemoteConstants.SERVICE_INTENTS, intents.trim()); + + props.put(RemoteConstants.ENDPOINT_ID, endpoint.getURI()); + // FIXME: [rfeng] How to pass in the remote service id from the endpoint XML + props.put(RemoteConstants.SERVICE_IMPORTED_CONFIGS, new String[] {"org.osgi.sca"}); + props.put(Endpoint.class.getName(), endpoint); + List<String> interfaces = getInterfaces(endpoint); + props.put(Constants.OBJECTCLASS, interfaces.toArray(new String[interfaces.size()])); + return props; + } + + public static Endpoint getEndpoint(EndpointDescription endpointDescription) { + return (Endpoint)endpointDescription.getProperties().get(Endpoint.class.getName()); + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointIntrospector.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointIntrospector.java new file mode 100644 index 0000000000..94ad7fbfc7 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointIntrospector.java @@ -0,0 +1,550 @@ +/* + * 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.tuscany.sca.osgi.remoteserviceadmin.impl; + +import static org.apache.tuscany.sca.assembly.Base.SCA11_TUSCANY_NS; +import static org.apache.tuscany.sca.implementation.osgi.OSGiProperty.SCA_BINDINGS; +import static org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.OSGiHelper.createOSGiProperty; +import static org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.OSGiHelper.getStringArray; +import static org.osgi.framework.Constants.OBJECTCLASS; +import static org.osgi.framework.Constants.SERVICE_ID; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.apache.tuscany.sca.assembly.AssemblyFactory; +import org.apache.tuscany.sca.assembly.Base; +import org.apache.tuscany.sca.assembly.Binding; +import org.apache.tuscany.sca.assembly.Component; +import org.apache.tuscany.sca.assembly.ComponentReference; +import org.apache.tuscany.sca.assembly.ComponentService; +import org.apache.tuscany.sca.assembly.Composite; +import org.apache.tuscany.sca.assembly.Endpoint; +import org.apache.tuscany.sca.assembly.Reference; +import org.apache.tuscany.sca.assembly.Service; +import org.apache.tuscany.sca.contribution.Contribution; +import org.apache.tuscany.sca.contribution.ContributionFactory; +import org.apache.tuscany.sca.contribution.processor.ContributionReadException; +import org.apache.tuscany.sca.contribution.processor.ContributionResolveException; +import org.apache.tuscany.sca.contribution.resolver.ExtensibleModelResolver; +import org.apache.tuscany.sca.contribution.resolver.ModelResolver; +import org.apache.tuscany.sca.contribution.resolver.ModelResolverExtensionPoint; +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.core.FactoryExtensionPoint; +import org.apache.tuscany.sca.core.UtilityExtensionPoint; +import org.apache.tuscany.sca.deployment.Deployer; +import org.apache.tuscany.sca.implementation.osgi.OSGiImplementation; +import org.apache.tuscany.sca.implementation.osgi.OSGiImplementationFactory; +import org.apache.tuscany.sca.implementation.osgi.OSGiProperty; +import org.apache.tuscany.sca.implementation.osgi.SCAConfig; +import org.apache.tuscany.sca.implementation.osgi.ServiceDescriptionsFactory; +import org.apache.tuscany.sca.interfacedef.InvalidInterfaceException; +import org.apache.tuscany.sca.interfacedef.java.JavaInterface; +import org.apache.tuscany.sca.interfacedef.java.JavaInterfaceContract; +import org.apache.tuscany.sca.interfacedef.java.JavaInterfaceFactory; +import org.apache.tuscany.sca.osgi.service.discovery.impl.LocalDiscoveryService; +import org.apache.tuscany.sca.osgi.service.discovery.impl.LocalDiscoveryService.ExtenderConfiguration; +import org.apache.tuscany.sca.policy.Intent; +import org.apache.tuscany.sca.policy.PolicyFactory; +import org.apache.tuscany.sca.policy.PolicySet; +import org.oasisopen.sca.ServiceRuntimeException; +import org.oasisopen.sca.annotation.PolicySets; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.RemoteConstants; +import org.osgi.util.tracker.ServiceTracker; + +/** + * Introspect an OSGi Service to create an SCA composite that contains a single component with + * implementation.osgi + */ +public class EndpointIntrospector { + // private BundleContext context; + private AssemblyFactory assemblyFactory; + private ContributionFactory contributionFactory; + private OSGiImplementationFactory implementationFactory; + private PolicyFactory policyFactory; + private ExtensionPointRegistry registry; + private FactoryExtensionPoint factories; + private ModelResolverExtensionPoint modelResolvers; + // private StAXArtifactProcessor<Composite> compositeProcessor; + private JavaInterfaceFactory javaInterfaceFactory; + private Deployer deployer; + private ServiceTracker discoveryTracker; + + /** + * @param name + * @return + */ + private static QName getQName(String name) { + QName qname; + if (name.startsWith("{")) { + int i = name.indexOf('}'); + if (i != -1) { + qname = new QName(name.substring(1, i), name.substring(i + 1)); + } else { + throw new IllegalArgumentException("Invalid qname: " + name); + } + } else { + // Default to SCA namespace + qname = new QName("", name); + } + return qname; + } + + /** + * @param context TODO + * @param registry + */ + public EndpointIntrospector(BundleContext context, ExtensionPointRegistry registry, ServiceTracker discoveryTracker) { + super(); + this.registry = registry; + // this.context = context; + this.discoveryTracker = discoveryTracker; + this.factories = registry.getExtensionPoint(FactoryExtensionPoint.class); + this.modelResolvers = registry.getExtensionPoint(ModelResolverExtensionPoint.class); +// this.compositeProcessor = +// registry.getExtensionPoint(StAXArtifactProcessorExtensionPoint.class).getProcessor(Composite.class); + this.assemblyFactory = factories.getFactory(AssemblyFactory.class); + this.contributionFactory = factories.getFactory(ContributionFactory.class); + this.policyFactory = factories.getFactory(PolicyFactory.class); + this.implementationFactory = factories.getFactory(OSGiImplementationFactory.class); + this.javaInterfaceFactory = factories.getFactory(JavaInterfaceFactory.class); + this.deployer = registry.getExtensionPoint(UtilityExtensionPoint.class).getUtility(Deployer.class); + } + + private Intent getIntent(String intent) { + QName name = getQName(intent); + Intent i = policyFactory.createIntent(); + i.setName(name); + return i; + } + + private List<Intent> getIntents(String[] intents) { + if (intents == null || intents.length == 0) { + return Collections.emptyList(); + } + List<Intent> intentList = new ArrayList<Intent>(); + for (String i : intents) { + Intent intent = getIntent(i); + if (intent != null) { + intentList.add(intent); + } + } + return intentList; + } + + /** + * Any property in the map overrides the service reference properties, regardless of + * case. That is, if the map contains a key then it will override any case variant + * of this key in the Service Reference.<p> + * If the map contains the objectClass or service. id property key in any case + * variant, then these properties must not override the Service Reference’s value. This + * implies that the map can provide the service.exported. interfaces, property allowing + * the Topology Manager to export any registered service, also services not specifically + * marked to be exported. + * @param reference + * @param props + * @return + */ + private Map<String, Object> getProperties(ServiceReference reference, Map<String, Object> props) { + String[] names = reference.getPropertyKeys(); + Map<String, Object> properties = new HashMap<String, Object>(); + if (names != null) { + for (String name : names) { + properties.put(name, reference.getProperty(name)); + } + } + if (props != null) { + // Create a map of names (key = lowcase name, value = name) + Map<String, String> nameMap = new HashMap<String, String>(); + if (names != null) { + for (String name : names) { + nameMap.put(name.toLowerCase(), name); + } + } + for (Map.Entry<String, Object> p : props.entrySet()) { + if (OBJECTCLASS.equalsIgnoreCase(p.getKey())) { + throw new IllegalArgumentException(OBJECTCLASS + " property cannot be overridden."); + } else if (SERVICE_ID.equalsIgnoreCase(p.getKey())) { + throw new IllegalArgumentException(SERVICE_ID + " property cannot be overridden."); + } + String key = nameMap.get(p.getKey().toLowerCase()); + if (key != null) { + properties.put(key, p.getValue()); + } else { + properties.put(p.getKey(), p.getValue()); + } + } + } + return properties; + } + + /** + * Parse the Stringp[] to support values that are separated by comma + * @param interfaces + * @return + */ + private String[] parse(String[] interfaces) { + if (interfaces == null) { + return null; + } + List<String> names = new ArrayList<String>(); + for (String i : interfaces) { + String[] parts = i.split(","); + for (String p : parts) { + names.add(p.trim()); + } + } + return names.toArray(new String[names.size()]); + } + + /** + * Introspect a local OSGi Service represented by the ServiceReference to create + * an SCA service with the required intents and bindings + * @param reference The service reference for a local OSGi service + * @param props Addiontal properties + * @return An SCA contribution with a deployable composite for the SCA service + * @throws Exception + */ + public Contribution introspect(ServiceReference reference, Map<String, Object> props) throws Exception { + Bundle bundle = reference.getBundle(); + Map<String, Object> properties = getProperties(reference, props); + Collection<OSGiProperty> osgiProps = implementationFactory.createOSGiProperties(reference); + Long sid = (Long)reference.getProperty(SERVICE_ID); + + String[] requiredIntents = getStringArray(properties.get(RemoteConstants.SERVICE_EXPORTED_INTENTS)); + List<Intent> intents = getIntents(requiredIntents); + String[] requiredIntentsExtra = getStringArray(properties.get(RemoteConstants.SERVICE_EXPORTED_INTENTS_EXTRA)); + List<Intent> extraIntents = getIntents(requiredIntentsExtra); + Set<Intent> allIntents = new HashSet<Intent>(intents); + allIntents.addAll(extraIntents); + + String[] bindingNames = getStringArray(properties.get(SCA_BINDINGS)); + Collection<Binding> bindings = loadBindings(bindingNames); + + String[] remoteInterfaces = getStringArray(reference.getProperty(RemoteConstants.SERVICE_EXPORTED_INTERFACES)); + if (remoteInterfaces == null || remoteInterfaces.length > 0 && "*".equals(remoteInterfaces[0])) { + remoteInterfaces = getStringArray(reference.getProperty(OBJECTCLASS)); + } else { + remoteInterfaces = parse(remoteInterfaces); + String[] objectClasses = getStringArray(reference.getProperty(OBJECTCLASS)); + Set<String> objectClassSet = new HashSet<String>(Arrays.asList(objectClasses)); + if (!objectClassSet.containsAll(Arrays.asList(remoteInterfaces))) { + throw new IllegalArgumentException( + "The exported interfaces are not a subset of the types" + " listed in the objectClass service property from the Service Reference"); + } + } + + Contribution contribution = generateContribution(bundle, sid, remoteInterfaces, bindings, allIntents, osgiProps); + return contribution; + } + + public String instrospectSCAConfig(ServiceReference reference, Map<String, Object> props, ComponentService service){ + + ServiceDescriptionsFactory serviceDescriptionFactory = registry.getExtensionPoint(ServiceDescriptionsFactory.class); + SCAConfig scaConfig = serviceDescriptionFactory.createSCAConfig(); + + // add the binding configurations + List<Binding> bindings = scaConfig.getBindings(); + bindings.addAll(service.getBindings()); + + // add the intent configurations + List<Intent> intents = scaConfig.getIntents(); + intents.addAll(service.getRequiredIntents()); + + // add the policy set configurations + List<PolicySet> policySets = scaConfig.getPolicySets(); + policySets.addAll(service.getPolicySets()); + + // set up the target namespace + // TODO - there is a bug in the spec which only allow bindings from one + // namsepace to be included in sca-config element. Here we just + // the first bindings namespace + Map<String, Object> properties = getProperties(reference, props); + String[] bindingNames = getStringArray(properties.get(SCA_BINDINGS)); + if (bindingNames.length > 0){ + QName firstBindingQName = getQName(bindingNames[0]); + scaConfig.setTargetNamespace(firstBindingQName.getNamespaceURI()); + } + + // write the sca config out to XML + String scaConfigXMLString = ""; + + try { + Writer writer = new StringWriter(); + deployer.saveXMLDocument(scaConfig, writer, deployer.createMonitor()); + scaConfigXMLString = writer.toString(); + } catch (Exception ex){ + throw new ServiceRuntimeException(ex); + } + + return scaConfigXMLString; + } + + /* + public Contribution loadContribution(Bundle bundle, Composite composite) { + try { + URL root = bundle.getEntry("/"); + Contribution contribution = deployer.loadContribution(root.toURI(), root, deployer.createMonitor()); + deployer.attachDeploymentComposite(contribution, composite, false); + return contribution; + } catch (Exception e) { + throw new ServiceRuntimeException(e); + } + } + */ + + /** + * Generate a contribution that contains the composite for the exported service + * @param bundle The OSGi bundle + * @param sid The service id + * @param remoteInterfaces + * @param bindings + * @param allIntents + * @return + * @throws ClassNotFoundException + * @throws InvalidInterfaceException + */ + private Contribution generateContribution(Bundle bundle, + Long sid, + String[] remoteInterfaces, + Collection<Binding> bindings, + Set<Intent> allIntents, + Collection<OSGiProperty> osgiProps) throws ClassNotFoundException, + InvalidInterfaceException { + String id = "osgi.service." + UUID.randomUUID(); + Composite composite = assemblyFactory.createComposite(); + composite.setName(new QName(SCA11_TUSCANY_NS, id)); + + Component component = assemblyFactory.createComponent(); + component.setName(id); + + composite.getComponents().add(component); + + OSGiImplementation implementation = implementationFactory.createOSGiImplementation(); + + implementation.setBundle(bundle); + component.setImplementation(implementation); + implementation.setUnresolved(false); + + OSGiProperty serviceID = implementationFactory.createOSGiProperty(); + serviceID.setName(SERVICE_ID); + // The service.id is Long + serviceID.setValue(String.valueOf(sid)); + + for (String intf : remoteInterfaces) { + Service service = assemblyFactory.createService(); + JavaInterfaceContract interfaceContract = createJavaInterfaceContract(bundle, intf); + String name = intf.substring(intf.lastIndexOf('.') + 1); + service.setName(name); + service.setInterfaceContract(interfaceContract); + + implementation.getServices().add(service); + + ComponentService componentService = assemblyFactory.createComponentService(); + componentService.setName(service.getName()); + componentService.getExtensions().add(serviceID); + componentService.getExtensions().addAll(osgiProps); + + component.getServices().add(componentService); + componentService.setService(service); + } + + for (ComponentService componentService : component.getServices()) { + componentService.getRequiredIntents().addAll(allIntents); + componentService.getBindings().addAll(bindings); + } + + // FIXME: Should we scan the owning bundle to create the SCA contribution? + Contribution contribution = loadContribution(bundle, id, composite); + return contribution; + } + + private Contribution loadContribution(Bundle bundle, String id, Composite composite) { + Contribution contribution = contributionFactory.createContribution(); + contribution.setClassLoader(OSGiHelper.createBundleClassLoader(bundle)); + contribution.setURI(id); + contribution.setLocation(bundle.getEntry("/").toString()); + deployer.attachDeploymentComposite(contribution, composite, false); + ModelResolver modelResolver = new ExtensibleModelResolver(contribution, modelResolvers, factories); + contribution.setModelResolver(modelResolver); + // compositeProcessor.resolve(composite, modelResolver, new ProcessorContext(registry)); + contribution.setUnresolved(true); + return contribution; + } + + /** + * @param bundle + * @param endpoint + * @return + * @throws Exception + */ + public Contribution introspect(Bundle bundle, EndpointDescription endpoint) throws Exception { + Collection<Binding> bindings = Collections.emptyList(); + Collection<String> interfaces = Collections.emptyList(); + Collection<Intent> intents = Collections.emptyList(); + Endpoint ep = (Endpoint)endpoint.getProperties().get(Endpoint.class.getName()); + Collection<OSGiProperty> osgiProps = implementationFactory.createOSGiProperties(endpoint.getProperties()); + if (ep != null) { + bindings = Collections.singletonList(ep.getBinding()); + interfaces = Collections.singletonList(((JavaInterface)ep.getComponentServiceInterfaceContract().getInterface()).getName()); + // FIXME: [rfeng] We need to build the in-memory composite so that intents are calculated at the ep level + intents = ep.getService().getRequiredIntents(); + } else { + Map<String, Object> properties = endpoint.getProperties(); + interfaces = endpoint.getInterfaces(); + String[] requiredIntents = getStringArray(properties.get(RemoteConstants.SERVICE_INTENTS)); + intents = getIntents(requiredIntents); + + String[] bindingNames = getStringArray(properties.get(SCA_BINDINGS)); + bindings = loadBindings(bindingNames); + } + + Contribution contribution = generateContribution(bundle, interfaces, bindings, intents, osgiProps); + return contribution; + } + + private Contribution generateContribution(Bundle bundle, + Collection<String> remoteInterfaces, + Collection<Binding> bindings, + Collection<Intent> intents, + Collection<OSGiProperty> osgiProps) throws ClassNotFoundException, + InvalidInterfaceException, ContributionResolveException { + String id = "osgi.reference." + UUID.randomUUID(); + Composite composite = assemblyFactory.createComposite(); + composite.setName(new QName(Base.SCA11_TUSCANY_NS, id)); + + Component component = assemblyFactory.createComponent(); + component.setName(id); + // component.setAutowire(Boolean.TRUE); + + composite.getComponents().add(component); + + OSGiImplementation implementation = implementationFactory.createOSGiImplementation(); + + implementation.setBundle(bundle); + component.setImplementation(implementation); + implementation.setUnresolved(false); + + int count = 0; + for (String intf : remoteInterfaces) { + Reference reference = assemblyFactory.createReference(); + JavaInterfaceContract interfaceContract = createJavaInterfaceContract(bundle, intf); + + reference.setName("ref" + (count++)); + reference.setInterfaceContract(interfaceContract); + + implementation.getReferences().add(reference); + + ComponentReference componentReference = assemblyFactory.createComponentReference(); + componentReference.setName(reference.getName()); + componentReference.getExtensions().addAll(osgiProps); + component.getReferences().add(componentReference); + componentReference.setReference(reference); + componentReference.setWiredByImpl(true); + } + + for (ComponentReference componentReference : component.getReferences()) { + componentReference.getRequiredIntents().addAll(intents); + componentReference.getBindings().addAll(bindings); + } + + Contribution contribution = loadContribution(bundle, id, composite); + return contribution; + } + + private JavaInterfaceContract createJavaInterfaceContract(Bundle bundle, String intf) + throws ClassNotFoundException, InvalidInterfaceException { + JavaInterfaceContract interfaceContract = javaInterfaceFactory.createJavaInterfaceContract(); + Class<?> interfaceClass = bundle.loadClass(intf); + JavaInterface javaInterface = javaInterfaceFactory.createJavaInterface(); + // [rfeng] For OSGi, the interfaces should be marked as remotable + javaInterface.setRemotable(true); + // [rfeng] We need to mark the interface to be remotable before the createJavaInterface() is called + javaInterfaceFactory.createJavaInterface(javaInterface, interfaceClass); + interfaceContract.setInterface(javaInterface); + if (javaInterface.getCallbackClass() != null) { + JavaInterface callbackInterface = javaInterfaceFactory.createJavaInterface(javaInterface.getCallbackClass()); + callbackInterface.setRemotable(true); + interfaceContract.setCallbackInterface(callbackInterface); + } + return interfaceContract; + } + + private Collection<Binding> loadBindings(String[] qnames) throws IOException, ContributionReadException, + XMLStreamException { + if (qnames == null || qnames.length == 0) { + return Collections.emptyList(); + } + QName[] bindingNames = new QName[qnames.length]; + int index = 0; + for (String name : qnames) { + bindingNames[index++] = getQName(name); + } + + LocalDiscoveryService discoveryService = (LocalDiscoveryService)discoveryTracker.getService(); + + Map<QName, Binding> bindingMap = new HashMap<QName, Binding>(); + if (discoveryService != null) { + for (ExtenderConfiguration config : discoveryService.getConfigurations()) { + for (SCAConfig sc : config.getSCAConfigs()) { + for (QName bindingName : bindingNames) { + if ("".equals(bindingName.getNamespaceURI()) || + sc.getTargetNamespace().equals(bindingName.getNamespaceURI())) { + for (Binding binding : sc.getBindings()) { + if (bindingName.getLocalPart().equals(binding.getName())) { + // We need to check duplications + if (bindingMap.put(bindingName, binding) != null) { + throw new ServiceRuntimeException("Duplicate binding found: " + bindingName); + } + } + } + } + } + } + } + } + for (QName bindingName : bindingNames) { + if (!bindingMap.containsKey(bindingName)) { + throw new ServiceRuntimeException("Binding cannot be resolved: " + bindingName); + } + } + return bindingMap.values(); + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointMatcher.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointMatcher.java new file mode 100644 index 0000000000..f978ba658d --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointMatcher.java @@ -0,0 +1,273 @@ +/* + * 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.tuscany.sca.osgi.remoteserviceadmin.impl; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.tuscany.sca.common.java.collection.CollectionMap; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Filter; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.hooks.service.ListenerHook.ListenerInfo; +import org.osgi.service.remoteserviceadmin.EndpointDescription; + +/** + * Matching endpoint descriptions against the sevice listeners using OSGi filiters + */ +public class EndpointMatcher { + private static final Logger logger = Logger.getLogger(EndpointMatcher.class.getName()); + private final EndpointMap endpointDescriptions = new EndpointMap(); + private final ListenerMap listeners = new ListenerMap(); + private final BundleContext context; + private final BlockingQueue<ImportAction> importQueue = new ArrayBlockingQueue<ImportAction>(256, true); + + public EndpointMatcher(BundleContext context) { + super(); + this.context = context; + } + + public static boolean matches(String filter, EndpointDescription endpointDescription) { + Filter f = null; + try { + f = FrameworkUtil.createFilter(filter); + } catch (InvalidSyntaxException e) { + throw new IllegalArgumentException(e); + } + Hashtable<String, Object> props = new Hashtable<String, Object>(endpointDescription.getProperties()); + return f.match(props); + } + + private void importEndpoint(ListenerInfo listener, EndpointDescription ep) { + ImportAction request = new ImportAction(ImportAction.Type.Add, listener, ep); + try { + importQueue.put(request); + } catch (InterruptedException e) { + throw new IllegalArgumentException(e); + } + } + + private void unimportEndpoint(ListenerInfo listener, EndpointDescription ep) { + ImportAction request = new ImportAction(ImportAction.Type.Remove, listener, ep); + try { + importQueue.put(request); + } catch (InterruptedException e) { + throw new IllegalArgumentException(e); + } + } + + public synchronized void added(ListenerInfo listener) { + String filter = listener.getFilter(); + listeners.putValue(filter, listener); + for (EndpointDescription ep : getEndpoints(filter)) { + importEndpoint(listener, ep); + } + } + + public synchronized Collection<String> added(Collection<ListenerInfo> listeners) { + for (ListenerInfo listener : listeners) { + if (accepts(listener)) { + if (!listener.isRemoved() && listener.getBundleContext().getBundle().getBundleId() != 0L) { + added(listener); + } + } + } + return getFilters(); + } + + private boolean accepts(ListenerInfo listener) { + BundleContext context = listener.getBundleContext(); + return context != null && listener.getFilter() != null && context != this.context; + } + + public synchronized void removed(ListenerInfo listener) { + String filter = listener.getFilter(); + if (accepts(listener)) + if (listeners.removeValue(filter, listener, true)) { + // Find the corresponding ImportRegistration with the listener + for (EndpointDescription ep : getEndpoints(filter)) { + unimportEndpoint(listener, ep); + } + if (getListeners(filter).isEmpty()) { + // No more listeners on the this filter, clean up the endpoint descriptionss + endpointDescriptions.remove(filter); + } + + } + } + + public synchronized Collection<String> removed(Collection<ListenerInfo> listeners) { + for (ListenerInfo listener : listeners) { + removed(listener); + } + return getFilters(); + } + + public synchronized void added(EndpointDescription endpointDescription) { + for (Map.Entry<String, Collection<ListenerInfo>> entry : listeners.entrySet()) { + if (matches(entry.getKey(), endpointDescription)) { + endpointDescriptions.putValue(entry.getKey(), endpointDescription); + for (ListenerInfo listener : entry.getValue()) { + importEndpoint(listener, endpointDescription); + } + } + } + } + + public synchronized void added(EndpointDescription endpointDescription, String matchedFilter) { + if (endpointDescriptions.putValue(matchedFilter, endpointDescription)) { + Collection<ListenerInfo> listenerInfos = listeners.get(matchedFilter); + if (listenerInfos != null) { + for (ListenerInfo listener : listenerInfos) { + importEndpoint(listener, endpointDescription); + } + } + } + } + + public synchronized void removed(EndpointDescription endpointDescription, String matchedFilter) { + if (endpointDescriptions.removeValue(matchedFilter, endpointDescription, true)) { + for (ListenerInfo listener : getListeners(matchedFilter)) { + unimportEndpoint(listener, endpointDescription); + } + } + } + + public synchronized Set<String> getFilters() { + return new HashSet<String>(listeners.keySet()); + } + + public synchronized void clear() { + endpointDescriptions.clear(); + listeners.clear(); + importQueue.clear(); + } + + public synchronized Collection<ListenerInfo> getListeners(String filter) { + Collection<ListenerInfo> collection = listeners.get(filter); + if (collection == null) { + return Collections.emptySet(); + } else { + return collection; + } + } + + public synchronized Collection<EndpointDescription> getEndpoints(String filter) { + Collection<EndpointDescription> collection = endpointDescriptions.get(filter); + if (collection == null) { + return Collections.emptySet(); + } else { + return collection; + } + } + + public CollectionMap<Class<?>, ListenerInfo> groupListeners(EndpointDescription endpointDescription, + String matchedFilter) { + Collection<ListenerInfo> snapshot = new HashSet<ListenerInfo>(getListeners(matchedFilter)); + + // Try to partition the listeners by the interface classes + List<String> interfaceNames = endpointDescription.getInterfaces(); + CollectionMap<Class<?>, ListenerInfo> interfaceToListeners = new CollectionMap<Class<?>, ListenerInfo>(); + for (String i : interfaceNames) { + for (Iterator<ListenerInfo> it = snapshot.iterator(); it.hasNext();) { + try { + ListenerInfo listener = it.next(); + if (listener.isRemoved()) { + it.remove(); + continue; + } + if (!matchedFilter.equals(listener.getFilter())) { + continue; + } + try { + // The classloading can be synchronzed against the serviceListeners + Class<?> interfaceClass = listener.getBundleContext().getBundle().loadClass(i); + interfaceToListeners.putValue(interfaceClass, listener); + } catch (IllegalStateException e) { + logger.log(Level.WARNING, e.getMessage(), e); + // Ignore the exception + } + } catch (ClassNotFoundException e) { + // Ignore the listener as it cannot load the interface class + } + } + } + return interfaceToListeners; + } + + public BlockingQueue<ImportAction> getImportQueue() { + return importQueue; + } + + private static class ListenerMap extends CollectionMap<String, ListenerInfo> { + private static final long serialVersionUID = -8612202123531331219L; + + @Override + protected Collection<ListenerInfo> createCollection() { + return new HashSet<ListenerInfo>(); + } + } + + private static class EndpointMap extends CollectionMap<String, EndpointDescription> { + private static final long serialVersionUID = -6261405398109798549L; + + @Override + protected Collection<EndpointDescription> createCollection() { + return new HashSet<EndpointDescription>(); + } + } + + /** + * Representation of an import/unimport request + */ + public static class ImportAction { + enum Type { + Add, Remove + }; + + public final Type type; + public final ListenerInfo listenerInfo; + public final EndpointDescription endpointDescription; + + /** + * @param type + * @param listenerInfo + * @param endpointDescription + */ + public ImportAction(Type type, ListenerInfo listenerInfo, EndpointDescription endpointDescription) { + super(); + this.type = type; + this.listenerInfo = listenerInfo; + this.endpointDescription = endpointDescription; + } + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ExportReferenceImpl.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ExportReferenceImpl.java new file mode 100644 index 0000000000..13c7dd3cf3 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ExportReferenceImpl.java @@ -0,0 +1,73 @@ +/* + * 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.tuscany.sca.osgi.remoteserviceadmin.impl; + +import org.apache.tuscany.sca.node.Node; +import org.osgi.framework.ServiceReference; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.ExportReference; +import org.osgi.service.remoteserviceadmin.ExportRegistration; + +/** + * + */ +public class ExportReferenceImpl implements ExportReference { + private Node node; + private final ServiceReference exportedService; + private final EndpointDescription endpointDescription; + private int count; + + /** + * @param exportedService + * @param endpointDescription + */ + public ExportReferenceImpl(Node node, ServiceReference exportedService, EndpointDescription endpointDescription) { + super(); + this.node = node; + this.exportedService = exportedService; + this.endpointDescription = endpointDescription; + } + + public ServiceReference getExportedService() { + return exportedService; + } + + public EndpointDescription getExportedEndpoint() { + return endpointDescription; + } + + public synchronized ExportRegistration register() { + count++; + return new ExportRegistrationImpl(this); + } + + public synchronized void unregister() { + if (count > 0) { + count--; + } + if (count == 0) { + if (node != null) { + node.stop(); + node = null; + } + } + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ExportRegistrationImpl.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ExportRegistrationImpl.java new file mode 100644 index 0000000000..39cbd7b9e6 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ExportRegistrationImpl.java @@ -0,0 +1,80 @@ +/* + * 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.tuscany.sca.osgi.remoteserviceadmin.impl; + +import org.osgi.framework.ServiceReference; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.ExportReference; +import org.osgi.service.remoteserviceadmin.ExportRegistration; + +/** + * Implementation of {@link ExportRegistration} + */ +public class ExportRegistrationImpl implements ExportRegistration { + private ExportReferenceImpl exportReference; + private Throwable exception; + + /** + * @param exportedService + * @param endpointDescription + * @param exception + */ + public ExportRegistrationImpl(ExportReferenceImpl exportReference, Throwable exception) { + super(); + this.exportReference = exportReference; + this.exception = exception; + } + + /** + * @param exportedService + * @param endpointDescription + */ + public ExportRegistrationImpl(ExportReferenceImpl exportReference) { + this(exportReference, null); + } + + /** + * @see org.osgi.remoteserviceadmin.ExportRegistration#close() + */ + public void close() { + if (exportReference != null) { + exportReference.unregister(); + } + exception = null; + exportReference = null; + } + + public ServiceReference getExportedService() { + return exportReference.getExportedService(); + } + + public EndpointDescription getEndpointDescription() { + return exportReference.getExportedEndpoint(); + } + + public Throwable getException() { + return exception; + } + + public ExportReference getExportReference() throws IllegalStateException { + return exportReference; + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ImportReferenceImpl.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ImportReferenceImpl.java new file mode 100644 index 0000000000..9c5b1818c8 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ImportReferenceImpl.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.tuscany.sca.osgi.remoteserviceadmin.impl; + +import org.apache.tuscany.sca.node.Node; +import org.osgi.framework.ServiceReference; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.ImportReference; +import org.osgi.service.remoteserviceadmin.ImportRegistration; + +/** + * Implementation of ImportReference. It keeps a reference count of ImportRegistrations + */ +public class ImportReferenceImpl implements ImportReference { + private Node node; + private final ServiceReference importedService; + private final EndpointDescription endpointDescription; + private int count = 0; + + /** + * @param exportedService + * @param endpointDescription + */ + public ImportReferenceImpl(Node node, ServiceReference importedService, EndpointDescription endpointDescription) { + super(); + this.node = node; + this.importedService = importedService; + this.endpointDescription = endpointDescription; + } + + public ServiceReference getImportedService() { + return importedService; + } + + public EndpointDescription getImportedEndpoint() { + return endpointDescription; + } + + public synchronized ImportRegistration register() { + count++; + return new ImportRegistrationImpl(this); + } + + public synchronized void unregister() { + if (count > 0) { + count--; + } + if (count == 0) { + if (node != null) { + node.stop(); + node = null; + } + } + } +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ImportRegistrationImpl.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ImportRegistrationImpl.java new file mode 100644 index 0000000000..9506128286 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ImportRegistrationImpl.java @@ -0,0 +1,70 @@ +/* + * 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.tuscany.sca.osgi.remoteserviceadmin.impl; + +import org.osgi.service.remoteserviceadmin.ImportReference; +import org.osgi.service.remoteserviceadmin.ImportRegistration; + +/** + * + */ +public class ImportRegistrationImpl implements ImportRegistration { + private ImportReferenceImpl importReference; + private Throwable exception; + + /** + * @param importReference + */ + public ImportRegistrationImpl(ImportReferenceImpl importReference) { + super(); + this.importReference = importReference; + } + + /** + * @param exportedService + * @param endpointDescription + * @param exception + */ + public ImportRegistrationImpl(ImportReferenceImpl importReference, Throwable exception) { + super(); + this.importReference = importReference; + this.exception = exception; + } + + /** + * @see org.osgi.remoteserviceadmin.ImportRegistration#close() + */ + public void close() { + if (importReference != null) { + importReference.unregister(); + } + exception = null; + importReference = null; + } + + public Throwable getException() { + return exception; + } + + public ImportReference getImportReference() { + return importReference; + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiHelper.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiHelper.java new file mode 100644 index 0000000000..460d291f53 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiHelper.java @@ -0,0 +1,234 @@ +/* + * 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.tuscany.sca.osgi.remoteserviceadmin.impl; + +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.UUID; + +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.core.FactoryExtensionPoint; +import org.apache.tuscany.sca.implementation.osgi.OSGiImplementationFactory; +import org.apache.tuscany.sca.implementation.osgi.OSGiProperty; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Filter; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; + +/** + * + */ +public class OSGiHelper { + public final static String FRAMEWORK_UUID = "org.osgi.framework.uuid"; + + private OSGiHelper() { + } + + /** + * In OSGi, the value of String+ can be a single String, String[] or Collection<String> + * @param value + * @return + */ + public static String[] getStringArray(Object value) { + if (value == null) { + return null; + } + if (value instanceof String) { + return new String[] {(String)value}; + } else if (value instanceof Collection) { + Collection<String> collection = (Collection)value; + return collection.toArray(new String[collection.size()]); + } + return (String[])value; + + } + + public static Collection<String> getStringCollection(Object value) { + String[] values = getStringArray(value); + if (values == null) { + return null; + } else { + return Arrays.asList(values); + } + } + + public static String[] getStringArray(ServiceReference serviceReference, String propertyName) { + Object propertyValue = serviceReference.getProperty(propertyName); + return getStringArray(propertyValue); + } + + public static Collection<String> getStringCollection(ServiceReference serviceReference, String propertyName) { + Object propertyValue = serviceReference.getProperty(propertyName); + return getStringCollection(propertyValue); + } + + public static Filter createFilter(BundleContext context, String filterValue) { + if (filterValue == null) { + return null; + } + try { + return context.createFilter(filterValue); + } catch (InvalidSyntaxException ex) { + throw new IllegalArgumentException("Invalid Filter: " + filterValue, ex); + } + } + + /** + * Get a collection of resources that are configured by the given header + * @param bundle The bundle + * @param header + * @param defaultValue + * @return + */ + public static Collection<URL> getConfiguration(Bundle bundle, String header, String defaultValue) { + String value = (String)bundle.getHeaders().get(header); + if (value == null) { + return Collections.emptyList(); + } + value = value.trim(); + String paths[] = value.split("( |\t|\n|\r|\f|,)+"); + if ("".equals(value) || paths.length == 0) { + if (defaultValue != null) { + paths = new String[] {defaultValue}; + } else { + paths = new String[0]; + } + } + Collection<URL> files = new HashSet<URL>(); + for (String path : paths) { + if ("".equals(path)) { + // Skip empty ones + continue; + } + if (path.endsWith("/")) { + path = path + "*.xml"; + } + if (!path.startsWith("/")) { + path = "/" + path; + } + int lastIndex = path.lastIndexOf('/'); + String root = path.substring(0, lastIndex); + if ("".equals(root)) { + root = "/"; + } + String pattern = path.substring(lastIndex + 1); + Enumeration<URL> entries = bundle.findEntries(root, pattern, false); + if (entries != null) { + while (entries.hasMoreElements()) { + files.add(entries.nextElement()); + } + } + } + return files; + } + + public static Collection<OSGiProperty> getOSGiProperties(ExtensionPointRegistry registry, ServiceReference reference) { + FactoryExtensionPoint factoryExtensionPoint = registry.getExtensionPoint(FactoryExtensionPoint.class); + OSGiImplementationFactory implementationFactory = + factoryExtensionPoint.getFactory(OSGiImplementationFactory.class); + return implementationFactory.createOSGiProperties(reference); + } + + public static OSGiProperty createOSGiProperty(ExtensionPointRegistry registry, String name, Object value) { + FactoryExtensionPoint factoryExtensionPoint = registry.getExtensionPoint(FactoryExtensionPoint.class); + OSGiImplementationFactory implementationFactory = + factoryExtensionPoint.getFactory(OSGiImplementationFactory.class); + return implementationFactory.createOSGiProperty(name, value); + } + + public synchronized static String getFrameworkUUID(BundleContext bundleContext) { + String uuid = null; + if (bundleContext != null) { + uuid = bundleContext.getProperty(FRAMEWORK_UUID); + } else { + uuid = System.getProperty(FRAMEWORK_UUID); + } + if (uuid == null) { + uuid = UUID.randomUUID().toString(); + } + System.setProperty(FRAMEWORK_UUID, uuid); + return uuid; + } + + public static ClassLoader createBundleClassLoader(Bundle bundle) { + return new BundleClassLoader(bundle); + } + + private static class BundleClassLoader extends ClassLoader { + private Bundle bundle; + + public BundleClassLoader(Bundle bundle) { + super(null); + this.bundle = bundle; + } + + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + return bundle.loadClass(name); + } + + @Override + protected URL findResource(String name) { + return bundle.getResource(name); + } + + @Override + protected Enumeration<URL> findResources(String name) throws IOException { + Enumeration<URL> urls = bundle.getResources(name); + if (urls == null) { + List<URL> list = Collections.emptyList(); + return Collections.enumeration(list); + } else { + return urls; + } + } + } + + /** + * Find out what elements are added between the oldValues and newValues + * @param oldValues + * @param newValues + * @return + */ + public static Collection<String> getAddedItems(Collection<String> oldValues, Collection<String> newValues) { + if (newValues == null) { + newValues = Collections.emptySet(); + } + + Collection<String> deltaInterest = new HashSet<String>(newValues); + if (oldValues == null) { + oldValues = Collections.emptySet(); + } + deltaInterest.removeAll(oldValues); + return deltaInterest; + } + + public static Collection<String> getRemovedItems(Collection<String> oldValues, Collection<String> newValues) { + return getAddedItems(newValues, oldValues); + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiServiceExporter.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiServiceExporter.java new file mode 100644 index 0000000000..081a3a60c0 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiServiceExporter.java @@ -0,0 +1,163 @@ +/* + * 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.tuscany.sca.osgi.remoteserviceadmin.impl; + +import static org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.EndpointHelper.createEndpointDescription; +import static org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.OSGiHelper.createOSGiProperty; +import static org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.OSGiHelper.getFrameworkUUID; +import static org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.OSGiHelper.getOSGiProperties; +import static org.osgi.framework.Constants.SERVICE_ID; +import static org.osgi.service.remoteserviceadmin.RemoteConstants.ENDPOINT_FRAMEWORK_UUID; +import static org.osgi.service.remoteserviceadmin.RemoteConstants.ENDPOINT_SERVICE_ID; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.tuscany.sca.assembly.Binding; +import org.apache.tuscany.sca.assembly.Component; +import org.apache.tuscany.sca.assembly.ComponentService; +import org.apache.tuscany.sca.assembly.Endpoint; +import org.apache.tuscany.sca.contribution.Contribution; +import org.apache.tuscany.sca.implementation.osgi.OSGiProperty; +import org.apache.tuscany.sca.implementation.osgi.SCAConfig; +import org.apache.tuscany.sca.implementation.osgi.ServiceDescriptionsFactory; +import org.apache.tuscany.sca.node.configuration.NodeConfiguration; +import org.apache.tuscany.sca.node.impl.NodeImpl; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.ExportRegistration; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +/** + * Watching and exporting OSGi services + */ +public class OSGiServiceExporter extends AbstractOSGiServiceHandler implements ServiceTrackerCustomizer { + private final static Logger logger = Logger.getLogger(OSGiServiceExporter.class.getName()); + + private Map<EndpointDescription, ExportReferenceImpl> exportReferences = + new ConcurrentHashMap<EndpointDescription, ExportReferenceImpl>(); + + /** + * @param context + * @param clazz + * @param customizer + */ + public OSGiServiceExporter(BundleContext context) { + super(context); + } + + public void start() { + init(); + } + + public void stop() { + exportReferences.clear(); + super.stop(); + } + + public Object addingService(ServiceReference reference) { + return exportService(reference, null); + } + + public List<ExportRegistration> exportService(ServiceReference reference, Map<String, Object> properties) { + // FIXME: [rfeng] We need to check if a corresponding endpoint has been exported + try { + Contribution contribution = introspector.introspect(reference, properties); + if (contribution != null) { + + NodeConfiguration configuration = nodeFactory.createNodeConfiguration(); + if (domainRegistry != null) { + configuration.setDomainRegistryURI(domainRegistry); + } + if (domainURI != null) { + configuration.setDomainURI(domainURI); + } + configuration.setURI(contribution.getURI()); + // configuration.getExtensions().add(reference.getBundle()); + Component component = contribution.getDeployables().get(0).getComponents().get(0); + ComponentService service = component.getServices().get(0); + service.getExtensions().addAll(getOSGiProperties(registry, reference)); + service.getExtensions().add(createOSGiProperty(registry, + ENDPOINT_FRAMEWORK_UUID, + getFrameworkUUID(reference.getBundle() + .getBundleContext()))); + service.getExtensions().add(createOSGiProperty(registry, ENDPOINT_SERVICE_ID, reference + .getProperty(SERVICE_ID))); + + // create the org.osgi.sca.config.xml property + service.getExtensions().add(createSCAConfigXMLProperty(reference, properties, service)); + + // FIXME: Configure the domain and node URI + NodeImpl node = new NodeImpl(nodeFactory, configuration, Collections.singletonList(contribution)); + node.start(); + List<ExportRegistration> exportedServices = new ArrayList<ExportRegistration>(); + for (Endpoint endpoint : service.getEndpoints()) { + EndpointDescription endpointDescription = createEndpointDescription(context, endpoint); + synchronized (this) { + ExportReferenceImpl exportReference = exportReferences.get(endpointDescription); + if (exportReference == null) { + exportReference = new ExportReferenceImpl(node, reference, endpointDescription); + } + ExportRegistration exportRegistration = exportReference.register(); + exportedServices.add(exportRegistration); + } + } + return exportedServices; + } else { + return null; + } + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + ExportRegistration exportRegistration = new ExportRegistrationImpl(null, e); + return Collections.singletonList(exportRegistration); + } + } + + public void modifiedService(ServiceReference reference, Object service) { + removedService(reference, service); + exportService(reference, null); + } + + public void removedService(ServiceReference reference, Object service) { + List<ExportRegistration> exportedServices = (List<ExportRegistration>)service; + for (ExportRegistration exportRegistration : exportedServices) { + exportRegistration.close(); + } + } + + private OSGiProperty createSCAConfigXMLProperty(ServiceReference reference, Map<String, Object> props, ComponentService service){ + + // create the <sca-config> element + String scaConfigXMLString = introspector.instrospectSCAConfig(reference, props, service); + + // wrap the <sca-config> element in an <xml> element + String scaConfigXMLStringWithoutXMLHeader = scaConfigXMLString.substring(scaConfigXMLString.indexOf("><") + 1); + scaConfigXMLString = "<xml>" + scaConfigXMLStringWithoutXMLHeader + "</xml>"; + + // create and return the OSGI property + return createOSGiProperty(registry, "org.osgi.sca.config.xml", scaConfigXMLString); + } +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiServiceImporter.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiServiceImporter.java new file mode 100644 index 0000000000..f24e9af7c1 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiServiceImporter.java @@ -0,0 +1,114 @@ +/* + * 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.tuscany.sca.osgi.remoteserviceadmin.impl; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.tuscany.sca.assembly.Component; +import org.apache.tuscany.sca.assembly.ComponentReference; +import org.apache.tuscany.sca.contribution.Contribution; +import org.apache.tuscany.sca.node.configuration.NodeConfiguration; +import org.apache.tuscany.sca.node.impl.NodeImpl; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.ImportRegistration; + +/** + * Watching and exporting OSGi services + */ +public class OSGiServiceImporter extends AbstractOSGiServiceHandler { + private final static Logger logger = Logger.getLogger(OSGiServiceImporter.class.getName()); + private Map<EndpointDescription, ImportReferenceImpl> importReferences = + new ConcurrentHashMap<EndpointDescription, ImportReferenceImpl>(); + + /** + * @param context + * @param clazz + * @param customizer + */ + public OSGiServiceImporter(BundleContext context) { + super(context); + } + + public void start() { + // Defer init() to importService() + } + + public void stop() { + importReferences.clear(); + super.stop(); + } + + public ImportRegistration importService(Bundle bundle, EndpointDescription endpointDescription) { + init(); + try { + Contribution contribution = introspector.introspect(bundle, endpointDescription); + if (contribution != null) { + + NodeConfiguration configuration = nodeFactory.createNodeConfiguration(); + if (domainRegistry != null) { + configuration.setDomainRegistryURI(domainRegistry); + } + if (domainURI != null) { + configuration.setDomainURI(domainURI); + } + configuration.setURI(contribution.getURI()); + // configuration.getExtensions().add(bundle); + // FIXME: Configure the domain and node URI + NodeImpl node = new NodeImpl(nodeFactory, configuration, Collections.singletonList(contribution)); + node.start(); + + Component component = contribution.getDeployables().get(0).getComponents().get(0); + ComponentReference componentReference = component.getReferences().get(0); + ServiceReference serviceReference = + context.getServiceReference("(sca.reference=" + component.getURI() + + "#reference(" + + componentReference.getName() + + ")"); + synchronized (this) { + ImportReferenceImpl importReference = importReferences.get(endpointDescription); + if (importReference == null) { + importReference = new ImportReferenceImpl(node, serviceReference, endpointDescription); + importReferences.put(endpointDescription, importReference); + } + return importReference.register(); + } + } else { + return null; + } + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + return new ImportRegistrationImpl(null, e); + } + } + + public void unimportService(ImportRegistration importRegistration) { + if (importRegistration != null) { + importRegistration.close(); + } + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/RemoteServiceAdminImpl.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/RemoteServiceAdminImpl.java new file mode 100644 index 0000000000..00eb45f3c1 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/RemoteServiceAdminImpl.java @@ -0,0 +1,346 @@ +/* + * 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.tuscany.sca.osgi.remoteserviceadmin.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.core.UtilityExtensionPoint; +import org.apache.tuscany.sca.definitions.Definitions; +import org.apache.tuscany.sca.deployment.Deployer; +import org.apache.tuscany.sca.policy.BindingType; +import org.apache.tuscany.sca.policy.Intent; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedService; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventAdmin; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.ExportReference; +import org.osgi.service.remoteserviceadmin.ExportRegistration; +import org.osgi.service.remoteserviceadmin.ImportReference; +import org.osgi.service.remoteserviceadmin.ImportRegistration; +import org.osgi.service.remoteserviceadmin.RemoteConstants; +import org.osgi.service.remoteserviceadmin.RemoteServiceAdmin; +import org.osgi.service.remoteserviceadmin.RemoteServiceAdminEvent; +import org.osgi.service.remoteserviceadmin.RemoteServiceAdminListener; +import org.osgi.util.tracker.ServiceTracker; + +/** + * SCA Implementation of {@link RemoteServiceAdmin} + */ +public class RemoteServiceAdminImpl implements RemoteServiceAdmin, ManagedService { + private BundleContext context; + private ServiceRegistration registration; + private ServiceRegistration managedService; + private ServiceTracker listeners; + + private OSGiServiceExporter exporter; + private OSGiServiceImporter importer; + + private Collection<ImportRegistration> importRegistrations = new ArrayList<ImportRegistration>(); + private Collection<ExportRegistration> exportedRegistrations = new ArrayList<ExportRegistration>(); + + public RemoteServiceAdminImpl(BundleContext context) { + this.context = context; + } + + public void start() { + this.exporter = new OSGiServiceExporter(context); + this.importer = new OSGiServiceImporter(context); + exporter.start(); + importer.start(); + Hashtable<String, Object> props = new Hashtable<String, Object>(); + props.put(RemoteConstants.REMOTE_CONFIGS_SUPPORTED, new String[] {"org.osgi.sca"}); + + ExtensionPointRegistry registry = exporter.getExtensionPointRegistry(); + UtilityExtensionPoint utilities = registry.getExtensionPoint(UtilityExtensionPoint.class); + Deployer deployer = utilities.getUtility(Deployer.class); + Definitions definitions = deployer.getSystemDefinitions(); + + String[] intents = new String[definitions.getIntents().size()]; + int i = 0; + for (Intent intent : definitions.getIntents()) { + intents[i++] = intent.toString(); + } + + String[] bindingTypes = new String[definitions.getBindingTypes().size()]; + i = 0; + for (BindingType bindingType : definitions.getBindingTypes()) { + bindingTypes[i++] = bindingType.getType().toString(); + } + + // FIXME: We should ask SCA domain for the supported intents + props.put(RemoteConstants.REMOTE_INTENTS_SUPPORTED, intents); + // FIXME: We should ask SCA domain for the supported binding types + props.put("org.osgi.sca.binding.types", bindingTypes); + registration = context.registerService(RemoteServiceAdmin.class.getName(), this, props); + + props = new Hashtable<String, Object>(); + props.put(Constants.SERVICE_PID, RemoteServiceAdminImpl.class.getName()); + managedService = context.registerService(ManagedService.class.getName(), this, props); + listeners = new ServiceTracker(this.context, RemoteServiceAdminListener.class.getName(), null); + listeners.open(); + } + + public void stop() { + if (registration != null) { + try { + registration.unregister(); + } catch (IllegalStateException e) { + // The service has been unregistered, ignore it + } + registration = null; + } + if (managedService != null) { + try { + managedService.unregister(); + } catch (IllegalStateException e) { + // The service has been unregistered, ignore it + } + managedService = null; + } + if (listeners != null) { + listeners.close(); + listeners = null; + } + for (ExportRegistration exportRegistration : exportedRegistrations) { + exportRegistration.close(); + } + exportedRegistrations.clear(); + for (ImportRegistration importRegistration : importRegistrations) { + importRegistration.close(); + } + importRegistrations.clear(); + if (importer != null) { + importer.stop(); + importer = null; + } + if (exporter != null) { + exporter.stop(); + exporter = null; + } + } + + /** + * @see org.osgi.remoteserviceadmin.RemoteServiceAdmin#exportService(org.osgi.framework.ServiceReference, + * java.util.Map) + */ + public List<ExportRegistration> exportService(ServiceReference ref, Map properties) { + List<ExportRegistration> exportRegistrations = exporter.exportService(ref, properties); + if (exportRegistrations != null) { + exportRegistrations.addAll(exportedRegistrations); + fireExportEvents(ref.getBundle(), exportRegistrations); + } + return exportRegistrations; + } + + private void fireExportEvents(Bundle source, List<ExportRegistration> exportRegistrations) { + for (ExportRegistration registration : exportRegistrations) { + RemoteServiceAdminEvent rsaEvent = + new RemoteServiceAdminEvent(RemoteServiceAdminEvent.EXPORT_REGISTRATION, source, registration + .getExportReference(), registration.getException()); + EventAdmin eventAdmin = getEventAdmin(); + if (eventAdmin != null) { + eventAdmin.postEvent(wrap(rsaEvent)); + } + for (Object listener : listeners.getServices()) { + RemoteServiceAdminListener rsaListener = (RemoteServiceAdminListener)listener; + rsaListener.remoteAdminEvent(rsaEvent); + } + } + } + + private EventAdmin getEventAdmin() { + ServiceReference reference = context.getServiceReference(EventAdmin.class.getName()); + if (reference == null) { + return null; + } else { + return (EventAdmin)context.getService(reference); + } + } + + private Event wrap(RemoteServiceAdminEvent rsaEvent) { + int type = rsaEvent.getType(); + String eventType = null; + switch (type) { + case RemoteServiceAdminEvent.EXPORT_ERROR: + eventType = "EXPORT_ERROR"; + break; + case RemoteServiceAdminEvent.EXPORT_REGISTRATION: + eventType = "EXPORT_REGISTRATION"; + break; + case RemoteServiceAdminEvent.EXPORT_UNREGISTRATION: + eventType = "EXPORT_UNREGISTRATION"; + break; + case RemoteServiceAdminEvent.EXPORT_WARNING: + eventType = "EXPORT_WARNING"; + break; + case RemoteServiceAdminEvent.IMPORT_ERROR: + eventType = "IMPORT_ERROR"; + break; + case RemoteServiceAdminEvent.IMPORT_REGISTRATION: + eventType = "IMPORT_REGISTRATION"; + break; + case RemoteServiceAdminEvent.IMPORT_UNREGISTRATION: + eventType = "EXPORT_ERROR"; + break; + case RemoteServiceAdminEvent.IMPORT_WARNING: + eventType = "IMPORT_UNREGISTRATION"; + break; + } + String topic = "org/osgi/service/remoteserviceadmin/" + eventType; + Map<String, Object> props = new HashMap<String, Object>(); + /* + * <ul> + <li>bundle – (Bundle) The Remote Service Admin bundle + <li>bundle-id – (Long) The id of the Blueprint bundle. + <li>bundle-symbolicname – (String) The Bundle Symbolic Name of the + Remote Service Admin bundle. + <li>bundle-version - (Version) The version of the Blueprint bundle. + <li>cause – The exception, if present. + <li>import.registration – An imported endpoint, if present + <li>export.registration – An exported endpoint, if present + <li>service.remote.id – Remote service UUID, if present + <li>service.remote.uuid – Remote service UUID, if present + <li>service.remote.uri – (String) The URI of the endpoint, if present + <li>objectClass – (String[]) The interface names, if present + <li>service.imported.configs – (String+) The configuration types of the + imported services, if present + <li>timestamp – (Long) The time when the event occurred + <li>event – (RemoteServiceAdminEvent) The RemoteServiceAdminEvent + object that caused this event. + </ul> + */ + Bundle rsaBundle = context.getBundle(); + props.put("bundle", rsaBundle); + props.put("bundle-id", rsaBundle.getBundleId()); + props.put("bundle-symbolicname", rsaBundle.getSymbolicName()); + props.put("bundle-version", rsaBundle.getHeaders().get(Constants.BUNDLE_VERSION)); + props.put("cause", rsaEvent.getException()); + props.put("import.registration", rsaEvent.getImportReference()); + props.put("export.registration", rsaEvent.getExportReference()); + EndpointDescription ep = null; + if (rsaEvent.getImportReference() != null) { + ep = rsaEvent.getImportReference().getImportedEndpoint(); + } + if (rsaEvent.getExportReference() != null) { + ep = rsaEvent.getExportReference().getExportedEndpoint(); + } + + if (ep != null) { + props.put("endpoint.service.id", ep.getServiceId()); + props.put("endpoint.framework.uuid", ep.getFrameworkUUID()); + props.put("endpoint.id", ep.getId()); + props.put("objectClass", ep.getInterfaces()); + props.put("service.imported.configs", ep.getConfigurationTypes()); + Object bindings = ep.getProperties().get("org.osgi.sca.bindings"); + if (bindings != null) { + props.put("org.osgi.sca.bindings", bindings); + } + Object config = ep.getProperties().get("org.osgi.sca.config.url"); + if (config != null) { + props.put("org.osgi.sca.config.url", config); + } + config = ep.getProperties().get("org.osgi.sca.config.xml"); + if (config != null) { + props.put("org.osgi.sca.config.xml", config); + } } + props.put("timestamp", Long.valueOf(System.currentTimeMillis())); + props.put("event", rsaEvent); + return new Event(topic, props); + } + + private void fireImportEvents(Bundle source, ImportRegistration registration) { + RemoteServiceAdminEvent rsaEvent = + new RemoteServiceAdminEvent(RemoteServiceAdminEvent.IMPORT_REGISTRATION, source, registration + .getImportReference(), registration.getException()); + EventAdmin eventAdmin = getEventAdmin(); + if (eventAdmin != null) { + eventAdmin.postEvent(wrap(rsaEvent)); + } + for (Object listener : listeners.getServices()) { + RemoteServiceAdminListener rsaListener = (RemoteServiceAdminListener)listener; + rsaListener.remoteAdminEvent(rsaEvent); + } + } + + /** + * @see org.osgi.remoteserviceadmin.RemoteServiceAdmin#getExportedServices() + */ + public Collection<ExportReference> getExportedServices() { + Collection<ExportReference> exportedServices = new ArrayList<ExportReference>(); + for (ExportRegistration registration : exportedRegistrations) { + exportedServices.add(registration.getExportReference()); + } + return exportedServices; + } + + /** + * @see org.osgi.remoteserviceadmin.RemoteServiceAdmin#getImportedEndpoints() + */ + public Collection<ImportReference> getImportedEndpoints() { + Collection<ImportReference> importedEndpoints = new ArrayList<ImportReference>(); + for (ImportRegistration registration : importRegistrations) { + importedEndpoints.add(registration.getImportReference()); + } + return importedEndpoints; + } + + /** + * @see org.osgi.remoteserviceadmin.RemoteServiceAdmin#importService(org.apache.tuscany.sca.dosgi.discovery.EndpointDescription) + */ + public ImportRegistration importService(EndpointDescription endpoint) { + Bundle bundle = (Bundle)endpoint.getProperties().get(Bundle.class.getName()); + ImportRegistration importReg = importer.importService(bundle, endpoint); + if (importReg != null) { + fireImportEvents(bundle, importReg); + importRegistrations.add(importReg); + } + return importReg; + } + + public synchronized void updated(Dictionary props) throws ConfigurationException { + if (props == null) { + // It can be null in Apache Felix + return; + } + String domainRegistry = (String)props.get("org.osgi.sca.domain.registry"); + String domainURI = (String)props.get("org.osgi.sca.domain.uri"); + if (domainRegistry != null) { + exporter.setDomainRegistry(domainRegistry); + importer.setDomainRegistry(domainRegistry); + } + if (domainURI != null) { + exporter.setDomainURI(domainURI); + importer.setDomainURI(domainURI); + } + } +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/TopologyManagerImpl.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/TopologyManagerImpl.java new file mode 100644 index 0000000000..a3f3636cce --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/TopologyManagerImpl.java @@ -0,0 +1,400 @@ +/* + * 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.tuscany.sca.osgi.remoteserviceadmin.impl; + +import static org.apache.tuscany.sca.implementation.osgi.OSGiProperty.SERVICE_EXPORTED_INTERFACES; +import static org.osgi.service.remoteserviceadmin.RemoteConstants.SERVICE_EXPORTED_CONFIGS; +import static org.osgi.service.remoteserviceadmin.RemoteConstants.SERVICE_IMPORTED; + +import java.util.Collection; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.tuscany.sca.common.java.collection.CollectionMap; +import org.apache.tuscany.sca.core.LifeCycleListener; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.EndpointMatcher.ImportAction; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.Filter; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.framework.hooks.service.ListenerHook; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.EndpointListener; +import org.osgi.service.remoteserviceadmin.ExportRegistration; +import org.osgi.service.remoteserviceadmin.ImportRegistration; +import org.osgi.service.remoteserviceadmin.RemoteServiceAdmin; +import org.osgi.service.remoteserviceadmin.RemoteServiceAdminEvent; +import org.osgi.service.remoteserviceadmin.RemoteServiceAdminListener; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +/** + * Implementation of Remote Controller + */ +public class TopologyManagerImpl implements ListenerHook, RemoteServiceAdminListener, EndpointListener, + ServiceTrackerCustomizer, LifeCycleListener /*, EventHook */{ + private final static Logger logger = Logger.getLogger(TopologyManagerImpl.class.getName()); + public final static String ENDPOINT_LOCAL = "service.local"; + + private BundleContext context; + private ServiceTracker remoteAdmins; + + private volatile ServiceRegistration registration; + private ServiceRegistration endpointListener; + + private ServiceTracker remotableServices; + + private EndpointMatcher endpointMatcher; + + private CollectionMap<ServiceReference, ExportRegistration> exportedServices = + new CollectionMap<ServiceReference, ExportRegistration>(); + private CollectionMap<ImportKey, ImportRegistration> importedServices = + new CollectionMap<ImportKey, ImportRegistration>(); + + private Filter remotableServiceFilter; + + public TopologyManagerImpl(BundleContext context) { + this.context = context; + this.endpointMatcher = new EndpointMatcher(context); + } + + public void start() { + String filter = + "(& (!(" + SERVICE_IMPORTED + + "=*)) (" + + SERVICE_EXPORTED_INTERFACES + + "=*) (" + + SERVICE_EXPORTED_CONFIGS + + "=org.osgi.sca) )"; + try { + remotableServiceFilter = context.createFilter(filter); + } catch (InvalidSyntaxException e) { + // Ignore + } + + endpointListener = context.registerService(EndpointListener.class.getName(), this, null); + remoteAdmins = new ServiceTracker(this.context, RemoteServiceAdmin.class.getName(), null); + remoteAdmins.open(); + + // DO NOT register EventHook.class.getName() as it cannot report existing services + String interfaceNames[] = + new String[] {ListenerHook.class.getName(), RemoteServiceAdminListener.class.getName()}; + // The registration will trigger the added() method before registration is assigned + registration = context.registerService(interfaceNames, this, null); + + remotableServices = new ServiceTracker(context, remotableServiceFilter, this); + remotableServices.open(true); + + Thread thread = new Thread(new ImportTask()); + thread.start(); + } + + public Object addingService(ServiceReference reference) { + exportService(reference); + return reference.getBundle().getBundleContext().getService(reference); + } + + public void modifiedService(ServiceReference reference, Object service) { + unexportService(reference); + exportService(reference); + } + + public void removedService(ServiceReference reference, Object service) { + unexportService(reference); + } + + private void unexportService(ServiceReference reference) { + // Call remote admin to unexport the service + Collection<ExportRegistration> exportRegistrations = exportedServices.get(reference); + if (exportRegistrations != null) { + for (Iterator<ExportRegistration> i = exportRegistrations.iterator(); i.hasNext();) { + ExportRegistration exported = i.next(); + exported.close(); + i.remove(); + } + } + } + + private void exportService(ServiceReference reference) { + // Call remote admin to export the service + Object[] admins = remoteAdmins.getServices(); + if (admins == null) { + // Ignore + logger.warning("No RemoteAdmin services are available."); + } else { + for (Object ra : admins) { + RemoteServiceAdmin remoteAdmin = (RemoteServiceAdmin)ra; + Collection<ExportRegistration> exportRegistrations = remoteAdmin.exportService(reference, null); + if (exportRegistrations != null && !exportRegistrations.isEmpty()) { + exportedServices.putValues(reference, exportRegistrations); + } + } + } + } + + /** + * @see org.osgi.framework.hooks.service.ListenerHook#added(java.util.Collection) + */ + public void added(Collection listeners) { + try { + synchronized (endpointMatcher) { + Collection<String> oldFilters = endpointMatcher.getFilters(); + Collection<String> newFilters = endpointMatcher.added(listeners); + if (!OSGiHelper.getAddedItems(oldFilters, newFilters).isEmpty()) { + updateEndpointListenerScope(newFilters); + } + } + } catch (Throwable e) { + logger.log(Level.SEVERE, e.getMessage(), e); + if (e instanceof Error) { + throw (Error)e; + } else if (e instanceof RuntimeException) { + throw (RuntimeException)e; + } else { + // Should not happen + throw new RuntimeException(e); + } + } + } + + private void updateEndpointListenerScope(Collection<String> filters) { + Dictionary<String, Object> props = new Hashtable<String, Object>(); + props.put(ENDPOINT_LISTENER_SCOPE, filters); + endpointListener.setProperties(props); + } + + /** + * @see org.osgi.framework.hooks.service.ListenerHook#removed(java.util.Collection) + */ + public void removed(Collection listeners) { + try { + synchronized (endpointMatcher) { + Collection<String> oldFilters = endpointMatcher.getFilters(); + Collection<String> newFilters = endpointMatcher.removed(listeners); + if (!OSGiHelper.getRemovedItems(oldFilters, newFilters).isEmpty()) { + updateEndpointListenerScope(newFilters); + } + } + } catch (Throwable e) { + logger.log(Level.SEVERE, e.getMessage(), e); + if (e instanceof Error) { + throw (Error)e; + } else if (e instanceof RuntimeException) { + throw (RuntimeException)e; + } else { + // Should not happen + throw new RuntimeException(e); + } + } + } + + /** + * @see org.apache.tuscany.sca.osgi.service.remoteadmin.RemoteAdminListener#remoteAdminEvent(org.apache.tuscany.sca.osgi.service.remoteadmin.RemoteAdminEvent) + */ + public void remoteAdminEvent(RemoteServiceAdminEvent event) { + switch (event.getType()) { + case RemoteServiceAdminEvent.EXPORT_ERROR: + case RemoteServiceAdminEvent.EXPORT_REGISTRATION: + case RemoteServiceAdminEvent.EXPORT_UNREGISTRATION: + case RemoteServiceAdminEvent.EXPORT_WARNING: + break; + case RemoteServiceAdminEvent.IMPORT_ERROR: + case RemoteServiceAdminEvent.IMPORT_REGISTRATION: + case RemoteServiceAdminEvent.IMPORT_UNREGISTRATION: + case RemoteServiceAdminEvent.IMPORT_WARNING: + break; + } + } + + /** + * @see org.osgi.remoteserviceadmin.EndpointListener#addEndpoint(org.osgi.service.remoteserviceadmin.EndpointDescription, + * java.lang.String) + */ + public void endpointAdded(EndpointDescription endpoint, String matchedFilter) { + endpointMatcher.added(endpoint, matchedFilter); + // importService(endpoint, matchedFilter); + } + + /** + * @see org.osgi.remoteserviceadmin.EndpointListener#removeEndpoint(org.osgi.service.remoteserviceadmin.EndpointDescription) + */ + public void endpointRemoved(EndpointDescription endpoint, String matchedFilter) { + endpointMatcher.removed(endpoint, matchedFilter); + // unimportService(endpoint); + } + + private void importService(EndpointDescription endpoint, String matchedFilter) { + Object[] admins = remoteAdmins.getServices(); + if (admins == null) { + logger.warning("No Remote Service Admin services are available."); + return; + } + + CollectionMap<Class<?>, ListenerInfo> interfaceToListeners = + endpointMatcher.groupListeners(endpoint, matchedFilter); + for (Map.Entry<Class<?>, Collection<ListenerInfo>> e : interfaceToListeners.entrySet()) { + Class<?> interfaceClass = e.getKey(); + Collection<ListenerInfo> listeners = e.getValue(); + // Get a listener + ListenerInfo listener = listeners.iterator().next(); + Bundle bundle = listener.getBundleContext().getBundle(); + if (bundle.getBundleId() == 0L) { + // Skip system bundles + continue; + } + try { + Filter filter = listener.getBundleContext().createFilter(matchedFilter); + if (!filter.match(new Hashtable<String, Object>(endpoint.getProperties()))) { + continue; + } + } catch (InvalidSyntaxException ex) { + logger.log(Level.SEVERE, ex.getMessage(), ex); + continue; + } + + Map<String, Object> props = new HashMap<String, Object>(endpoint.getProperties()); + props.put(Bundle.class.getName(), bundle); + props.put(Constants.OBJECTCLASS, new String[] {interfaceClass.getName()}); + EndpointDescription description = new EndpointDescription(props); + + if (admins != null) { + for (Object ra : admins) { + RemoteServiceAdmin remoteAdmin = (RemoteServiceAdmin)ra; + ImportRegistration importRegistration = remoteAdmin.importService(description); + if (importRegistration != null) { + importedServices.putValue(new ImportKey(description, listener), importRegistration); + } + } + } + } + } + + private void unimportService(EndpointDescription endpoint, ListenerInfo listenerInfo) { + // Call remote admin to unimport the service + Collection<ImportRegistration> importRegistrations = + importedServices.get(new ImportKey(endpoint, listenerInfo)); + if (importRegistrations != null) { + for (Iterator<ImportRegistration> i = importRegistrations.iterator(); i.hasNext();) { + ImportRegistration imported = i.next(); + imported.close(); + i.remove(); + } + } + } + + public void stop() { + remotableServices.close(); + + if (registration != null) { + try { + registration.unregister(); + } catch (IllegalStateException e) { + // The service has been unregistered, ignore it + } + registration = null; + } + if (remoteAdmins != null) { + remoteAdmins.close(); + remoteAdmins = null; + } + if (endpointMatcher != null) { + endpointMatcher.clear(); + } + } + + private class ImportTask implements Runnable { + public void run() { + while (registration != null) { + BlockingQueue<EndpointMatcher.ImportAction> queue = endpointMatcher.getImportQueue(); + ImportAction action = null; + try { + action = queue.poll(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + // Ignore + } + if (action != null) { + if (action.type == ImportAction.Type.Add) { + importService(action.endpointDescription, action.listenerInfo.getFilter()); + } else if (action.type == ImportAction.Type.Remove) { + unimportService(action.endpointDescription, action.listenerInfo); + } + } + } + } + } + + private static class ImportKey { + private EndpointDescription endpointDescription; + + /** + * @param endpointDescription + * @param listenerInfo + */ + private ImportKey(EndpointDescription endpointDescription, ListenerInfo listenerInfo) { + super(); + this.endpointDescription = endpointDescription; + this.listenerInfo = listenerInfo; + } + + private ListenerInfo listenerInfo; + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((endpointDescription == null) ? 0 : endpointDescription.hashCode()); + result = prime * result + ((listenerInfo == null) ? 0 : listenerInfo.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ImportKey other = (ImportKey)obj; + if (endpointDescription == null) { + if (other.endpointDescription != null) + return false; + } else if (!endpointDescription.equals(other.endpointDescription)) + return false; + if (listenerInfo == null) { + if (other.listenerInfo != null) + return false; + } else if (!listenerInfo.equals(other.listenerInfo)) + return false; + return true; + } + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/AbstractDiscoveryService.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/AbstractDiscoveryService.java new file mode 100644 index 0000000000..fd57dbded8 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/AbstractDiscoveryService.java @@ -0,0 +1,265 @@ +/* + * 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.tuscany.sca.osgi.service.discovery.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.core.LifeCycleListener; +import org.apache.tuscany.sca.node.NodeFactory; +import org.apache.tuscany.sca.node.impl.NodeFactoryImpl; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.OSGiHelper; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.Filter; +import org.osgi.framework.ServiceReference; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.EndpointListener; +import org.osgi.service.remoteserviceadmin.RemoteConstants; +import org.osgi.util.tracker.ServiceTracker; + +/** + * + */ +public abstract class AbstractDiscoveryService implements Discovery, LifeCycleListener { + protected final static int ADDED = 0x1; + protected final static int REMOVED = 0x2; + protected final static int MODIFIED = 0x4; + + protected final static Logger logger = Logger.getLogger(AbstractDiscoveryService.class.getName()); + + protected BundleContext context; + protected ExtensionPointRegistry registry; + // private WorkScheduler workScheduler; + + private Map<EndpointListener, Collection<String>> listenersToFilters = + new ConcurrentHashMap<EndpointListener, Collection<String>>(); + + protected Map<EndpointDescription, Bundle> endpointDescriptions = + new ConcurrentHashMap<EndpointDescription, Bundle>(); + private ServiceTracker trackerTracker; + + public AbstractDiscoveryService(BundleContext context) { + super(); + this.context = context; + } + + public void start() { + getExtensionPointRegistry(); + // UtilityExtensionPoint utilityExtensionPoint = registry.getExtensionPoint(UtilityExtensionPoint.class); + // this.workScheduler = utilityExtensionPoint.getUtility(WorkScheduler.class); + + // track the registration of EndpointListener + trackerTracker = new ServiceTracker(this.context, EndpointListener.class.getName(), null) { + public Object addingService(ServiceReference reference) { + Object result = super.addingService(reference); + cacheTracker(reference, result); + return result; + } + + public void modifiedService(ServiceReference reference, Object service) { + super.modifiedService(reference, service); + updateTracker(reference, service); + } + + public void removedService(ServiceReference reference, Object service) { + super.removedService(reference, service); + clearTracker(service); + } + }; + + trackerTracker.open(); + } + + public void stop() { + trackerTracker.close(); + } + + protected ExtensionPointRegistry getExtensionPointRegistry() { + NodeFactoryImpl factory = (NodeFactoryImpl)NodeFactory.getInstance(); + factory.init(); + ServiceTracker tracker = new ServiceTracker(context, ExtensionPointRegistry.class.getName(), null); + tracker.open(); + // tracker.waitForService(1000); + registry = (ExtensionPointRegistry)tracker.getService(); + tracker.close(); + return registry; + } + + protected Dictionary<String, Object> getProperties() { + Dictionary headers = context.getBundle().getHeaders(); + Hashtable<String, Object> props = new Hashtable<String, Object>(); + props.put(PRODUCT_NAME, "Apache Tuscany SCA"); + props.put(PRODUCT_VERSION, headers.get(Constants.BUNDLE_VERSION)); + props.put(VENDOR_NAME, headers.get(Constants.BUNDLE_VENDOR)); + // props.put(SUPPORTED_PROTOCOLS, new String[] {"local", "org.osgi.sca"}); + return props; + } + + private void cacheTracker(ServiceReference reference, Object service) { + if (service instanceof EndpointListener) { + EndpointListener listener = (EndpointListener)service; + Collection<String> filters = null; + synchronized (this) { + filters = addTracker(reference, listener, EndpointListener.ENDPOINT_LISTENER_SCOPE); + // Take a snapshot of the endpoints + triggerCallbacks(null, filters, listener); + } + } + } + + private void clearTracker(Object service) { + if (service instanceof EndpointListener) { + synchronized (this) { + removeTracker((EndpointListener)service); + } + } + } + + private void updateTracker(ServiceReference reference, Object service) { + if (service instanceof EndpointListener) { + EndpointListener listener = (EndpointListener)service; + Collection<String> oldFilters = null; + Collection<String> newFilters = null; + synchronized (this) { + if (logger.isLoggable(Level.FINE)) { + logger.fine("updating listener: " + listener); + } + oldFilters = removeTracker(listener); + newFilters = addTracker(reference, listener, EndpointListener.ENDPOINT_LISTENER_SCOPE); + triggerCallbacks(oldFilters, newFilters, listener); + } + } + } + + private void triggerCallbacks(Collection<String> oldInterest, + Collection<String> newInterest, + EndpointListener listener) { + // compute delta between old & new interfaces/filters and + // trigger callbacks for any entries in servicesInfo that + // match any *additional* interface/filters + Collection<String> deltaInterest = getDelta(oldInterest, newInterest); + + Iterator<String> i = deltaInterest.iterator(); + while (i.hasNext()) { + String next = i.next(); + for (EndpointDescription sd : endpointDescriptions.keySet()) { + triggerCallbacks(listener, next, sd, ADDED); + } + } + // Find removed filters + deltaInterest = getDelta(newInterest, oldInterest); + + i = deltaInterest.iterator(); + while (i.hasNext()) { + String next = i.next(); + for (EndpointDescription sd : endpointDescriptions.keySet()) { + triggerCallbacks(listener, next, sd, REMOVED); + } + } + } + + private Collection<String> getDelta(Collection<String> oldInterest, Collection<String> newInterest) { + if (newInterest == null) { + newInterest = Collections.emptySet(); + } + + Collection<String> deltaInterest = new ArrayList<String>(newInterest); + if (oldInterest == null) { + oldInterest = Collections.emptySet(); + } + deltaInterest.removeAll(oldInterest); + return deltaInterest; + } + + /** + * Notify the endpoint listener + * @param listener + * @param matchedFilter + * @param endpoint + * @param type + */ + private static void notify(EndpointListener listener, String matchedFilter, EndpointDescription endpoint, int type) { + switch (type) { + case ADDED: + listener.endpointAdded(endpoint, matchedFilter); + break; + case REMOVED: + listener.endpointRemoved(endpoint, matchedFilter); + break; + case MODIFIED: + listener.endpointRemoved(endpoint, matchedFilter); + listener.endpointAdded(endpoint, matchedFilter); + break; + } + } + + private void triggerCallbacks(EndpointListener listener, + String matchedFilter, + EndpointDescription endpoint, + int type) { + // workScheduler.scheduleWork(new Notifier(listener, matchedFilter, endpoint, type)); + notify(listener, matchedFilter, endpoint, type); + } + + private boolean filterMatches(String filterValue, EndpointDescription sd) { + Filter filter = OSGiHelper.createFilter(context, filterValue); + Hashtable<String, Object> props = new Hashtable<String, Object>(sd.getProperties()); + // Add two faked properties to make the filter match + props.put(Constants.OBJECTCLASS, sd.getInterfaces()); + props.put(RemoteConstants.SERVICE_IMPORTED, "true"); + return filter != null ? filter.match(props) : false; + } + + private Collection<String> removeTracker(EndpointListener listener) { + return listenersToFilters.remove(listener); + } + + private Collection<String> addTracker(ServiceReference reference, EndpointListener listener, String property) { + Collection<String> collection = OSGiHelper.getStringCollection(reference, property); + if (collection != null && !collection.isEmpty()) { + listenersToFilters.put(listener, new ArrayList<String>(collection)); + } + return collection; + } + + protected void endpointChanged(EndpointDescription sd, int type) { + synchronized (this) { + for (Map.Entry<EndpointListener, Collection<String>> entry : listenersToFilters.entrySet()) { + for (String filter : entry.getValue()) { + if (filterMatches(filter, sd)) { + triggerCallbacks(entry.getKey(), filter, sd, type); + } + } + } + } + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/Discovery.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/Discovery.java new file mode 100644 index 0000000000..3ad1e53fee --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/Discovery.java @@ -0,0 +1,66 @@ +/* + * 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.tuscany.sca.osgi.service.discovery.impl; + +/** + * Every Discovery Provider registers a service implementing this interface. + * This service is registered with extra properties identified at the beginning + * of this interface to denote the name of the product providing Discovery + * functionality, its version, vendor, used protocols etc.. + * <p> + * Discovery allows to publish services exposed for remote access as well as to + * search for remote services. + * <p> + * Discovery service implementations usually rely on some discovery protocols or + * other information distribution means. + * + * @ThreadSafe + */ +public interface Discovery { + + /** + * ServiceRegistration property for the name of the Discovery product. + * <p> + * Value of this property is of type <code>String</code>. + */ + String PRODUCT_NAME = "osgi.remote.discovery.product"; + + /** + * ServiceRegistration property for the version of the Discovery product. + * <p> + * Value of this property is of type <code>String</code>. + */ + String PRODUCT_VERSION = "osgi.remote.discovery.product.version"; + + /** + * ServiceRegistration property for the Discovery product vendor name. + * <p> + * Value of this property is of type <code>String</code>. + */ + String VENDOR_NAME = "osgi.remote.discovery.vendor"; + + /** + * ServiceRegistration property that lists the discovery protocols used by + * this Discovery service. + * <p> + * Value of this property is of type + * <code>Collection (<? extends String>)</code>. + */ + String SUPPORTED_PROTOCOLS = "osgi.remote.discovery.supported_protocols"; +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/DiscoveryActivator.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/DiscoveryActivator.java new file mode 100644 index 0000000000..32bd10b77b --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/DiscoveryActivator.java @@ -0,0 +1,57 @@ +/** + * 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.tuscany.sca.osgi.service.discovery.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +public class DiscoveryActivator implements BundleActivator { + private List<AbstractDiscoveryService> discoveryServices = new ArrayList<AbstractDiscoveryService>(); + private List<ServiceRegistration> discoveryServiceRegistrations = new ArrayList<ServiceRegistration>(); + + public void start(BundleContext context) { + discoveryServices.add(new LocalDiscoveryService(context)); + + discoveryServices.add(new DomainDiscoveryService(context)); + + for (AbstractDiscoveryService service : discoveryServices) { + service.start(); + ServiceRegistration registration = + context.registerService(Discovery.class.getName(), service, service.getProperties()); + discoveryServiceRegistrations.add(registration); + } + } + + public void stop(BundleContext context) { + for (ServiceRegistration registration : discoveryServiceRegistrations) { + try { + registration.unregister(); + } catch (IllegalStateException e) { + // The service has been unregistered, ignore it + } + } + for (AbstractDiscoveryService service : discoveryServices) { + service.stop(); + } + } +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/DomainDiscoveryService.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/DomainDiscoveryService.java new file mode 100644 index 0000000000..0cbdda7b56 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/DomainDiscoveryService.java @@ -0,0 +1,140 @@ +/* + * 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.tuscany.sca.osgi.service.discovery.impl; + +import static org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.EndpointHelper.createEndpointDescription; + +import java.util.Dictionary; + +import org.apache.tuscany.sca.assembly.Endpoint; +import org.apache.tuscany.sca.assembly.Implementation; +import org.apache.tuscany.sca.core.LifeCycleListener; +import org.apache.tuscany.sca.implementation.osgi.OSGiImplementation; +import org.apache.tuscany.sca.node.configuration.NodeConfiguration; +import org.apache.tuscany.sca.runtime.DomainRegistryFactory; +import org.apache.tuscany.sca.runtime.EndpointListener; +import org.apache.tuscany.sca.runtime.EndpointRegistry; +import org.apache.tuscany.sca.runtime.ExtensibleDomainRegistryFactory; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.service.remoteserviceadmin.EndpointDescription; + +/** + * Discovery service based on the distributed SCA domain + */ +public class DomainDiscoveryService extends AbstractDiscoveryService implements EndpointListener { + private DomainRegistryFactory domainRegistryFactory; + private EndpointRegistry endpointRegistry; + + public DomainDiscoveryService(BundleContext context) { + super(context); + } + + public void start() { + super.start(); + this.domainRegistryFactory = ExtensibleDomainRegistryFactory.getInstance(registry); + domainRegistryFactory.addListener(this); + + // [rfeng] Starting of the endpoint registry takes a long time and it leaves the bundle + // state to be starting. When the registry is started, remote endpoints come in and that + // triggers the classloading from this bundle. + Thread thread = new Thread() { + public void run() { + startEndpointRegistry(); + } + }; + thread.start(); + } + + private synchronized void startEndpointRegistry() { + // The following code forced the start() of the domain registry in absense of services + String domainRegistry = context.getProperty("org.osgi.sca.domain.registry"); + if (domainRegistry == null) { + domainRegistry = NodeConfiguration.DEFAULT_DOMAIN_REGISTRY_URI; + } + String domainURI = context.getProperty("org.osgi.sca.domain.uri"); + if (domainURI == null) { + domainURI = NodeConfiguration.DEFAULT_DOMAIN_URI; + } + if (domainRegistry != null) { + endpointRegistry = domainRegistryFactory.getEndpointRegistry(domainRegistry, domainURI); + } + } + + public void endpointAdded(Endpoint endpoint) { + Implementation impl = endpoint.getComponent().getImplementation(); + + /* + if (!(impl instanceof OSGiImplementation)) { + return; + } + */ + + BundleContext bundleContext = null; + // Remote endpoint doesn't have a bundle + if (!endpoint.isRemote()) { + OSGiImplementation osgiImpl = (OSGiImplementation)impl; + Bundle bundle = osgiImpl.getBundle(); + bundleContext = bundle != null ? bundle.getBundleContext() : null; + } + + // Notify the endpoint listeners + EndpointDescription description = createEndpointDescription(bundleContext, endpoint); + // Set the owning bundle to runtime bundle to avoid NPE + synchronized (this) { + endpointDescriptions.put(description, context.getBundle()); + endpointChanged(description, ADDED); + } + } + + public void endpointRemoved(Endpoint endpoint) { + EndpointDescription description = createEndpointDescription(context, endpoint); + synchronized (this) { + endpointDescriptions.remove(description); + endpointChanged(description, REMOVED); + } + } + + public void endpointUpdated(Endpoint oldEndpoint, Endpoint newEndpoint) { + // FIXME: This is a quick and dirty way for the update + endpointRemoved(oldEndpoint); + endpointAdded(newEndpoint); + } + + public void stop() { + if (domainRegistryFactory != null) { + domainRegistryFactory.removeListener(this); + if (endpointRegistry instanceof LifeCycleListener) { + ((LifeCycleListener)endpointRegistry).stop(); + } + domainRegistryFactory = null; + endpointRegistry = null; + super.stop(); + } + } + + @Override + protected Dictionary<String, Object> getProperties() { + Dictionary<String, Object> props = super.getProperties(); + props.put(SUPPORTED_PROTOCOLS, new String[] {"org.osgi.sca"}); + return props; + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/LocalDiscoveryService.java b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/LocalDiscoveryService.java new file mode 100644 index 0000000000..dc9ae096dd --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1-RC1/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/LocalDiscoveryService.java @@ -0,0 +1,262 @@ +/** + * 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.tuscany.sca.osgi.service.discovery.impl; + +import static org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.OSGiHelper.getConfiguration; +import static org.osgi.service.remoteserviceadmin.RemoteConstants.ENDPOINT_FRAMEWORK_UUID; +import static org.osgi.service.remoteserviceadmin.RemoteConstants.ENDPOINT_ID; +import static org.osgi.service.remoteserviceadmin.RemoteConstants.ENDPOINT_SERVICE_ID; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; +import java.util.Map.Entry; +import java.util.logging.Level; + +import javax.xml.namespace.QName; + +import org.apache.tuscany.sca.assembly.Binding; +import org.apache.tuscany.sca.core.UtilityExtensionPoint; +import org.apache.tuscany.sca.deployment.Deployer; +import org.apache.tuscany.sca.implementation.osgi.SCAConfig; +import org.apache.tuscany.sca.implementation.osgi.ServiceDescription; +import org.apache.tuscany.sca.implementation.osgi.ServiceDescriptions; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.OSGiHelper; +import org.apache.tuscany.sca.policy.Intent; +import org.apache.tuscany.sca.policy.PolicySet; +import org.oasisopen.sca.ServiceRuntimeException; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.Constants; +import org.osgi.framework.Filter; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.util.tracker.BundleTracker; +import org.osgi.util.tracker.BundleTrackerCustomizer; +import org.osgi.util.tracker.ServiceTracker; + +public class LocalDiscoveryService extends AbstractDiscoveryService implements BundleTrackerCustomizer { + private Deployer deployer; + private BundleTracker bundleTracker; + private Collection<ExtenderConfiguration> extenders = new ArrayList<ExtenderConfiguration>(); + + public LocalDiscoveryService(BundleContext context) { + super(context); + } + + public void start() { + super.start(); + + UtilityExtensionPoint utilities = this.registry.getExtensionPoint(UtilityExtensionPoint.class); + this.deployer = utilities.getUtility(Deployer.class); + bundleTracker = new BundleTracker(context, Bundle.ACTIVE | Bundle.STARTING, this); + bundleTracker.open(); + } + + public static ServiceTracker getTracker(BundleContext context) { + Filter filter = null; + try { + filter = + context.createFilter("(& (" + Discovery.SUPPORTED_PROTOCOLS + + "=local) (" + + Constants.OBJECTCLASS + + "=" + + Discovery.class.getName() + + "))"); + } catch (InvalidSyntaxException e) { + throw new IllegalArgumentException(e); + } + return new ServiceTracker(context, filter, null); + } + + private EndpointDescription createEndpointDescription(ServiceDescription sd) { + Map<String, Object> props = new HashMap<String, Object>(sd.getProperties()); + props.put(Constants.OBJECTCLASS, sd.getInterfaces().toArray(new String[sd.getInterfaces().size()])); + if (!props.containsKey(ENDPOINT_SERVICE_ID)) { + props.put(ENDPOINT_SERVICE_ID, Long.valueOf(System.currentTimeMillis())); + } + if (!props.containsKey(ENDPOINT_FRAMEWORK_UUID)) { + props.put(ENDPOINT_FRAMEWORK_UUID, OSGiHelper.getFrameworkUUID(context)); + } + if (!props.containsKey(ENDPOINT_ID)) { + props.put(ENDPOINT_ID, UUID.randomUUID().toString()); + } + + EndpointDescription sed = new EndpointDescription(props); + return sed; + } + + private void removeServicesDeclaredInBundle(Bundle bundle) { + for (Iterator<Map.Entry<EndpointDescription, Bundle>> i = endpointDescriptions.entrySet().iterator(); i.hasNext();) { + Entry<EndpointDescription, Bundle> entry = i.next(); + if (entry.getValue().equals(bundle)) { + serviceDescriptionRemoved(entry.getKey()); + i.remove(); + } + } + } + + private void serviceDescriptionAdded(EndpointDescription endpointDescription) { + endpointChanged(endpointDescription, ADDED); + } + + private void serviceDescriptionRemoved(EndpointDescription endpointDescription) { + endpointChanged(endpointDescription, REMOVED); + } + + public void stop() { + if (bundleTracker != null) { + bundleTracker.close(); + } + super.stop(); + } + + public Object addingBundle(Bundle bundle, BundleEvent event) { + if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null || bundle.getBundleId() == 0) { + // Ignore fragments + return null; + } + Collection<URL> scaConfigs = getConfiguration(bundle, "SCA-Configuration", "OSGI-INF/sca-config/*.xml"); + Collection<URL> descriptions = getConfiguration(bundle, "Remote-Service", null); + if (scaConfigs.isEmpty() && descriptions.isEmpty()) { + return null; + } + ExtenderConfiguration extender = new ExtenderConfiguration(); + for (URL url : scaConfigs) { + try { + SCAConfig scaConfig = deployer.loadXMLDocument(url, deployer.createMonitor()); + extender.scaConfigs.add(scaConfig); + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + throw new ServiceRuntimeException(e); + } + } + for (URL url : descriptions) { + try { + ServiceDescriptions sds = deployer.loadXMLDocument(url, deployer.createMonitor()); + extender.remoteServiceDescriptions.add(sds); + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + // throw new ServiceRuntimeException(e); + } + } + + // Add to the extenders before notifying the listeners (the endpoints may references to the config) + ExtenderConfiguration.validate(extenders, extender); + this.extenders.add(extender); + + // Notify + for (ServiceDescriptions sds : extender.getRemoteServiceDescriptions()) { + for (ServiceDescription sd : sds) { + EndpointDescription sed = createEndpointDescription(sd); + endpointDescriptions.put(sed, bundle); + serviceDescriptionAdded(sed); + } + } + + return extender; + } + + public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) { + // STARTING --> ACTIVE + } + + public void removedBundle(Bundle bundle, BundleEvent event, Object object) { + if (object instanceof ExtenderConfiguration) { + extenders.remove((ExtenderConfiguration)object); + removeServicesDeclaredInBundle(bundle); + } + } + + public Collection<ExtenderConfiguration> getConfigurations() { + return extenders; + } + + public static class ExtenderConfiguration { + private Collection<SCAConfig> scaConfigs = new ArrayList<SCAConfig>(); + private Collection<ServiceDescriptions> remoteServiceDescriptions = new ArrayList<ServiceDescriptions>(); + + public Collection<ServiceDescriptions> getRemoteServiceDescriptions() { + return remoteServiceDescriptions; + } + + public Collection<SCAConfig> getSCAConfigs() { + return scaConfigs; + } + + public static void validate(Collection<ExtenderConfiguration> configs, ExtenderConfiguration newConfig) { + Map<QName, Binding> bindings = new HashMap<QName, Binding>(); + Map<QName, Intent> intents = new HashMap<QName, Intent>(); + Map<QName, PolicySet> policySets = new HashMap<QName, PolicySet>(); + + for (ExtenderConfiguration config : configs) { + for (SCAConfig c : config.getSCAConfigs()) { + addBindings(bindings, c); + addIntents(intents, c); + addPolicySets(policySets, c); + } + } + for (SCAConfig c : newConfig.getSCAConfigs()) { + addBindings(bindings, c); + addIntents(intents, c); + addPolicySets(policySets, c); + } + } + + private static void addIntents(Map<QName, Intent> intents, SCAConfig c) { + for (Intent i: c.getIntents()) { + if (intents.put(i.getName(), i) != null) { + throw new ServiceRuntimeException("Duplicate intent: " + i.getName()); + } + } + } + + private static void addPolicySets(Map<QName, PolicySet> policySets, SCAConfig c) { + for (PolicySet ps: c.getPolicySets()) { + if (policySets.put(ps.getName(), ps) != null) { + throw new ServiceRuntimeException("Duplicate policySet: " + ps.getName()); + } + } + } + + private static void addBindings(Map<QName, Binding> bindings, SCAConfig c) { + for (Binding b : c.getBindings()) { + QName name = new QName(c.getTargetNamespace(), b.getName()); + if (bindings.put(name, b) != null) { + throw new ServiceRuntimeException("Duplicate binding: " + name); + } + } + } + + } + + @Override + protected Dictionary<String, Object> getProperties() { + Dictionary<String, Object> props = super.getProperties(); + props.put(SUPPORTED_PROTOCOLS, new String[] {"local"}); + return props; + } + +} |