From 4537071c7caf6b151c6984fad1e11dbf698f50b0 Mon Sep 17 00:00:00 2001 From: antelder Date: Fri, 30 Oct 2009 08:29:42 +0000 Subject: Create 2.0-M4 release branch git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@831226 13f79535-47bb-0310-9956-ffa450edef68 --- .../remoteserviceadmin/EndpointDescription.java | 385 +++++++++ .../osgi/remoteserviceadmin/EndpointListener.java | 112 +++ .../remoteserviceadmin/EndpointPermission.java | 938 +++++++++++++++++++++ .../remoteserviceadmin/ExportRegistration.java | 61 ++ .../remoteserviceadmin/ImportRegistration.java | 60 ++ .../osgi/remoteserviceadmin/RemoteConstants.java | 155 ++++ .../remoteserviceadmin/RemoteServiceAdmin.java | 110 +++ .../RemoteServiceAdminEvent.java | 149 ++++ .../RemoteServiceAdminListener.java | 16 + .../impl/AbstractOSGiServiceHandler.java | 90 ++ .../remoteserviceadmin/impl/EndpointHelper.java | 82 ++ .../impl/EndpointIntrospector.java | 494 +++++++++++ .../impl/ExportRegistrationImpl.java | 89 ++ .../impl/ImportRegistrationImpl.java | 92 ++ .../osgi/remoteserviceadmin/impl/OSGiHelper.java | 135 +++ .../impl/OSGiServiceExporter.java | 108 +++ .../impl/OSGiServiceImporter.java | 92 ++ .../impl/RemoteServiceAdminImpl.java | 284 +++++++ .../impl/TopologyManagerImpl.java | 410 +++++++++ .../discovery/impl/AbstractDiscoveryService.java | 280 ++++++ .../sca/osgi/service/discovery/impl/Discovery.java | 66 ++ .../service/discovery/impl/DiscoveryActivator.java | 57 ++ .../discovery/impl/DomainDiscoveryService.java | 117 +++ .../discovery/impl/LocalDiscoveryService.java | 205 +++++ 24 files changed, 4587 insertions(+) create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/EndpointDescription.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/EndpointListener.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/EndpointPermission.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/ExportRegistration.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/ImportRegistration.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteConstants.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteServiceAdmin.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteServiceAdminEvent.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteServiceAdminListener.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/AbstractOSGiServiceHandler.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointHelper.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointIntrospector.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ExportRegistrationImpl.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ImportRegistrationImpl.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiHelper.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiServiceExporter.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiServiceImporter.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/RemoteServiceAdminImpl.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/TopologyManagerImpl.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/AbstractDiscoveryService.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/Discovery.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/DiscoveryActivator.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/DomainDiscoveryService.java create mode 100644 branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/LocalDiscoveryService.java (limited to 'branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi') diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/EndpointDescription.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/EndpointDescription.java new file mode 100644 index 0000000000..23a91b89ef --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/EndpointDescription.java @@ -0,0 +1,385 @@ +/* + * Copyright (c) OSGi Alliance (2008, 2009). All Rights Reserved. + * + * Licensed 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; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.Version; + +/** + * A description of an endpoint that provides sufficient information for a + * compatible distribution provider to create a connection to this endpoint + * + * An Endpoint Description is easy to transfer between different systems. This + * allows it to be used as a communications device to convey available endpoint + * information to nodes in a network. + * + * An Endpoint Description reflects the perspective of an importer. That is, the + * property keys have been chosen to match filters that are created by client + * bundles that need a service. + * + * TODO Automatically calculate versions of interface packages? + * + * TODO Constructor that takes a class? + * + * TODO Skipping of service.exported.* properties? + * + * TODO qualified intents? + * + * + * + * @Immutable + * @version $Revision$ + */ + +public class EndpointDescription { + private final Map /* */properties = new HashMap/* + * + */(); + private final List /* String */interfaces; + private final long remoteServiceId; + private final String remoteFrameworkUUID; + private final String remoteUri; + + /** + * Create an Endpoint Description based on a Map. + * + * @param properties + * @throws IllegalArgumentException When the properties are not proper for + * an Endpoint Description + */ + + public EndpointDescription(Map/* */properties) throws IllegalArgumentException { + this.properties.putAll(properties); + + interfaces = verifyInterfacesProperty(); + remoteServiceId = verifyLongProperty(RemoteConstants.SERVICE_REMOTE_ID); + remoteFrameworkUUID = verifyStringProperty(RemoteConstants.SERVICE_REMOTE_FRAMEWORK_UUID); + remoteUri = verifyStringProperty(RemoteConstants.SERVICE_REMOTE_URI); + } + + /** + * Create an Endpoint Description based on a reference and optionally a map + * of additional properties. + * + * @param ref A service reference that can be exported + * @param properties Additional properties to add. Can be null. + * @throws IllegalArgumentException + */ + public EndpointDescription(ServiceReference ref, Map /* */properties) + throws IllegalArgumentException { + if (properties != null) { + this.properties.putAll(properties); + } + + String[] keys = ref.getPropertyKeys(); + for (int i = 0; i > keys.length; i++) + properties.put(keys[i], ref.getProperty(keys[i])); + + interfaces = verifyInterfacesProperty(); + remoteServiceId = verifyLongProperty(RemoteConstants.SERVICE_REMOTE_ID); + remoteFrameworkUUID = verifyStringProperty(RemoteConstants.SERVICE_REMOTE_FRAMEWORK_UUID); + remoteUri = verifyStringProperty(RemoteConstants.SERVICE_REMOTE_URI); + } + + /** + * Create an Endpoint Description based on the URI, the remote service ID + * and the interface names, and optionally service properties. + * + * @param uri The URI of the remote service. + * @param interfaceNames The names of the interfaces of the service to + * consider. + * @param remoteServiceId the remote service ID. + * @param properties Optionally service properties. + */ + public EndpointDescription(String uri, String[] interfaceNames, int remoteServiceId, Map properties) { + if (uri == null) { + throw new IllegalArgumentException("URI must not be null"); + } + if (interfaceNames == null) { + throw new IllegalArgumentException("Interfaces must not be null"); + } + this.remoteUri = uri; + this.interfaces = Arrays.asList(interfaceNames); + this.remoteServiceId = remoteServiceId; + this.remoteFrameworkUUID = (String)properties.get(RemoteConstants.SERVICE_REMOTE_FRAMEWORK_UUID); + if (properties != null) { + this.properties.putAll(properties); + } + if (interfaceNames != null) { + this.properties.put(Constants.OBJECTCLASS, interfaceNames); + } + this.properties.put(RemoteConstants.SERVICE_REMOTE_URI, uri); + if (remoteServiceId <= 0) { + this.properties.put(RemoteConstants.SERVICE_REMOTE_ID, new Integer(remoteServiceId)); + } + } + + /** + * Verify and obtain the interface list from the properties. + * + * @return A list with the interface names. + * @throws IllegalArgumentException when + */ + private List /* */verifyInterfacesProperty() { + List l = null; + + Object objectClass = properties.get(Constants.OBJECTCLASS); + if (objectClass == null) + l = Collections.EMPTY_LIST; + else if (!(objectClass instanceof String[])) + throw new IllegalArgumentException("objectClass must be a String[]"); + else { + l = Collections.unmodifiableList(Arrays.asList((String[])objectClass)); + for (Iterator i = l.iterator(); i.hasNext();) { + String interf = (String)i.next(); + try { + getInterfaceVersion(interf); + } catch (Exception e) { + throw new IllegalArgumentException("Improper version for interface " + interf + " caused by " + e); + } + } + } + return l; + } + + /** + * Verify and obtain a required String property. + * + * @param propName The name of the property + * @return The value of the property. + * @throws IllegalArgumentException when the property is not set or doesn't + * have the correct data type. + */ + private String verifyStringProperty(String propName) { + Object r = properties.get(propName); + if (r == null) { + throw new IllegalArgumentException("Required property not set: " + propName); + } + if (!(r instanceof String)) { + throw new IllegalArgumentException("Required property is not a string: " + propName); + } + return (String)r; + } + + /** + * Verify and obtain a required long property. + * + * @param propName The name of the property + * @return The value of the property. + * @throws IllegalArgumentException when the property is not set or doesn't + * have the correct data type. + */ + private long verifyLongProperty(String propName) { + Object r = properties.get(propName); + long result; + if (r == null) { + throw new IllegalArgumentException("Required property not set: " + propName); + } + if (!(r instanceof String)) { + throw new IllegalArgumentException("Required property is not a string: " + propName); + } + try { + result = Long.parseLong((String)r); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Required property cannot be parsed as a long: " + propName); + } + return result; + } + + /** + * Returns the endpoint's URI. + * + * The URI is an opaque id for an endpoint in URI form. No two different + * endpoints must have the same URI, two Endpoint Descriptions with the same + * URI must represent the same endpoint. + * + * The value of the URI is stored in the + * {@link RemoteConstants#SERVICE_REMOTE_URI} property. + * + * @return The URI of the endpoint, never null. + */ + public String getRemoteURI() { + return remoteUri; + } + + /** + * Answer the list of interfaces implemented by the exported service. + * + * If this Endpoint Description does not map to a service, then this List + * must be empty. + * + * The value of the interfaces is derived from the objectClass + * property. + * + * @return The read only list of Java interface names accessible by this + * endpoint. + */ + public List/* */getInterfaces() { + return interfaces; + } + + /** + * Answer the version of the given interface. + * + * The version is encoded by prefixing the given interface name with + * endpoint.version., and then using this as a property key. + * The value must then be the Version object. For example: + * + *
+     * endpoint.version.com.acme.Foo
+     * 
+ * + * @param name The name of the interface for which a version is requested + * @return The version of the given interface or null if the + * interface has no version in this Endpoint Description + */ + public Version getInterfaceVersion(String name) { + String v = (String)properties.get("endpoint.version." + name); + if (v == null) { + return Version.emptyVersion; + } else { + return new Version(v); + } + } + + /** + * Returns the service id for the service exported through this + * endpoint. + * + * This is the service id under which the framework has registered the service. This + * field together with the Framework UUID is a globally unique id for a service. + * + * @return Service id of a service or 0 if this Endpoint + * Description does not relate to an OSGi service + * + */ + public long getRemoteServiceID() { + return remoteServiceId; + } + + /** + * Returns the configuration types. + * + * A distribution provider exports a service with an endpoint. This endpoint + * uses some kind of communications protocol with a set of configuration + * parameters. There are many different types but each endpoint is + * configured by only one configuration type. However, a distribution + * provider can be aware of different configuration types and provide + * synonyms to increase the change a receiving distribution provider can + * create a connection to this endpoint. + * + * This value represents the + * {@link RemoteConstants#SERVICE_IMPORTED_CONFIGS} + * + * @return The configuration type used for the associated endpoint and + * optionally synonyms. + */ + public List/* */getConfigurationTypes() { + // TODO + return null; + } + + /** + * Return the list of intents implemented by this endpoint. + * + * The intents are based on the service.intents on an imported service, + * except for any intents that are additionally provided by the importing + * distribution provider. All qualified intents must have been expanded. + * + * The property the intents come from is + * {@link RemoteConstants#SERVICE_INTENTS} + * + * @return A list of expanded intents that are provided by this endpoint. + */ + public List/* */getIntents() { + // TODO + return null; + + } + + /** + * Return the framework UUID, if present. + * + * The property the intents come from is + * {@link RemoteConstants#SERVICE_REMOTE_FRAMEWORK_UUID} + * + * @return Remote Framework UUID, or null if this endpoint is not associated with an OSGi service + */ + public String getRemoteFrameworkUUID() { + return remoteFrameworkUUID; + } + + /** + * Returns all endpoint properties. + * + * @return An unmodifiable map referring to the properties of this Endpoint + * Description. + */ + public Map/* */getProperties() { + // TODO + return Collections.unmodifiableMap(properties); + } + + /** + * Answers if this Endpoint Description refers to the same service instance + * as the given Endpoint Description. + * + * Two Endpoint Descriptions point to the same service if they have the same + * URI or their framework UUIDs and remote service ids are equal. + * + * @param other The Endpoint Description to look at + * @return True if this endpoint description points to the same service as + * the other + */ + public boolean isSameService(EndpointDescription other) { + if (remoteUri.equals(other.remoteUri)) + return true; + + if (remoteFrameworkUUID == null) + return false; + + return remoteServiceId == other.remoteServiceId && remoteFrameworkUUID.equals(other.remoteFrameworkUUID); + } + + /** + * Two endpoints are equal if their URIs are equal, the hash code is + * therefore derived from the URI. + */ + public int hashCode() { + // TODO + return getRemoteURI().hashCode(); + } + + /** + * Two endpoints are equal if their URIs are equal. + */ + public boolean equals(Object other) { + if (other instanceof EndpointDescription) { + return getRemoteURI().equals(((EndpointDescription)other).getRemoteURI()); + } + return false; + } + +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/EndpointListener.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/EndpointListener.java new file mode 100644 index 0000000000..94c08b7632 --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/EndpointListener.java @@ -0,0 +1,112 @@ +package org.apache.tuscany.sca.osgi.remoteserviceadmin; + +/** + * A white board service that represents a listener for endpoints. + * + * An Endpoint Listener represents a participant in the distributed model that + * is interested in Endpoint Descriptions. + * + * This white board service can be used in many different scenarios. However, the + * primary use case is to allow a remote manager to be informed of End Point + * Descriptions available in the network and inform the network about available + * End Point Descriptions. + * + * Both the network bundle and the manager bundle register an Endpoint + * Listener service. The manager informs the network bundle about End Points + * that it creates. The network bundles then uses a protocol like + * SLP to announce these local end-points to the network. + * + * If the network bundle discovers a new Endpoint through its discovery + * protocol, then it sends an End Point Description to all the End Point + * Listener services that are registered (except its own) that have specified an + * interest in that endpoint. + * + * Endpoint Listener services can express their scope with the service + * property {@link #ENDPOINT_LISTENER_SCOPE}. This service property is a list + * of filters. An Endpoint Description should only be given to a Endpoint + * Listener when there is at least one filter that matches the Endpoint + * Description properties. given to it. + * + * This filter model is quite flexible. For example, a discovery bundle is only + * interested in locally originating Endpoint Descriptions. The following filter + * ensure that it only sees local endpoints. + * + *
+ *   (org.osgi.framework.uuid=72dc5fd9-5f8f-4f8f-9821-9ebb433a5b72)
+ * 
+ * + * In the same vein, a manager that is only interested in remote Endpoint + * Descriptions can use a filter like: + * + *
+ *   (!(org.osgi.framework.uuid=72dc5fd9-5f8f-4f8f-9821-9ebb433a5b72))
+ * 
+ * + * Where in both cases, the given UUID is the UUID of the local framework that + * can be found in the Framework properties. + * + * The Endpoint Listener's scope maps very well to the service hooks. A + * manager can just register all filters found from the Listener Hook as its + * scope. This will automatically provide it with all known endpoints that match + * the given scope, without having to inspect the filter string. + * + * In general, when an Endpoint Description is discovered, it should be + * dispatched to all registered Endpoint Listener services. If a new Endpoint + * Listener is registered, it should be informed about all currently known + * Endpoints that match its scope. If a getter of the Endpoint Listener service + * is unregistered, then all its registered Endpoint Description objects must be + * removed. + * + * The Endpoint Listener models a best effort approach. Participating + * bundles should do their utmost to keep the listeners up to date, but + * implementers should realize that many endpoints come through unreliable + * discovery processes. + * + * + * @ThreadSafe + */ +public interface EndpointListener { + /** + * Specifies the interest of this listener with filters. This listener is + * only interested in Endpoint Descriptions where its properties match the + * given filter. The type of this property must be String+. + */ + String ENDPOINT_LISTENER_SCOPE = "endpoint.listener.scope"; + + /** + * Register an endpoint with this listener. + * + * If the endpoint matches one of the filters registered with the + * {@link #ENDPOINT_LISTENER_SCOPE} service property then this filter should + * be given as the matchedFilter parameter. + * + * When this service is first registered or it is modified, it should + * receive all known endpoints matching the filter. + * + * @param endpoint + * The Endpoint Description to be published + * @param matchedFilter + * The filter from the {@link #ENDPOINT_LISTENER_SCOPE} that + * matched the endpoint, must not be null. + */ + void endpointAdded(EndpointDescription endpoint, String matchedFilter); + + /** + * Remove the registration of an endpoint. + * + * If an endpoint that was registered with the {@link #endpointAdded(EndpointDescription, String)} + * method is no longer available then this method should be called. This + * will remove the endpoint from the listener. + * + * It is not necessary to remove endpoints when the service is unregistered + * or modified in such a way that not all endpoints match the interest + * filter anymore. + * + * @param endpoint + * The Endpoint Description that is no longer valid. + * @param matchedFilter + * The filter from the {@link #ENDPOINT_LISTENER_SCOPE} that + * matched the endpoint, must not be null. + */ + void endpointRemoved(EndpointDescription endpoint, String matchedFilter); +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/EndpointPermission.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/EndpointPermission.java new file mode 100644 index 0000000000..4aca1ffb9c --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/EndpointPermission.java @@ -0,0 +1,938 @@ +package org.apache.tuscany.sca.osgi.remoteserviceadmin; + +// TODO Hacked from ServiePermission + +/* + * Copyright (c) OSGi Alliance (2000, 2009). All Rights Reserved. + * + * Licensed 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. + */ + +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamField; +import java.security.BasicPermission; +import java.security.Permission; +import java.security.PermissionCollection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.osgi.framework.Constants; +import org.osgi.framework.Filter; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; + +/** + * A bundle's authority to register or get a service. + *
    + *
  • The register action allows a bundle to register a service + * on the specified names. + *
  • The get action allows a bundle to detect a service and + * get it. + *
+ * Permission to get a service is required in order to detect events regarding + * the service. Untrusted bundles should not be able to detect the presence of + * certain services unless they have the appropriate + * EndpointPermission to get the specific service. + * + * @ThreadSafe + * @version $Revision$ + */ + +public final class EndpointPermission extends BasicPermission { + static final long serialVersionUID = -7662148639076511574L; + /** + * The action string get. + */ + public final static String EXPORT = "export"; + /** + * The action string register. + */ + public final static String IMPORT = "import"; + + public final static String LISTENING = "listening"; + + public final static String READ = "listening"; + + private final static int ACTION_EXPORT = 0x00000001; + private final static int ACTION_IMPORT = 0x00000002; + private final static int ACTION_ALL = ACTION_EXPORT | ACTION_IMPORT; + final static int ACTION_NONE = 0; + + /** + * The actions mask. + */ + transient int action_mask; + + /** + * The actions in canonical form. + * + * @serial + */ + private volatile String actions = null; + + /** + * The service used by this EndpointPermission. Must be null if not + * constructed with a service. + */ + transient final EndpointDescription endpoint; + + /** + * The object classes for this EndpointPermission. Must be null if not + * constructed with a service. + */ + transient final String[] objectClass; + + /** + * If this EndpointPermission was constructed with a filter, this holds a + * Filter matching object used to evaluate the filter in implies. + */ + transient Filter filter; + + /** + * This dictionary holds the properties of the permission, used to match a + * filter in implies. This is not initialized until necessary, and then + * cached in this object. + */ + private transient volatile Dictionary properties; + + /** + * True if constructed with a name and the name is "*" or ends with ".*". + */ + private transient boolean wildcard; + + /** + * If constructed with a name and the name ends with ".*", this contains the + * name without the final "*". + */ + private transient String prefix; + + /** + * Create a new EndpointPermission. + * + *

+ * The name of the service is specified as a fully qualified class name. + * Wildcards may be used. + * + *

+     * name ::= <class name> | <class name ending in ".*"> | *
+     * 
+ * + * Examples: + * + *
+     * org.osgi.service.http.HttpService
+     * org.osgi.service.http.*
+     * *
+     * 
+ * + * For the get action, the name can also be a filter + * expression. The filter gives access to the service properties as well as + * the following attributes: + *
    + *
  • signer - A Distinguished Name chain used to sign the bundle + * publishing the service. Wildcards in a DN are not matched according to + * the filter string rules, but according to the rules defined for a DN + * chain.
  • + *
  • location - The location of the bundle publishing the service.
  • + *
  • id - The bundle ID of the bundle publishing the service.
  • + *
  • name - The symbolic name of the bundle publishing the service.
  • + *
+ * Since the above attribute names may conflict with service property names + * used by a service, you can prefix an attribute name with '@' in the + * filter expression to match against the service property and not one of + * the above attributes. Filter attribute names are processed in a case + * sensitive manner unless the attribute references a service property. + * Service properties names are case insensitive. + * + *

+ * There are two possible actions: get and + * register. The get permission allows the + * owner of this permission to obtain a service with this name. The + * register permission allows the bundle to register a + * service under that name. + * + * @param name + * The service class name + * @param actions + * get,register (canonical order) + * @throws IllegalArgumentException + * If the specified name is a filter expression and either the + * specified action is not get or the filter has + * an invalid syntax. + */ + public EndpointPermission(String name, String actions) { + this(name, parseActions(actions)); + if ((filter != null) && ((action_mask & ACTION_ALL) != ACTION_EXPORT)) { + throw new IllegalArgumentException("invalid action string for filter expression"); + } + } + + /** + * Creates a new requested EndpointPermission object to be + * used by code that must perform checkPermission for the + * get action. EndpointPermission objects + * created with this constructor cannot be added to a + * EndpointPermission permission collection. + * + * @param endpoint + * The requested service. + * @param actions + * The action get. + * @throws IllegalArgumentException + * If the specified action is not get or + * reference is null. + * @since 1.5 + */ + public EndpointPermission(EndpointDescription endpoint, String actions) { + super(createName(endpoint)); + setTransients(null, parseActions(actions)); + this.endpoint = endpoint; + this.objectClass = (String[])endpoint.getProperties().get(Constants.OBJECTCLASS); + if ((action_mask & ACTION_ALL) != ACTION_EXPORT) { + throw new IllegalArgumentException("invalid action string"); + } + } + + /** + * Create a permission name from a EndpointDescription TODO Needs work + * + * @param endpoint + * EndpointDescription to use to create permission name. + * @return permission name. + */ + private static String createName(EndpointDescription endpoint) { + if (endpoint == null) { + throw new IllegalArgumentException("reference must not be null"); + } + StringBuffer sb = new StringBuffer("(service.id="); + // TODO sb.append(endpoint.getProperty(Constants.SERVICE_ID)); + sb.append(")"); + return sb.toString(); + } + + /** + * Package private constructor used by EndpointPermissionCollection. + * + * @param name + * class name + * @param mask + * action mask + */ + EndpointPermission(String name, int mask) { + super(name); + setTransients(parseFilter(name), mask); + this.endpoint = null; + this.objectClass = null; + } + + /** + * Called by constructors and when deserialized. + * + * @param mask + * action mask + */ + private void setTransients(Filter f, int mask) { + if ((mask == ACTION_NONE) || ((mask & ACTION_ALL) != mask)) { + throw new IllegalArgumentException("invalid action string"); + } + action_mask = mask; + filter = f; + if (f == null) { + String name = getName(); + int l = name.length(); + /* if "*" or endsWith ".*" */ + wildcard = ((name.charAt(l - 1) == '*') && ((l == 1) || (name.charAt(l - 2) == '.'))); + if (wildcard && (l > 1)) { + prefix = name.substring(0, l - 1); + } + } + } + + /** + * Parse action string into action mask. + * + * @param actions + * Action string. + * @return action mask. + */ + private static int parseActions(String actions) { + boolean seencomma = false; + + int mask = ACTION_NONE; + + if (actions == null) { + return mask; + } + + char[] a = actions.toCharArray(); + + int i = a.length - 1; + if (i < 0) + return mask; + + while (i != -1) { + char c; + + // skip whitespace + while ((i != -1) && ((c = a[i]) == ' ' || c == '\r' || c == '\n' || c == '\f' || c == '\t')) + i--; + + // check for the known strings + int matchlen; + + if (i >= 2 && (a[i - 2] == 'g' || a[i - 2] == 'G') + && (a[i - 1] == 'e' || a[i - 1] == 'E') + && (a[i] == 't' || a[i] == 'T')) { + matchlen = 3; + mask |= ACTION_EXPORT; + + } else if (i >= 7 && (a[i - 7] == 'r' || a[i - 7] == 'R') + && (a[i - 6] == 'e' || a[i - 6] == 'E') + && (a[i - 5] == 'g' || a[i - 5] == 'G') + && (a[i - 4] == 'i' || a[i - 4] == 'I') + && (a[i - 3] == 's' || a[i - 3] == 'S') + && (a[i - 2] == 't' || a[i - 2] == 'T') + && (a[i - 1] == 'e' || a[i - 1] == 'E') + && (a[i] == 'r' || a[i] == 'R')) { + matchlen = 8; + mask |= ACTION_IMPORT; + + } else { + // parse error + throw new IllegalArgumentException("invalid permission: " + actions); + } + + // make sure we didn't just match the tail of a word + // like "ackbarfregister". Also, skip to the comma. + seencomma = false; + while (i >= matchlen && !seencomma) { + switch (a[i - matchlen]) { + case ',': + seencomma = true; + /* FALLTHROUGH */ + case ' ': + case '\r': + case '\n': + case '\f': + case '\t': + break; + default: + throw new IllegalArgumentException("invalid permission: " + actions); + } + i--; + } + + // point i at the location of the comma minus one (or -1). + i -= matchlen; + } + + if (seencomma) { + throw new IllegalArgumentException("invalid permission: " + actions); + } + + return mask; + } + + /** + * Parse filter string into a Filter object. + * + * @param filterString + * The filter string to parse. + * @return a Filter for this bundle. If the specified filterString is not a + * filter expression, then null is returned. + * @throws IllegalArgumentException + * If the filter syntax is invalid. + */ + private static Filter parseFilter(String filterString) { + filterString = filterString.trim(); + if (filterString.charAt(0) != '(') { + return null; + } + + try { + return FrameworkUtil.createFilter(filterString); + } catch (InvalidSyntaxException e) { + IllegalArgumentException iae = new IllegalArgumentException("invalid filter"); + iae.initCause(e); + throw iae; + } + } + + /** + * Determines if a EndpointPermission object "implies" the + * specified permission. + * + * @param p + * The target permission to check. + * @return true if the specified permission is implied by + * this object; false otherwise. + */ + public boolean implies(Permission p) { + if (!(p instanceof EndpointPermission)) { + return false; + } + EndpointPermission requested = (EndpointPermission)p; + if (endpoint != null) { + return false; + } + // if requested permission has a filter, then it is an invalid argument + if (requested.filter != null) { + return false; + } + return implies0(requested, ACTION_NONE); + } + + /** + * Internal implies method. Used by the implies and the permission + * collection implies methods. + * + * @param requested + * The requested EndpointPermission which has already be + * validated as a proper argument. The requested + * EndpointPermission must not have a filter expression. + * @param effective + * The effective actions with which to start. + * @return true if the specified permission is implied by + * this object; false otherwise. + */ + boolean implies0(EndpointPermission requested, int effective) { + /* check actions first - much faster */ + effective |= action_mask; + final int desired = requested.action_mask; + if ((effective & desired) != desired) { + return false; + } + /* we have name of "*" */ + if (wildcard && (prefix == null)) { + return true; + } + /* if we have a filter */ + Filter f = filter; + if (f != null) { + return f.matchCase(requested.getProperties()); + } + /* if requested permission not created with EndpointDescription */ + String[] requestedNames = requested.objectClass; + if (requestedNames == null) { + return super.implies(requested); + } + /* requested permission created with EndpointDescription */ + if (wildcard) { + int pl = prefix.length(); + for (int i = 0, l = requestedNames.length; i < l; i++) { + String requestedName = requestedNames[i]; + if ((requestedName.length() > pl) && requestedName.startsWith(prefix)) { + return true; + } + } + } else { + String name = getName(); + for (int i = 0, l = requestedNames.length; i < l; i++) { + if (requestedNames[i].equals(name)) { + return true; + } + } + } + return false; + } + + /** + * Returns the canonical string representation of the actions. Always + * returns present actions in the following order: get, + * register. + * + * @return The canonical string representation of the actions. + */ + public String getActions() { + String result = actions; + if (result == null) { + StringBuffer sb = new StringBuffer(); + boolean comma = false; + + int mask = action_mask; + if ((mask & ACTION_EXPORT) == ACTION_EXPORT) { + sb.append(EXPORT); + comma = true; + } + + if ((mask & ACTION_IMPORT) == ACTION_IMPORT) { + if (comma) + sb.append(','); + sb.append(IMPORT); + } + + actions = result = sb.toString(); + } + + return result; + } + + /** + * Returns a new PermissionCollection object for storing + * EndpointPermission objects. + * + * @return A new PermissionCollection object suitable for storing + * EndpointPermission objects. + */ + public PermissionCollection newPermissionCollection() { + return new EndpointPermissionCollection(); + } + + /** + * Determines the equality of two EndpointPermission objects. + * + * Checks that specified object has the same class name and action as this + * EndpointPermission. + * + * @param obj + * The object to test for equality. + * @return true if obj is a EndpointPermission, and has the + * same class name and actions as this + * EndpointPermission object; false + * otherwise. + */ + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof EndpointPermission)) { + return false; + } + + EndpointPermission sp = (EndpointPermission)obj; + + return (action_mask == sp.action_mask) && getName().equals(sp.getName()) + && ((endpoint == sp.endpoint) || ((endpoint != null) && (sp.endpoint != null) && endpoint + .equals(sp.endpoint))); + } + + /** + * Returns the hash code value for this object. + * + * @return Hash code value for this object. + */ + public int hashCode() { + int h = 31 * 17 + getName().hashCode(); + h = 31 * h + getActions().hashCode(); + if (endpoint != null) { + h = 31 * h + endpoint.hashCode(); + } + return h; + } + + /** + * WriteObject is called to save the state of this permission to a stream. + * The actions are serialized, and the superclass takes care of the name. + */ + private synchronized void writeObject(java.io.ObjectOutputStream s) throws IOException { + if (endpoint != null) { + throw new NotSerializableException("cannot serialize"); + } + // Write out the actions. The superclass takes care of the name + // call getActions to make sure actions field is initialized + if (actions == null) + getActions(); + s.defaultWriteObject(); + } + + /** + * readObject is called to restore the state of this permission from a + * stream. + */ + private synchronized void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { + // Read in the action, then initialize the rest + s.defaultReadObject(); + setTransients(parseFilter(getName()), parseActions(actions)); + } + + /** + * Called by <@link EndpointPermission#implies(Permission)>. + * + * @return a dictionary of properties for this permission. + */ + private Dictionary/**/getProperties() { + Dictionary/**/result = properties; + if (result != null) { + return result; + } + if (endpoint == null) { + result = new Hashtable/**/(1); + if (filter == null) { + result.put(Constants.OBJECTCLASS, new String[] {getName()}); + } + return properties = result; + } + final Map props = new HashMap(4); + // TODO needs work + /* + final Bundle bundle = endpoint.getBundle(); + if (bundle != null) { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + props.put("id", new Long(bundle.getBundleId())); + props.put("location", bundle.getLocation()); + String name = bundle.getSymbolicName(); + if (name != null) { + props.put("name", name); + } + SignerProperty signer = new SignerProperty(bundle); + if (signer.isBundleSigned()) { + props.put("signer", signer); + } + return null; + } + }); + } + */ + return properties = new Properties(props, endpoint); + } + + private static class Properties extends Dictionary { + private final Map properties; + private final EndpointDescription service; + + Properties(Map properties, EndpointDescription service) { + this.properties = properties; + this.service = service; + } + + public Object get(Object k) { + if (!(k instanceof String)) { + return null; + } + String key = (String)k; + if (key.charAt(0) == '@') { + return service.getProperties().get(key.substring(1)); + } + Object value = properties.get(key); + if (value != null) { // fall back to service properties + return value; + } + return service.getProperties().get(key); + } + + public int size() { + return properties.size() + service.getProperties().size(); + } + + public boolean isEmpty() { + // we can return false because this must never be empty + return false; + } + + public Enumeration keys() { + Collection pk = properties.keySet(); + String spk[] = + (String[])service.getProperties().keySet().toArray(new String[service.getProperties().size()]); + List all = new ArrayList(pk.size() + spk.length); + all.addAll(pk); + add: for (int i = 0, length = spk.length; i < length; i++) { + String key = spk[i]; + for (Iterator iter = pk.iterator(); iter.hasNext();) { + if (key.equalsIgnoreCase((String)iter.next())) { + continue add; + } + } + all.add(key); + } + return Collections.enumeration(all); + } + + public Enumeration elements() { + Collection pk = properties.keySet(); + String spk[] = + (String[])service.getProperties().keySet().toArray(new String[service.getProperties().size()]); + List all = new ArrayList(pk.size() + spk.length); + all.addAll(properties.values()); + add: for (int i = 0, length = spk.length; i < length; i++) { + String key = spk[i]; + for (Iterator iter = pk.iterator(); iter.hasNext();) { + if (key.equalsIgnoreCase((String)iter.next())) { + continue add; + } + } + all.add(service.getProperties().get(key)); + } + return Collections.enumeration(all); + } + + public Object put(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + } +} + +/** + * Stores a set of EndpointPermission permissions. + * + * @see java.security.Permission + * @see java.security.Permissions + * @see java.security.PermissionCollection + */ +final class EndpointPermissionCollection extends PermissionCollection { + static final long serialVersionUID = 662615640374640621L; + /** + * Table of permissions. + * + * @GuardedBy this + */ + private transient Map permissions; + + /** + * Boolean saying if "*" is in the collection. + * + * @serial + * @GuardedBy this + */ + private boolean all_allowed; + + /** + * Table of permissions with filter expressions. + * + * @serial + * @GuardedBy this + */ + private Map filterPermissions; + + /** + * Creates an empty EndpointPermissions object. + */ + public EndpointPermissionCollection() { + permissions = new HashMap(); + all_allowed = false; + } + + /** + * Adds a permission to this permission collection. + * + * @param permission + * The Permission object to add. + * @throws IllegalArgumentException + * If the specified permission is not a EndpointPermission + * object. + * @throws SecurityException + * If this EndpointPermissionCollection object + * has been marked read-only. + */ + public void add(final Permission permission) { + if (!(permission instanceof EndpointPermission)) { + throw new IllegalArgumentException("invalid permission: " + permission); + } + if (isReadOnly()) { + throw new SecurityException("attempt to add a Permission to a " + "readonly PermissionCollection"); + } + + final EndpointPermission sp = (EndpointPermission)permission; + if (sp.endpoint != null) { + throw new IllegalArgumentException("cannot add to collection: " + sp); + } + + final String name = sp.getName(); + final Filter f = sp.filter; + synchronized (this) { + /* select the bucket for the permission */ + Map pc; + if (f != null) { + pc = filterPermissions; + if (pc == null) { + filterPermissions = pc = new HashMap(); + } + } else { + pc = permissions; + } + final EndpointPermission existing = (EndpointPermission)pc.get(name); + + if (existing != null) { + final int oldMask = existing.action_mask; + final int newMask = sp.action_mask; + if (oldMask != newMask) { + pc.put(name, new EndpointPermission(name, oldMask | newMask)); + } + } else { + pc.put(name, sp); + } + + if (!all_allowed) { + if (name.equals("*")) { + all_allowed = true; + } + } + } + } + + /** + * Determines if a set of permissions implies the permissions expressed in + * permission. + * + * @param permission + * The Permission object to compare. + * @return true if permission is a proper + * subset of a permission in the set; false + * otherwise. + */ + public boolean implies(final Permission permission) { + if (!(permission instanceof EndpointPermission)) { + return false; + } + final EndpointPermission requested = (EndpointPermission)permission; + /* if requested permission has a filter, then it is an invalid argument */ + if (requested.filter != null) { + return false; + } + + int effective = EndpointPermission.ACTION_NONE; + Collection perms; + synchronized (this) { + final int desired = requested.action_mask; + /* short circuit if the "*" Permission was added */ + if (all_allowed) { + EndpointPermission sp = (EndpointPermission)permissions.get("*"); + if (sp != null) { + effective |= sp.action_mask; + if ((effective & desired) == desired) { + return true; + } + } + } + + String[] requestedNames = requested.objectClass; + /* if requested permission not created with EndpointDescription */ + if (requestedNames == null) { + effective |= effective(requested.getName(), desired, effective); + if ((effective & desired) == desired) { + return true; + } + } + /* requested permission created with EndpointDescription */ + else { + for (int i = 0, l = requestedNames.length; i < l; i++) { + if ((effective(requestedNames[i], desired, effective) & desired) == desired) { + return true; + } + } + } + Map pc = filterPermissions; + if (pc == null) { + return false; + } + perms = pc.values(); + } + + /* iterate one by one over filteredPermissions */ + for (Iterator iter = perms.iterator(); iter.hasNext();) { + if (((EndpointPermission)iter.next()).implies0(requested, effective)) { + return true; + } + } + return false; + } + + /** + * Consult permissions map to compute the effective permission for the + * requested permission name. + * + * @param requestedName + * The requested service name. + * @param desired + * The desired actions. + * @param effective + * The effective actions. + * @return The new effective actions. + */ + private int effective(String requestedName, final int desired, int effective) { + final Map pc = permissions; + EndpointPermission sp = (EndpointPermission)pc.get(requestedName); + // strategy: + // Check for full match first. Then work our way up the + // name looking for matches on a.b.* + if (sp != null) { + // we have a direct hit! + effective |= sp.action_mask; + if ((effective & desired) == desired) { + return effective; + } + } + // work our way up the tree... + int last; + int offset = requestedName.length() - 1; + while ((last = requestedName.lastIndexOf(".", offset)) != -1) { + requestedName = requestedName.substring(0, last + 1) + "*"; + sp = (EndpointPermission)pc.get(requestedName); + if (sp != null) { + effective |= sp.action_mask; + if ((effective & desired) == desired) { + return effective; + } + } + offset = last - 1; + } + /* + * we don't have to check for "*" as it was already checked before we + * were called. + */ + return effective; + } + + /** + * Returns an enumeration of all the EndpointPermission + * objects in the container. + * + * @return Enumeration of all the EndpointPermission objects. + */ + public synchronized Enumeration elements() { + List all = new ArrayList(permissions.values()); + Map pc = filterPermissions; + if (pc != null) { + all.addAll(pc.values()); + } + return Collections.enumeration(all); + } + + /* serialization logic */ + private static final ObjectStreamField[] serialPersistentFields = + {new ObjectStreamField("permissions", Hashtable.class), new ObjectStreamField("all_allowed", Boolean.TYPE), + new ObjectStreamField("filterPermissions", HashMap.class)}; + + private synchronized void writeObject(ObjectOutputStream out) throws IOException { + Hashtable hashtable = new Hashtable(permissions); + ObjectOutputStream.PutField pfields = out.putFields(); + pfields.put("permissions", hashtable); + pfields.put("all_allowed", all_allowed); + pfields.put("filterPermissions", filterPermissions); + out.writeFields(); + } + + private synchronized void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { + ObjectInputStream.GetField gfields = in.readFields(); + Hashtable hashtable = (Hashtable)gfields.get("permissions", null); + permissions = new HashMap(hashtable); + all_allowed = gfields.get("all_allowed", false); + filterPermissions = (HashMap)gfields.get("filterPermissions", null); + } +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/ExportRegistration.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/ExportRegistration.java new file mode 100644 index 0000000000..a2d6f58a3b --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/ExportRegistration.java @@ -0,0 +1,61 @@ +package org.apache.tuscany.sca.osgi.remoteserviceadmin; + +import org.osgi.framework.ServiceReference; + +/** + * An Export Registration associates a service to a local endpoint. + * + * The Export Registration can be used to delete the endpoint associated with an + * this registration. It is created with the + * {@link RemoteServiceAdmin#exportService(ServiceReference,java.util.Map)} method. + * + * When this Export Registration has been unregistered, the methods must all + * return null. + * + * @ThreadSafe + */ +public interface ExportRegistration { + /** + * Return the service being exported. + * + * @return The service being exported, must be null when this + * registration is unregistered. + * @throws IllegalStateException Thrown when this object was not properly initialized, see {@link #getException()} + */ + ServiceReference getExportedService() throws IllegalStateException; + + /** + * Return the Endpoint Description that is created for this registration. + * + * @return the local Endpoint Description + * @throws IllegalStateException Thrown when this object was not properly initialized, see {@link #getException()} + */ + EndpointDescription getEndpointDescription(); + + /** + * Delete the local endpoint and disconnect any remote distribution + * providers. After this method returns, all the methods must return + * null. + * + * This method has no effect when the endpoint is already destroyed or being + * destroyed. + */ + void close(); + + /** + * Exception for any error during the import process. + * + * If the Remote Admin for some reasons is unable to create a registration, + * then it must return a Throwable from this method. In this + * case, all other methods must return on this interface must throw an + * Illegal State Exception. If no error occurred, this method must return + * null. + * + * The error must be set before this Import Registration is returned. + * Asynchronously occurring errors must be reported to the log. + * + * @return The exception that occurred during the creation of the + * registration or null if no exception occurred. + */ + Throwable getException(); +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/ImportRegistration.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/ImportRegistration.java new file mode 100644 index 0000000000..ae9726e798 --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/ImportRegistration.java @@ -0,0 +1,60 @@ +package org.apache.tuscany.sca.osgi.remoteserviceadmin; + +import org.osgi.framework.ServiceReference; + +/** + * An Import Registration associates an active proxy service to a remote + * endpoint. + * + * The Import Registration can be used to delete the proxy associated with an + * endpoint. It is created with the{@link RemoteServiceAdmin#importService} + * method. + * + * @ThreadSafe + */ +public interface ImportRegistration { + /** + * Answer the associated Service Reference for the proxy to the endpoint. + * + * @return A Service Reference to the proxy for the endpoint. + * @throws IllegalStateException Thrown when this object was not properly initialized, see {@link #getException()} + */ + ServiceReference getImportedService(); + + /** + * Answer the associated remote Endpoint Description. + * + * @return A Endpoint Description for the remote endpoint. + * @throws IllegalStateException Thrown when this object was not properly initialized, see {@link #getException()} + */ + EndpointDescription getImportedEndpointDescription(); + + /** + * Unregister this Import Registration. This must close the connection + * to the end endpoint unregister the proxy. After this method returns, + * all other methods must return null. + * + * This method has no effect when the service is already unregistered or in the process off. + */ + void close(); + + /** + * Exception for any error during the import process. + * + * If the Remote Admin for some reasons is unable to create a registration, + * then it must return a Throwable from this method. In this + * case, all other methods must return on this interface must thrown an + * Illegal State Exception. If no error occurred, this method must return + * null. + * + * The error must be set before this Import Registration is returned. + * Asynchronously occurring errors must be reported to the log. + * + * TODO can we assume the proxy service is registered when getException==null? + * + * @return The exception that occurred during the creation of the + * registration or null if no exception occurred. + */ + Throwable getException(); + +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteConstants.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteConstants.java new file mode 100644 index 0000000000..c3f16aecb7 --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteConstants.java @@ -0,0 +1,155 @@ +package org.apache.tuscany.sca.osgi.remoteserviceadmin; + +/** + * Provide the definition of the constants used in the Remote Services API. + * + * TODO Should some of the properties in chapter 13 go the framework constants? + */ +public class RemoteConstants { + private RemoteConstants() { + } + + /** + * The configuration types supported by this Distribution Provider. + * + * Services that are suitable for distribution list the configuration types + * that describe the configuration information for that service in the + * {@link #SERVICE_EXPORTED_CONFIGS} or {@link #SERVICE_IMPORTED_CONFIGS} + * property. + * + * A distribution provider must register a service that has this property + * and enumerate all configuration types that it supports. + * + * The type of this property String+ + * + * @see #SERVICE_EXPORTED_CONFIGS + * @see #SERVICE_IMPORTED_CONFIGS + */ + public final static String REMOTE_CONFIGS_SUPPORTED = "remote.configs.supported"; + + /** + * Service property that lists the intents supported by the distribution + * provider. + * + * Each distribution provider must register a service that has this property + * and enumerate all the supported intents, having any qualified intents + * expanded. + * + * The value of this property is of type String+. + * + * @see #SERVICE_INTENTS + * @see #SERVICE_EXPORTED_INTENTS + * @see #SERVICE_EXPORTED_INTENTS_EXTRA + */ + public final static String REMOTE_INTENTS_SUPPORTED = "remote.intents.supported"; + + /** + * Defines the interfaces under which this service can be exported. + * + * This list must be a subset of the types listed in the objectClass service + * property. The single value of an asterisk ('*' \u002A) indicates all + * interfaces in the registration's objectClass property (not classes). It + * is highly recommended to only export interfaces and not concrete classes + * due to the complexity of creating proxies for some type of classes. + * + * The value of this property is of type String+. + */ + public final static String SERVICE_EXPORTED_INTERFACES = "service.exported.interfaces"; + + /** + * A list of intents that the distribution provider must implement to + * distribute the service. Intents listed in this property are reserved for + * intents that are critical for the code to function correctly, for + * example, ordering of messages. These intents should not be configurable. + * + * The value of this property is of type String+. + */ + public final static String SERVICE_EXPORTED_INTENTS = "service.exported.intents"; + + /** + * Extra intents configured in addition to the the intents specified in + * {@link #SERVICE_EXPORTED_INTENTS}. + * + * These intents are merged with the service.exported.intents and therefore + * have the same semantics. They are extra, so that the + * {@link #SERVICE_EXPORTED_INTENTS} can be set by the bundle developer and + * this property is then set by the administrator/deployer. Bundles should + * make this property configurable, for example through the Configuration + * Admin service. + * + * The value of this property is of type String+. + */ + public final static String SERVICE_EXPORTED_INTENTS_EXTRA = "service.exported.intents.extra"; + + /** + * A list of configuration types that should be used to export the service. + * + * Configuration types can be synonymous or alternatives. + * In principle, a distribution provider should create an endpoint for each + * recognized configuration type, the deployer is responsible that synonyms + * do not clash. + * + * Each configuration type has an associated specification that describes + * how the configuration data for the exported service is represented in an + * OSGi framework. + * + * The value of this property is of type String+. + */ + public final static String SERVICE_EXPORTED_CONFIGS = "service.exported.configs"; + + /** + * Must be set by a distribution provider to true when it + * registers the end-point proxy as an imported service. Can be used by a + * bundle to prevent it from getting an imported service. + * + * The value of this property is not defined, setting it is sufficient. + */ + public final static String SERVICE_IMPORTED = "service.imported"; + + /** + * The configuration type used to import this services, as described in + * {@link #SERVICE_EXPORTED_CONFIGS}. Any associated properties for this + * configuration types must be properly mapped to the importing system. For + * example, a URL in these properties must point to a valid resource when + * used in the importing framework. Configuration types in this property + * must be synonymous. + * + * The value of this property is of type String+. + */ + public final String SERVICE_IMPORTED_CONFIGS = "service.imported.configs"; + + /** + * A list of intents that this service implements. This property has dual + * purpose. A bundle can use this service property to notify the + * distribution provider that these intents are already implemented by the + * exported service object. For an imported service, a distribution provider + * must use this property to convey the combined intents of the exporting + * service and the intents that the distribution providers add. To export a + * service, a distribution provider must recognize all these intents and + * expand any qualified intents. + * + * The value of this property is of type String+. + */ + public final static String SERVICE_INTENTS = "service.intents"; + + /** + * The property key for the endpoint URI. This is a unique id for an + * endpoint following the URI syntax. As far as this specification is + * concerned, this unique id is opaque. + */ + final public static String SERVICE_REMOTE_URI = "service.remote.uri"; + + /** + * The property key for the endpoint service id. This is a unique id for a + * service based on the framework id '.' service id or another model. As far as this specification is + * concerned, this unique id is opaque. + */ + final public static String SERVICE_REMOTE_ID = "service.remote.id"; + + /** + * The property key for the endpoint service id. This is a unique id for a + * service based on the framework id '.' service id or another model. As far as this specification is + * concerned, this unique id is opaque. + */ + final public static String SERVICE_REMOTE_FRAMEWORK_UUID = "service.remote.framework.id"; +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteServiceAdmin.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteServiceAdmin.java new file mode 100644 index 0000000000..09ad0635f3 --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteServiceAdmin.java @@ -0,0 +1,110 @@ +package org.apache.tuscany.sca.osgi.remoteserviceadmin; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.osgi.framework.ServiceReference; + +/** + * A Remote Service Admin manages the import and export of services. + * + * A Distribution Provider can expose a control interface. This interface allows + * the a remote manager to control the export and import of services. + * + * The API allows a remote manager to export a service, to import a service, and + * find out about the current imports and exports. + * + * + * + * @ThreadSafe + */ +public interface RemoteServiceAdmin { + + /** + * Export a service to a given endpoint. The Remote Service Admin must + * create an endpoint from the given description that can be used by other + * Distrbution Providers to connect to this Remote Service Admin and use the + * exported service. This method can return null if the service could not be + * exported because the endpoint could not be implemented by this Remote + * Service Admin. + * + * The properties on a Service Reference are case insensitive while the + * properties on a properties are case sensitive. A value in + * the properties must therefore override any case variant in + * the properties of the Service Reference. + * + * If an endpoint can not be created because no + * {@link EndpointPermission#EXPORT} can be obtained to export this service, + * then this endpoint must be ignored and no Export Registration must be + * included in the returned list. + * + * @param ref The Service Reference to export + * @param properties The properties to create a local endpoint that can be + * implemented by this Remote Service Admin. If this is null, the + * endpoint will be determined by the properties on the service. The + * properties are the same as given for an exported service. They are + * overlaid over any properties the service defines (case + * insensitive). This parameter can be null, this + * should be treated as an empty map. + * @return An Export Registration that combines the Endpoint Description and + * the Service Reference or null if the service could + * not be exported + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + * + * TODO discuss case difference in properties + * + * TODO More exceptions? + * TODO Can you export ANY service by providing the proper properties? + */ + List/* */exportService(ServiceReference ref, Map/* */properties) + throws IllegalArgumentException, UnsupportedOperationException; + + /** + * Import a service from an endpoint. The Remote Service Admin must use the + * given endpoint to create a proxy. This method can return null if the + * service could not be imported. + * + * TODO if the import reg. is valid (getException==null), can we then assume the + * service is registered? + * + * If an endpoint can not be imported because no + * {@link EndpointPermission#IMPORT} can be obtained, then this endpoint + * must be ignored and no Import Registration must included in the returned + * list. + * + * @param endpoint The Endpoint Description to be used for import + * @return An Import Registration that combines the Endpoint Description and + * the Service Reference or null if the endpoint + * could not be imported + */ + ImportRegistration importService(EndpointDescription endpoint); + + /** + * Answer the currently active Export Registrations. + * + * @return A collection of Export Registrations that are currently active. + * @throws SecurityException When the caller no + * {@link EndpointPermission#READ} could be obtained + * + * TODO I guess we must ensure these registrations cannot be closed? Only the owners should be able to close them, + * TODO should we make sure that the list contains the registration objects that the caller created? + */ + Collection/* */getExportedServices(); + + /** + * Answer the currently active Import Registrations. + * + * @throws SecurityException When the caller no EndpointPermission LIST + * could be obtained + * @return A collection of Import Registrations that are currently active. + * @throws SecurityException When the caller no + * {@link EndpointPermission#READ} could be obtained + * + * TODO I guess we must ensure these registrations cannot be closed? Only the owners should be able to close them, + * TODO should we make sure that the list contains the registration objects that the caller created? + */ + Collection/* */getImportedEndpoints(); + +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteServiceAdminEvent.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteServiceAdminEvent.java new file mode 100644 index 0000000000..bdb083b940 --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteServiceAdminEvent.java @@ -0,0 +1,149 @@ +package org.apache.tuscany.sca.osgi.remoteserviceadmin; + +import org.osgi.framework.Bundle; + +/** + * + * Provides the event information for a Remote Admin event. + * + * @Immutable + */ +public class RemoteServiceAdminEvent { + /** + * Add an import registration. The Remote Services Admin will call this + * method when it imports a service. When this service is registered, the + * Remote Service Admin must notify the listener of all existing Import + * Registrations. + * + */ + public static final int IMPORT_REGISTRATION = 1; + + /** + * Add an export registration. The Remote Services Admin will call this + * method when it exports a service. When this service is registered, the + * Remote Service Admin must notify the listener of all existing Export + * Registrations. + */ + public static final int EXPORT_REGISTRATION = 2; + + /** + * Remove an export registration. The Remote Services Admin will call this + * method when it removes the export of a service. + * + */ + public static final int EXPORT_UNREGISTRATION = 3; + + /** + * Remove an import registration. The Remote Services Admin will call this + * method when it removes the import of a service. + * + */ + public static final int IMPORT_UNREGISTRATION = 4; + + /** + * A fatal importing error occurred. The Import Registration has been + * closed. + */ + public static final int IMPORT_ERROR = 5; + + /** + * A fatal exporting error occurred. The Export Registration has been + * closed. + */ + public static final int EXPORT_ERROR = 6; + + /** + * A problematic situation occurred, the export is still active. + */ + public static final int EXPORT_WARNING = 7; + /** + * A problematic situation occurred, the import is still active. + */ + public static final int IMPORT_WARNING = 8; + + private final ImportRegistration importRegistration; + private final ExportRegistration exportRegistration; + private final Throwable exception; + private final int type; + private final Bundle source; + + /** + * Private constructor. + * + * @param type The event type + * @param source The source bundle, must not be null. + * @param importRegistration The importRegistration, can be null. + * @param exportRegistration The exportRegistration, can be null. + * @param exception Any exceptions encountered, can be null + */ + RemoteServiceAdminEvent(int type, + Bundle source, + ImportRegistration importRegistration, + ExportRegistration exportRegistration, + Throwable exception) { + this.type = type; + this.source = source; + this.importRegistration = importRegistration; + this.exportRegistration = exportRegistration; + this.exception = exception; + } + + /** + * Create a Remote Service Admin Event for an export issue. + * + * @param type The event type + * @param source The source bundle, must not be null. + * @param exportRegistration The exportRegistration, can not be null. + * @param exception Any exceptions encountered, can be null + */ + public RemoteServiceAdminEvent(int type, Bundle source, ExportRegistration exportRegistration, Throwable exception) { + this(type, source, null, exportRegistration, exception); + } + + /** + * Create a Remote Service Admin Event for an import issue. + * + * @param type The event type + * @param source The source bundle, must not be null. + * @param importRegistration The importRegistration, can not be null. + * @param exception Any exceptions encountered, can be null + */ + public RemoteServiceAdminEvent(int type, Bundle source, ImportRegistration importRegistration, Throwable exception) { + this(type, source, importRegistration, null, exception); + } + + /** + * @return the importRegistration or null + */ + public ImportRegistration getImportRegistration() { + return importRegistration; + } + + /** + * @return the exportRegistration or null + */ + public ExportRegistration getExportRegistration() { + return exportRegistration; + } + + /** + * @return the exception or null + */ + public Throwable getException() { + return exception; + } + + /** + * @return the type + */ + public int getType() { + return type; + } + + /** + * @return the source + */ + public Bundle getSource() { + return source; + } +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteServiceAdminListener.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteServiceAdminListener.java new file mode 100644 index 0000000000..28a5958704 --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/RemoteServiceAdminListener.java @@ -0,0 +1,16 @@ +package org.apache.tuscany.sca.osgi.remoteserviceadmin; + +/** + * A Remote Service Admin Listener is notified asynchronously of any export or + * import registrations and unregistrations. + * + * @ThreadSafe + */ + +public interface RemoteServiceAdminListener { + /** + * Receive a Remote Service Admin event. + * @param event + */ + void remoteAdminEvent(RemoteServiceAdminEvent event); +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/AbstractOSGiServiceHandler.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/AbstractOSGiServiceHandler.java new file mode 100644 index 0000000000..a043e33c32 --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/AbstractOSGiServiceHandler.java @@ -0,0 +1,90 @@ +/* + * 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; + + /** + * @param context + * @param clazz + * @param customizer + */ + protected AbstractOSGiServiceHandler(BundleContext context) { + this.context = context; + } + + 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.newInstance(); + 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; + } + +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointHelper.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointHelper.java new file mode 100644 index 0000000000..8ac949ccc5 --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointHelper.java @@ -0,0 +1,82 @@ +/* + * 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 java.util.UUID; + +import org.apache.tuscany.sca.assembly.Endpoint; +import org.apache.tuscany.sca.interfacedef.Interface; +import org.apache.tuscany.sca.interfacedef.java.JavaInterface; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.EndpointDescription; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.RemoteConstants; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; + +/** + * Implementation of {@link EndpointDescription} + */ +public class EndpointHelper { + private final static String FRAMEWORK_UUID = "org.osgi.framework.uuid"; + private EndpointHelper() { + } + + public static EndpointDescription createEndpointDescription(BundleContext bundleContext, Endpoint endpoint) { + return new EndpointDescription(getProperties(bundleContext, endpoint)); + } + + private static List getInterfaces(Endpoint endpoint) { + Interface intf = endpoint.getInterfaceContract().getInterface(); + JavaInterface javaInterface = (JavaInterface)intf; + return Collections.singletonList(javaInterface.getName()); + } + + private static Map getProperties(BundleContext bundleContext, Endpoint endpoint) { + Map props = new HashMap(); + + String uuid = getFrameworkUUID(bundleContext); + + props.put(RemoteConstants.SERVICE_REMOTE_FRAMEWORK_UUID, uuid); + props.put(RemoteConstants.SERVICE_REMOTE_URI, endpoint.getURI()); + props.put(RemoteConstants.SERVICE_REMOTE_ID, String.valueOf(System.currentTimeMillis())); + props.put(RemoteConstants.SERVICE_EXPORTED_CONFIGS, new String[] {"org.osgi.sca"}); + props.put(Endpoint.class.getName(), endpoint); + List interfaces = getInterfaces(endpoint); + props.put(Constants.OBJECTCLASS, interfaces.toArray(new String[interfaces.size()])); + return props; + } + + public synchronized static String getFrameworkUUID(BundleContext bundleContext) { + String uuid = System.getProperty(FRAMEWORK_UUID); + if (uuid == null) { + uuid = UUID.randomUUID().toString(); + } + System.setProperty(FRAMEWORK_UUID, uuid); + return uuid; + } + + public static Endpoint getEndpoint(EndpointDescription endpointDescription) { + return (Endpoint)endpointDescription.getProperties().get(Endpoint.class.getName()); + } + +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointIntrospector.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointIntrospector.java new file mode 100644 index 0000000000..c88632407c --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointIntrospector.java @@ -0,0 +1,494 @@ +/* + * 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.implementation.osgi.OSGiProperty.SERVICE_EXPORTED_INTENTS; +import static org.apache.tuscany.sca.implementation.osgi.OSGiProperty.SERVICE_EXPORTED_INTENTS_EXTRA; +import static org.apache.tuscany.sca.implementation.osgi.OSGiProperty.SERVICE_EXPORTED_INTERFACES; +import static org.osgi.framework.Constants.OBJECTCLASS; +import static org.osgi.framework.Constants.SERVICE_ID; + +import java.io.IOException; +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 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.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.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.remoteserviceadmin.EndpointDescription; +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.oasisopen.sca.ServiceRuntimeException; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +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 JavaInterfaceFactory javaInterfaceFactory; + private Deployer deployer; + private ServiceTracker discoveryTracker; + + /** + * @param intentName + * @return + */ + private static QName getQName(String intentName) { + QName qname; + if (intentName.startsWith("{")) { + int i = intentName.indexOf('}'); + if (i != -1) { + qname = new QName(intentName.substring(1, i), intentName.substring(i + 1)); + } else { + throw new IllegalArgumentException("Invalid intent: " + intentName); + } + } else { + // Default to SCA namespace + qname = new QName(Base.SCA11_NS, intentName); + } + return qname; + } + + /** + * @param context TODO + * @param registry + */ + public EndpointIntrospector(BundleContext context, ExtensionPointRegistry registry, ServiceTracker discoveryTracker) { + super(); + this.context = context; + this.discoveryTracker = discoveryTracker; + this.registry = registry; + this.factories = registry.getExtensionPoint(FactoryExtensionPoint.class); + this.modelResolvers = registry.getExtensionPoint(ModelResolverExtensionPoint.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 getIntents(String[] intents) { + if (intents == null || intents.length == 0) { + return Collections.emptyList(); + } + List intentList = new ArrayList(); + 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.

+ * 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 getProperties(ServiceReference reference, Map props) { + String[] names = reference.getPropertyKeys(); + Map properties = new HashMap(); + 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 nameMap = new HashMap(); + if (names != null) { + for (String name : names) { + nameMap.put(name.toLowerCase(), name); + } + } + for (Map.Entry p : props.entrySet()) { + if (Constants.OBJECTCLASS.equalsIgnoreCase(p.getKey())) { + throw new IllegalArgumentException(Constants.OBJECTCLASS + " property cannot be overridden."); + } else if (Constants.SERVICE_ID.equalsIgnoreCase(p.getKey())) { + throw new IllegalArgumentException(Constants.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 names = new ArrayList(); + 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 props) throws Exception { + Map properties = getProperties(reference, props); + + OSGiProperty serviceID = implementationFactory.createOSGiProperty(); + serviceID.setName(SERVICE_ID); + // The service.id is Long + serviceID.setValue(String.valueOf(reference.getProperty(SERVICE_ID))); + + 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); + component.setAutowire(Boolean.TRUE); + + composite.getComponents().add(component); + + Bundle bundle = reference.getBundle(); + OSGiImplementation implementation = implementationFactory.createOSGiImplementation(); + + implementation.setBundle(bundle); + component.setImplementation(implementation); + implementation.setUnresolved(false); + + String[] remoteInterfaces = getStrings(reference.getProperty(SERVICE_EXPORTED_INTERFACES)); + if (remoteInterfaces == null || remoteInterfaces.length > 0 && "*".equals(remoteInterfaces[0])) { + remoteInterfaces = getStrings(reference.getProperty(OBJECTCLASS)); + } else { + remoteInterfaces = parse(remoteInterfaces); + String[] objectClasses = getStrings(reference.getProperty(OBJECTCLASS)); + Set objectClassSet = new HashSet(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"); + } + } + for (String intf : remoteInterfaces) { + Service service = assemblyFactory.createService(); + JavaInterfaceContract interfaceContract = javaInterfaceFactory.createJavaInterfaceContract(); + Class interfaceClass = bundle.loadClass(intf); + JavaInterface javaInterface = javaInterfaceFactory.createJavaInterface(interfaceClass); + interfaceContract.setInterface(javaInterface); + if (javaInterface.getCallbackClass() != null) { + interfaceContract.setCallbackInterface(javaInterfaceFactory.createJavaInterface(javaInterface + .getCallbackClass())); + } + + service.setName(interfaceClass.getSimpleName()); + service.setInterfaceContract(interfaceContract); + + service.getExtensions().add(serviceID); + + implementation.getServices().add(service); + + ComponentService componentService = assemblyFactory.createComponentService(); + componentService.setName(service.getName()); + component.getServices().add(componentService); + componentService.setService(service); + } + + String[] requiredIntents = getStrings(properties.get(SERVICE_EXPORTED_INTENTS)); + List intents = getIntents(requiredIntents); + String[] requiredIntentsExtra = getStrings(properties.get(SERVICE_EXPORTED_INTENTS_EXTRA)); + List extraIntents = getIntents(requiredIntentsExtra); + + String[] bindingNames = getStrings(properties.get(SCA_BINDINGS)); + Collection bindings = loadBindings(bindingNames); + + for (ComponentService componentService : component.getServices()) { + componentService.getRequiredIntents().addAll(intents); + componentService.getRequiredIntents().addAll(extraIntents); + componentService.getBindings().addAll(bindings); + } + + // FIXME: Should we scan the owning bundle to create the SCA contribution? + Contribution contribution = contributionFactory.createContribution(); + contribution.setURI("urn:" + id); + contribution.setLocation(bundle.getEntry("/").toString()); + contribution.getDeployables().add(composite); + ModelResolver modelResolver = new ExtensibleModelResolver(contribution, modelResolvers, factories); + contribution.setModelResolver(modelResolver); + contribution.setUnresolved(true); + return contribution; + } + + public Contribution introspect(Bundle bundle, EndpointDescription endpoint) throws Exception { + Endpoint ep = (Endpoint)endpoint.getProperties().get(Endpoint.class.getName()); + if (ep != null) { + return introspect(bundle, ep); + } + Map properties = endpoint.getProperties(); + List remoteInterfaces = endpoint.getInterfaces(); + + 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 = javaInterfaceFactory.createJavaInterfaceContract(); + Class interfaceClass = bundle.loadClass(intf); + JavaInterface javaInterface = javaInterfaceFactory.createJavaInterface(interfaceClass); + interfaceContract.setInterface(javaInterface); + if (javaInterface.getCallbackClass() != null) { + interfaceContract.setCallbackInterface(javaInterfaceFactory.createJavaInterface(javaInterface + .getCallbackClass())); + } + + reference.setName("ref" + (count++)); + reference.setInterfaceContract(interfaceContract); + + implementation.getReferences().add(reference); + + ComponentReference componentReference = assemblyFactory.createComponentReference(); + componentReference.setName(reference.getName()); + component.getReferences().add(componentReference); + componentReference.setReference(reference); + componentReference.setWiredByImpl(true); + } + + String[] requiredIntents = getStrings(properties.get(SERVICE_EXPORTED_INTENTS)); + List intents = getIntents(requiredIntents); + + String[] bindingNames = getStrings(properties.get(SCA_BINDINGS)); + Collection bindings = loadBindings(bindingNames); + + for (ComponentReference componentReference : component.getReferences()) { + componentReference.getRequiredIntents().addAll(intents); + componentReference.getBindings().addAll(bindings); + } + // FIXME: Should we scan the owning bundle to create the SCA contribution? + Contribution contribution = contributionFactory.createContribution(); + contribution.setURI("urn:" + id); + contribution.setLocation(bundle.getEntry("/").toString()); + contribution.getDeployables().add(composite); + ModelResolver modelResolver = new ExtensibleModelResolver(contribution, modelResolvers, factories); + contribution.setModelResolver(modelResolver); + contribution.setUnresolved(true); + return contribution; + } + + public Contribution introspect(Bundle bundle, Endpoint endpoint) throws Exception { + 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); + + Reference reference = assemblyFactory.createReference(); + Service service = endpoint.getService().getService(); + reference.setInterfaceContract(service.getInterfaceContract()); + reference.setName("ref"); + + reference.getBindings().add(endpoint.getBinding()); + + /* + reference.getRequiredIntents().addAll(service.getRequiredIntents()); + reference.getPolicySets().addAll(service.getPolicySets()); + */ + + implementation.getReferences().add(reference); + + ComponentReference componentReference = assemblyFactory.createComponentReference(); + component.getReferences().add(componentReference); + componentReference.setReference(reference); + componentReference.setName(reference.getName()); + componentReference.setWiredByImpl(true); + + // FIXME: Should we scan the owning bundle to create the SCA contribution? + Contribution contribution = contributionFactory.createContribution(); + contribution.setURI("urn:" + id); + contribution.setLocation(bundle.getEntry("/").toString()); + contribution.getDeployables().add(composite); + ModelResolver modelResolver = new ExtensibleModelResolver(contribution, modelResolvers, factories); + contribution.setModelResolver(modelResolver); + contribution.setUnresolved(true); + return contribution; + } + + private Collection 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 bindingMap = new HashMap(); + if (discoveryService != null) { + for (ExtenderConfiguration config : discoveryService.getConfigurations()) { + for (SCAConfig sc : config.getSCAConfigs()) { + for (QName bindingName : bindingNames) { + if (sc.getTargetNamespace().equals(bindingName.getNamespaceURI())) { + for (Binding binding : sc.getBindings()) { + if (bindingName.getLocalPart().equals(binding.getName())) { + bindingMap.put(bindingName, binding); + break; + } + } + } + } + } + } + } + for (QName bindingName : bindingNames) { + if (!bindingMap.containsKey(bindingName)) { + throw new ServiceRuntimeException("Binding cannot be resolved: " + bindingName); + } + } + return bindingMap.values(); + } + + /** + * In OSGi, the value of String+ can be a single String, String[] or Collection + * @param value + * @return + */ + private String[] getStrings(Object value) { + if (value == null) { + return null; + } + if (value instanceof String) { + return new String[] {(String)value}; + } else if (value instanceof Collection) { + Collection collection = (Collection)value; + return collection.toArray(new String[collection.size()]); + } + return (String[])value; + + } + +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ExportRegistrationImpl.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ExportRegistrationImpl.java new file mode 100644 index 0000000000..58d72f389f --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ExportRegistrationImpl.java @@ -0,0 +1,89 @@ +/* + * 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.apache.tuscany.sca.osgi.remoteserviceadmin.EndpointDescription; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.ExportRegistration; +import org.osgi.framework.ServiceReference; + +/** + * Implementation of {@link ExportRegistration} + */ +public class ExportRegistrationImpl implements ExportRegistration { + private Node node; + private ServiceReference exportedService; + private EndpointDescription endpointDescription; + private Throwable exception; + + /** + * @param exportedService + * @param endpointDescription + * @param exception + */ + public ExportRegistrationImpl(Node node, + ServiceReference exportedService, + EndpointDescription endpointDescription, + Throwable exception) { + super(); + this.node = node; + this.exportedService = exportedService; + this.endpointDescription = endpointDescription; + this.exception = exception; + } + + /** + * @param exportedService + * @param endpointDescription + */ + public ExportRegistrationImpl(Node node, ServiceReference exportedService, EndpointDescription endpointDescription) { + this(node, exportedService, endpointDescription, null); + } + + /** + * @see org.apache.tuscany.sca.osgi.remoteserviceadmin.ExportRegistration#close() + */ + public void close() { + if (node != null) { + node.stop(); + node = null; + } + exception = null; + endpointDescription = null; + exportedService = null; + } + + public ServiceReference getExportedService() { + return exportedService; + } + + public EndpointDescription getEndpointDescription() { + return endpointDescription; + } + + public Throwable getException() { + return exception; + } + + public Node getNode() { + return node; + } + +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ImportRegistrationImpl.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ImportRegistrationImpl.java new file mode 100644 index 0000000000..d085bd282d --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/ImportRegistrationImpl.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.osgi.remoteserviceadmin.impl; + +import org.apache.tuscany.sca.node.Node; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.EndpointDescription; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.ImportRegistration; +import org.osgi.framework.ServiceReference; + +/** + * + */ +public class ImportRegistrationImpl implements ImportRegistration { + private Node node; + private ServiceReference exportedService; + private EndpointDescription endpointDescription; + private Throwable exception; + + /** + * @param exportedService + * @param endpointDescription + * @param exception + */ + public ImportRegistrationImpl(Node node, + ServiceReference exportedService, + EndpointDescription endpointDescription, + Throwable exception) { + super(); + this.node = node; + this.exportedService = exportedService; + this.endpointDescription = endpointDescription; + this.exception = exception; + } + + /** + * @param exportedService + * @param endpointDescription + */ + public ImportRegistrationImpl(Node node, ServiceReference exportedService, EndpointDescription endpointDescription) { + super(); + this.node = node; + this.exportedService = exportedService; + this.endpointDescription = endpointDescription; + } + + /** + * @see org.apache.tuscany.sca.osgi.remoteserviceadmin.ImportRegistration#close() + */ + public void close() { + if (node != null) { + node.stop(); + node = null; + } + exception = null; + endpointDescription = null; + exportedService = null; + } + + public ServiceReference getImportedService() { + return exportedService; + } + + public EndpointDescription getImportedEndpointDescription() { + return endpointDescription; + } + + public Throwable getException() { + return exception; + } + + public Node getNode() { + return node; + } + +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiHelper.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiHelper.java new file mode 100644 index 0000000000..d5a2b7aa95 --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiHelper.java @@ -0,0 +1,135 @@ +/* + * 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.net.URL; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; + +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 { + private OSGiHelper() { + } + + /** + * In OSGi, the value of String+ can be a single String, String[] or Collection + * @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 collection = (Collection)value; + return collection.toArray(new String[collection.size()]); + } + return (String[])value; + + } + + public static Collection 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 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 getConfiguration(Bundle bundle, String header, String defaultValue) { + String value = (String)bundle.getHeaders().get(header); + if (value == null) { + return Collections.emptyList(); + } + String paths[] = value.trim().split("( |\t|\n|\r|\f|,)+"); + if (paths.length == 0) { + if (defaultValue != null) { + paths = new String[] {defaultValue}; + } else { + paths = new String[0]; + } + } + Collection files = new HashSet(); + for (String path : paths) { + 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 entries = bundle.findEntries(root, pattern, false); + if (entries != null) { + while (entries.hasMoreElements()) { + files.add(entries.nextElement()); + } + } + } + return files; + } + +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiServiceExporter.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiServiceExporter.java new file mode 100644 index 0000000000..02603417cc --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiServiceExporter.java @@ -0,0 +1,108 @@ +/* + * 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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +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.node.configuration.NodeConfiguration; +import org.apache.tuscany.sca.node.impl.NodeImpl; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.EndpointDescription; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.ExportRegistration; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +/** + * Watching and exporting OSGi services + */ +public class OSGiServiceExporter extends AbstractOSGiServiceHandler implements ServiceTrackerCustomizer { + + /** + * @param context + * @param clazz + * @param customizer + */ + public OSGiServiceExporter(BundleContext context) { + super(context); + } + + public void start() { + init(); + } + + public Object addingService(ServiceReference reference) { + return exportService(reference, null); + } + + public List exportService(ServiceReference reference, Map 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); + } + configuration.setURI(contribution.getURI()); + configuration.getExtensions().add(reference.getBundle()); + // FIXME: Configure the domain and node URI + NodeImpl node = new NodeImpl(nodeFactory, configuration, Collections.singletonList(contribution)); + node.start(); + List exportedServices = new ArrayList(); + Component component = contribution.getDeployables().get(0).getComponents().get(0); + ComponentService service = component.getServices().get(0); + for (Endpoint endpoint : service.getEndpoints()) { + EndpointDescription endpointDescription = createEndpointDescription(context, endpoint); + ExportRegistration exportRegistration = + new ExportRegistrationImpl(node, reference, endpointDescription); + exportedServices.add(exportRegistration); + } + return exportedServices; + } else { + return null; + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public void modifiedService(ServiceReference reference, Object service) { + removedService(reference, service); + exportService(reference, null); + } + + public void removedService(ServiceReference reference, Object service) { + List exportedServices = (List)service; + for(ExportRegistration exportRegistration: exportedServices) { + exportRegistration.close(); + } + } +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiServiceImporter.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiServiceImporter.java new file mode 100644 index 0000000000..59a21ade06 --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/OSGiServiceImporter.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.osgi.remoteserviceadmin.impl; + +import java.util.Collections; + +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.Node; +import org.apache.tuscany.sca.node.configuration.NodeConfiguration; +import org.apache.tuscany.sca.node.impl.NodeImpl; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.EndpointDescription; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.ImportRegistration; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; + +/** + * Watching and exporting OSGi services + */ +public class OSGiServiceImporter extends AbstractOSGiServiceHandler { + + /** + * @param context + * @param clazz + * @param customizer + */ + public OSGiServiceImporter(BundleContext context) { + super(context); + } + + public void start() { + // Defer init() to importService() + } + + 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); + } + 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() + + ")"); + return new ImportRegistrationImpl(node, serviceReference, endpointDescription); + } else { + return null; + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public void unimportService(ImportRegistration importRegistration) { + Node node = (Node)importRegistration.getImportedService().getProperty("sca.node"); + node.stop(); + } + +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/RemoteServiceAdminImpl.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/RemoteServiceAdminImpl.java new file mode 100644 index 0000000000..bc2e3ce012 --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/RemoteServiceAdminImpl.java @@ -0,0 +1,284 @@ +/* + * 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.osgi.remoteserviceadmin.EndpointDescription; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.ExportRegistration; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.ImportRegistration; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.RemoteServiceAdmin; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.RemoteServiceAdminEvent; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.RemoteServiceAdminListener; +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.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 importedEndpoints = new ArrayList(); + private Collection exportedServices = new ArrayList(); + + public RemoteServiceAdminImpl(BundleContext context) { + this.context = context; + } + + public void start() { + this.exporter = new OSGiServiceExporter(context); + this.importer = new OSGiServiceImporter(context); + exporter.start(); + importer.start(); + registration = context.registerService(RemoteServiceAdmin.class.getName(), this, null); + Hashtable props = new Hashtable(); + 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 : exportedServices) { + exportRegistration.close(); + } + exportedServices.clear(); + for (ImportRegistration importRegistration : importedEndpoints) { + importRegistration.close(); + } + importedEndpoints.clear(); + if (importer != null) { + importer.stop(); + importer = null; + } + if (exporter != null) { + exporter.stop(); + exporter = null; + } + } + + /** + * @see org.apache.tuscany.sca.osgi.remoteserviceadmin.RemoteServiceAdmin#exportService(org.osgi.framework.ServiceReference, + * java.util.Map) + */ + public List exportService(ServiceReference ref, Map properties) { + List exportRegistrations = exporter.exportService(ref, properties); + if (exportRegistrations != null) { + exportedServices.addAll(exportRegistrations); + fireExportEvents(ref.getBundle(), exportRegistrations); + } + return exportRegistrations; + } + + private void fireExportEvents(Bundle source, List exportRegistrations) { + for (ExportRegistration registration : exportRegistrations) { + RemoteServiceAdminEvent rsaEvent = + new RemoteServiceAdminEvent(RemoteServiceAdminEvent.EXPORT_REGISTRATION, source, registration, + 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 props = new HashMap(); + /* + *

    +
  • bundle – (Bundle) The Remote Service Admin bundle +
  • bundle-id – (Long) The id of the Blueprint bundle. +
  • bundle-symbolicname – (String) The Bundle Symbolic Name of the + Remote Service Admin bundle. +
  • bundle-version - (Version) The version of the Blueprint bundle. +
  • cause – The exception, if present. +
  • import.registration – An imported endpoint, if present +
  • export.registration – An exported endpoint, if present +
  • service.remote.id – Remote service UUID, if present +
  • service.remote.uuid – Remote service UUID, if present +
  • service.remote.uri – (String) The URI of the endpoint, if present +
  • objectClass – (String[]) The interface names, if present +
  • service.imported.configs – (String+) The configuration types of the + imported services, if present +
  • timestamp – (Long) The time when the event occurred +
  • event – (RemoteServiceAdminEvent) The RemoteServiceAdminEvent + object that caused this event. +
+ */ + 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.getImportRegistration()); + props.put("export.registration", rsaEvent.getExportRegistration()); + EndpointDescription ep = null; + if (rsaEvent.getImportRegistration() != null) { + ep = rsaEvent.getImportRegistration().getImportedEndpointDescription(); + } else { + ep = rsaEvent.getExportRegistration().getEndpointDescription(); + } + props.put("service.remote.id", ep.getRemoteServiceID()); + props.put("service.remote.uuid", ep.getRemoteFrameworkUUID()); + props.put("service.remote.uri", ep.getRemoteURI()); + props.put("objectClass", ep.getInterfaces()); + props.put("service.imported.configs", ep.getConfigurationTypes()); + props.put("timestamp", new Long(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, 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.apache.tuscany.sca.osgi.remoteserviceadmin.RemoteServiceAdmin#getExportedServices() + */ + public Collection getExportedServices() { + return exportedServices; + } + + /** + * @see org.apache.tuscany.sca.osgi.remoteserviceadmin.RemoteServiceAdmin#getImportedEndpoints() + */ + public Collection getImportedEndpoints() { + return importedEndpoints; + } + + /** + * @see org.apache.tuscany.sca.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); + importedEndpoints.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"); + if (domainRegistry != null) { + exporter.setDomainRegistry(domainRegistry); + importer.setDomainRegistry(domainRegistry); + } + } +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/TopologyManagerImpl.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/TopologyManagerImpl.java new file mode 100644 index 0000000000..1967fa6e4a --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/TopologyManagerImpl.java @@ -0,0 +1,410 @@ +/* + * 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.apache.tuscany.sca.osgi.remoteserviceadmin.RemoteConstants.SERVICE_EXPORTED_CONFIGS; +import static org.apache.tuscany.sca.osgi.remoteserviceadmin.RemoteConstants.SERVICE_IMPORTED; + +import java.util.Collection; +import java.util.Dictionary; +import java.util.HashMap; +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.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.EndpointDescription; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.EndpointListener; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.ExportRegistration; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.ImportRegistration; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.RemoteServiceAdmin; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.RemoteServiceAdminEvent; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.RemoteServiceAdminListener; +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.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 ServiceRegistration registration; + private ServiceRegistration endpointListener; + + private ServiceTracker remotableServices; + + // Service listeners keyed by the filter + private CollectionMap serviceListeners = new CollectionMap(); + + private CollectionMap exportedServices = + new CollectionMap(); + private CollectionMap importedServices = + new CollectionMap(); + + private Filter remotableServiceFilter; + + public TopologyManagerImpl(BundleContext context) { + this.context = 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); + + } + + /** + * @see org.osgi.framework.hooks.service.EventHook#event(org.osgi.framework.ServiceEvent, + * java.util.Collection) + */ + /* + public void event(ServiceEvent event, Collection contexts) { + ServiceReference reference = event.getServiceReference(); + if (!remotableServiceFilter.match(reference)) { + // Only export remotable services that are for SCA + return; + } + switch (event.getType()) { + case ServiceEvent.REGISTERED: + exportService(reference); + break; + case ServiceEvent.UNREGISTERING: + unexportService(reference); + break; + case ServiceEvent.MODIFIED: + case ServiceEvent.MODIFIED_ENDMATCH: + // First check if the property changes will impact + // Call remote admin to update the service + unexportService(reference); + exportService(reference); + break; + } + } + */ + + 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 exportRegistrations = exportedServices.get(reference); + if (exportRegistrations != null) { + for (Iterator 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; + List 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) { + synchronized (serviceListeners) { + try { + Collection listenerInfos = (Collection)listeners; + boolean changed = false; + for (ListenerInfo l : listenerInfos) { + if (!l.isRemoved() && l.getBundleContext() != context) { + String key = l.getFilter(); + if (key == null) { + // key = ""; + // FIXME: It should always match, let's ignore it for now + logger.warning("Service listner without a filter is skipped: " + l); + continue; + } + Collection infos = serviceListeners.get(key); + if (infos == null) { + infos = new HashSet(); + serviceListeners.put(key, infos); + } + infos.add(l); + changed = true; + } + } + if (changed) { + updateEndpointListenerScope(); + } + } 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() { + Set filterSet = serviceListeners.keySet(); + + Dictionary props = new Hashtable(); + props.put(ENDPOINT_LISTENER_SCOPE, filterSet.toArray(new String[filterSet.size()])); + endpointListener.setProperties(props); + } + + private CollectionMap, ListenerInfo> findServiceListeners(EndpointDescription endpointDescription, + String matchedFilter) { + synchronized (serviceListeners) { + + // First find all the listeners that have the matching filter + Collection listeners = serviceListeners.get(matchedFilter); + if (listeners == null) { + return null; + } + + // Try to partition the listeners by the interface classes + List interfaceNames = endpointDescription.getInterfaces(); + CollectionMap, ListenerInfo> interfaceToListeners = new CollectionMap, ListenerInfo>(); + for (String i : interfaceNames) { + for (Iterator it = listeners.iterator(); it.hasNext();) { + try { + ListenerInfo listener = it.next(); + if (listener.isRemoved()) { + it.remove(); + continue; + } + try { + 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; + } + } + + /** + * @see org.osgi.framework.hooks.service.ListenerHook#removed(java.util.Collection) + */ + public void removed(Collection listeners) { + synchronized (serviceListeners) { + try { + Collection listenerInfos = (Collection)listeners; + boolean changed = false; + for (ListenerInfo l : listenerInfos) { + if (registration != null && l.getBundleContext() != context) { + String key = l.getFilter(); + if (key == null) { + continue; + } + if (serviceListeners.removeValue(key, l)) { + changed = true; + } + } + } + if (changed) { + updateEndpointListenerScope(); + } + } 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.apache.tuscany.sca.osgi.remoteserviceadmin.EndpointListener#addEndpoint(org.apache.tuscany.sca.osgi.remoteserviceadmin.EndpointDescription, + * java.lang.String) + */ + public void endpointAdded(EndpointDescription endpoint, String matchedFilter) { + importService(endpoint, matchedFilter); + } + + /** + * @see org.apache.tuscany.sca.osgi.remoteserviceadmin.EndpointListener#removeEndpoint(org.apache.tuscany.sca.osgi.remoteserviceadmin.EndpointDescription) + */ + public void endpointRemoved(EndpointDescription endpoint, String matchedFilter) { + unimportService(endpoint); + } + + private void importService(EndpointDescription endpoint, String matchedFilter) { + Object[] admins = remoteAdmins.getServices(); + if (admins == null) { + logger.warning("No RemoteAdmin services are available."); + return; + } + + CollectionMap, ListenerInfo> interfaceToListeners = findServiceListeners(endpoint, matchedFilter); + for (Map.Entry, Collection> e : interfaceToListeners.entrySet()) { + Class interfaceClass = e.getKey(); + Collection listeners = e.getValue(); + // Get a listener + ListenerInfo listener = listeners.iterator().next(); + Bundle bundle = listener.getBundleContext().getBundle(); + + Map props = new HashMap(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(endpoint, importRegistration); + } + } + } + } + } + + private void unimportService(EndpointDescription endpoint) { + // Call remote admin to unimport the service + Collection importRegistrations = importedServices.get(endpoint); + if (importRegistrations != null) { + for (Iterator 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; + } + synchronized (serviceListeners) { + serviceListeners.clear(); + } + } + +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/AbstractDiscoveryService.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/AbstractDiscoveryService.java new file mode 100644 index 0000000000..5b0b311d7a --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/AbstractDiscoveryService.java @@ -0,0 +1,280 @@ +/* + * 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.EndpointListener.ENDPOINT_LISTENER_SCOPE; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +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.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.EndpointDescription; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.EndpointListener; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.RemoteConstants; +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.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 Map> filtersToListeners = new HashMap>(); + // this is effectively a set which allows for multiple service descriptions with the + // same interface name but different properties and takes care of itself with respect to concurrency + protected Map servicesInfo = new ConcurrentHashMap(); + private Map> listenersToFilters = + new HashMap>(); + private ServiceTracker trackerTracker; + + public AbstractDiscoveryService(BundleContext context) { + super(); + this.context = context; + } + + public void start() { + // 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.newInstance(); + 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 getProperties() { + Dictionary headers = context.getBundle().getHeaders(); + Hashtable props = new Hashtable(); + 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 synchronized void cacheTracker(ServiceReference reference, Object service) { + if (service instanceof EndpointListener) { + EndpointListener listener = (EndpointListener)service; + Collection filters = + addTracker(reference, listener, ENDPOINT_LISTENER_SCOPE, filtersToListeners, listenersToFilters); + + triggerCallbacks(null, filters, listener, true); + } + } + + private synchronized void clearTracker(Object service) { + if (service instanceof EndpointListener) { + removeTracker((EndpointListener)service, filtersToListeners, listenersToFilters); + } + } + + private synchronized void updateTracker(ServiceReference reference, Object service) { + if (service instanceof EndpointListener) { + EndpointListener listener = (EndpointListener)service; + if (logger.isLoggable(Level.FINE)) { + logger.fine("updating listener: " + listener); + } + Collection oldFilters = removeTracker(listener, filtersToListeners, listenersToFilters); + + Collection newFilters = + addTracker(reference, listener, ENDPOINT_LISTENER_SCOPE, filtersToListeners, listenersToFilters); + + triggerCallbacks(oldFilters, newFilters, listener, true); + } + } + + private void triggerCallbacks(Collection oldInterest, + Collection newInterest, + EndpointListener listener, + boolean isFilter) { + // compute delta between old & new interfaces/filters and + // trigger callbacks for any entries in servicesInfo that + // match any *additional* interface/filters + Collection deltaInterest = new ArrayList(); + if (newInterest != null && !newInterest.isEmpty()) { + if (oldInterest == null || oldInterest.isEmpty()) { + deltaInterest.addAll(newInterest); + } else { + Iterator i = newInterest.iterator(); + while (i.hasNext()) { + String next = (String)i.next(); + if (!oldInterest.contains(next)) { + deltaInterest.add(next); + } + } + } + } + + if (logger.isLoggable(Level.FINE)) { + if (servicesInfo.size() > 0) { + logger.fine("search for matches to trigger callbacks with delta: " + deltaInterest); + } else { + logger.fine("nothing to search for matches to trigger callbacks with delta: " + deltaInterest); + } + } + Iterator i = deltaInterest.iterator(); + while (i.hasNext()) { + String next = i.next(); + for (EndpointDescription sd : servicesInfo.keySet()) { + triggerCallbacks(listener, next, sd, ADDED); + } + } + } + + private void triggerCallbacks(EndpointListener listener, String matchedFilter, EndpointDescription sd, int type) { + switch (type) { + case ADDED: + listener.endpointAdded(sd, matchedFilter); + break; + case REMOVED: + listener.endpointRemoved(sd, matchedFilter); + break; + case MODIFIED: + listener.endpointRemoved(sd, matchedFilter); + listener.endpointAdded(sd, matchedFilter); + break; + } + } + + private boolean filterMatches(String filterValue, EndpointDescription sd) { + Filter filter = OSGiHelper.createFilter(context, filterValue); + Hashtable props = new Hashtable(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; + } + + static Collection removeTracker(EndpointListener listener, + Map> forwardMap, + Map> reverseMap) { + Collection collection = reverseMap.get(listener); + if (collection != null && !collection.isEmpty()) { + reverseMap.remove(listener); + Iterator i = collection.iterator(); + while (i.hasNext()) { + String element = i.next(); + if (forwardMap.containsKey(element)) { + forwardMap.get(element).remove(listener); + } else { + // if the element wasn't on the forwardmap, its a new element and + // shouldn't be returned as part of the collection of old ones + i.remove(); + } + } + } + return collection; + } + + @SuppressWarnings("unchecked") + static Collection addTracker(ServiceReference reference, + EndpointListener listener, + String property, + Map> forwardMap, + Map> reverseMap) { + Collection collection = OSGiHelper.getStringCollection(reference, property); + if (logger.isLoggable(Level.FINE)) { + logger.fine("adding listener: " + listener + + " collection: " + + collection + + " registered against prop: " + + property); + } + if (collection != null && !collection.isEmpty()) { + reverseMap.put(listener, new ArrayList(collection)); + Iterator i = collection.iterator(); + while (i.hasNext()) { + String element = i.next(); + if (forwardMap.containsKey(element)) { + forwardMap.get(element).add(listener); + } else { + List trackerList = new ArrayList(); + trackerList.add(listener); + forwardMap.put(element, trackerList); + } + } + } + return collection; + } + + protected void endpointChanged(EndpointDescription sd, int type) { + for (Map.Entry> entry : listenersToFilters.entrySet()) { + for (String filter : entry.getValue()) { + if (filterMatches(filter, sd)) { + triggerCallbacks(entry.getKey(), filter, sd, type); + } + } + } + } + +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/Discovery.java b/branches/sca-java-2.0-M4/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/branches/sca-java-2.0-M4/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.. + *

+ * Discovery allows to publish services exposed for remote access as well as to + * search for remote services. + *

+ * 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. + *

+ * Value of this property is of type String. + */ + String PRODUCT_NAME = "osgi.remote.discovery.product"; + + /** + * ServiceRegistration property for the version of the Discovery product. + *

+ * Value of this property is of type String. + */ + String PRODUCT_VERSION = "osgi.remote.discovery.product.version"; + + /** + * ServiceRegistration property for the Discovery product vendor name. + *

+ * Value of this property is of type String. + */ + String VENDOR_NAME = "osgi.remote.discovery.vendor"; + + /** + * ServiceRegistration property that lists the discovery protocols used by + * this Discovery service. + *

+ * Value of this property is of type + * Collection (<? extends String>). + */ + String SUPPORTED_PROTOCOLS = "osgi.remote.discovery.supported_protocols"; +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/DiscoveryActivator.java b/branches/sca-java-2.0-M4/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/branches/sca-java-2.0-M4/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 discoveryServices = new ArrayList(); + private List discoveryServiceRegistrations = new ArrayList(); + + 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/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/DomainDiscoveryService.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/DomainDiscoveryService.java new file mode 100644 index 0000000000..2577a68808 --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/DomainDiscoveryService.java @@ -0,0 +1,117 @@ +/* + * 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.UtilityExtensionPoint; +import org.apache.tuscany.sca.implementation.osgi.OSGiImplementation; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.EndpointDescription; +import org.apache.tuscany.sca.runtime.DomainRegistryFactory; +import org.apache.tuscany.sca.runtime.EndpointListener; +import org.osgi.framework.BundleContext; + +/** + * Discovery service based on the distributed SCA domain + */ +public class DomainDiscoveryService extends AbstractDiscoveryService implements EndpointListener { + private DomainRegistryFactory domainRegistryFactory; + + public DomainDiscoveryService(BundleContext context) { + super(context); + } + + public void start() { + super.start(); + getExtensionPointRegistry(); + this.domainRegistryFactory = + registry.getExtensionPoint(UtilityExtensionPoint.class).getUtility(DomainRegistryFactory.class); + domainRegistryFactory.addListener(this); + } + + public void endpointAdded(Endpoint endpoint) { + Implementation impl = endpoint.getComponent().getImplementation(); + if (!(impl instanceof OSGiImplementation)) { + return; + } + + OSGiImplementation osgiImpl = (OSGiImplementation)impl; + BundleContext bundleContext = osgiImpl.getBundle().getBundleContext(); + + /* + if (!endpoint.isRemote()) { + Interface intf = endpoint.getService().getInterfaceContract().getInterface(); + JavaInterface javaInterface = (JavaInterface)intf; + // String filter = getOSGiFilter(provider.getOSGiProperties(service)); + // FIXME: What is the filter? + String filter = "(!(sca.reference=*))"; + // "(sca.service=" + component.getURI() + "#service-name\\(" + service.getName() + "\\))"; + ServiceReference ref = null; + try { + ref = bundleContext.getServiceReferences(javaInterface.getName(), filter)[0]; + } catch (InvalidSyntaxException e) { + // Ignore + } + if (ref != null) { + + } + } else + */ + { + // Notify the endpoint listeners + EndpointDescription description = createEndpointDescription(bundleContext, endpoint); + endpointChanged(description, ADDED); + } + } + + public void endpointRemoved(Endpoint endpoint) { + /* + if (!endpoint.isRemote()) { + // export services + } else + */ + { + EndpointDescription description = createEndpointDescription(context, endpoint); + 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() { + domainRegistryFactory.removeListener(this); + super.stop(); + } + + @Override + protected Dictionary getProperties() { + Dictionary props = super.getProperties(); + props.put(SUPPORTED_PROTOCOLS, new String[] {"org.osgi.sca"}); + return props; + } +} diff --git a/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/LocalDiscoveryService.java b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/LocalDiscoveryService.java new file mode 100644 index 0000000000..594fe32e54 --- /dev/null +++ b/branches/sca-java-2.0-M4/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/service/discovery/impl/LocalDiscoveryService.java @@ -0,0 +1,205 @@ +/** + * 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 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 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.EndpointDescription; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.RemoteConstants; +import org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.EndpointHelper; +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.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 extenders = new ArrayList(); + + public LocalDiscoveryService(BundleContext context) { + super(context); + } + + public void start() { + super.start(); + getExtensionPointRegistry(); + + 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 props = new HashMap(sd.getProperties()); + props.put(Constants.OBJECTCLASS, sd.getInterfaces().toArray(new String[sd.getInterfaces().size()])); + if (!props.containsKey(RemoteConstants.SERVICE_REMOTE_ID)) { + props.put(RemoteConstants.SERVICE_REMOTE_ID, String.valueOf(System.currentTimeMillis())); + } + if (!props.containsKey(RemoteConstants.SERVICE_REMOTE_FRAMEWORK_UUID)) { + props.put(RemoteConstants.SERVICE_REMOTE_FRAMEWORK_UUID, EndpointHelper.getFrameworkUUID(context)); + } + if (!props.containsKey(RemoteConstants.SERVICE_REMOTE_URI)) { + props.put(RemoteConstants.SERVICE_REMOTE_URI, UUID.randomUUID().toString()); + } + + EndpointDescription sed = new EndpointDescription(props); + return sed; + } + + private void removeServicesDeclaredInBundle(Bundle bundle) { + for (Iterator> i = servicesInfo.entrySet().iterator(); i.hasNext();) { + Entry 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) { + Collection scaConfigs = getConfiguration(bundle, "SCA-Configuration", "OSGI-INF/sca-config/*.xml"); + Collection 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); + } + } + + // Notify + for (ServiceDescriptions sds : extender.getRemoteServiceDescriptions()) { + for (ServiceDescription sd : sds) { + EndpointDescription sed = createEndpointDescription(sd); + servicesInfo.put(sed, bundle); + serviceDescriptionAdded(sed); + } + } + + this.extenders.add(extender); + 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 getConfigurations() { + return extenders; + } + + public static class ExtenderConfiguration { + private Collection scaConfigs = new ArrayList(); + private Collection remoteServiceDescriptions = new ArrayList(); + + public Collection getRemoteServiceDescriptions() { + return remoteServiceDescriptions; + } + + public Collection getSCAConfigs() { + return scaConfigs; + } + + } + + @Override + protected Dictionary getProperties() { + Dictionary props = super.getProperties(); + props.put(SUPPORTED_PROTOCOLS, new String[] {"local"}); + return props; + } + +} -- cgit v1.2.3