From 88bf2a256b02e1858993bf097f4dc743d389e3f0 Mon Sep 17 00:00:00 2001 From: jsdelfino Date: Sun, 29 Aug 2010 02:55:29 +0000 Subject: Sandbox to experiment and extend the runtime. git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@990479 13f79535-47bb-0310-9956-ffa450edef68 --- .../sca/core/DefaultExtensionPointRegistry.java | 193 +++ .../sca/core/DefaultFactoryExtensionPoint.java | 162 +++ .../core/DefaultModuleActivatorExtensionPoint.java | 154 +++ .../sca/core/DefaultUtilityExtensionPoint.java | 219 ++++ .../tuscany/sca/core/ExtensionPointRegistry.java | 66 + .../tuscany/sca/core/FactoryExtensionPoint.java | 55 + .../apache/tuscany/sca/core/LifeCycleListener.java | 37 + .../apache/tuscany/sca/core/ModuleActivator.java | 72 + .../sca/core/ModuleActivatorExtensionPoint.java | 53 + .../tuscany/sca/core/UtilityExtensionPoint.java | 74 ++ .../sca/extensibility/ClassLoaderContext.java | 213 +++ .../ContextClassLoaderServiceDiscoverer.java | 179 +++ .../sca/extensibility/ServiceDeclaration.java | 82 ++ .../extensibility/ServiceDeclarationParser.java | 375 ++++++ .../sca/extensibility/ServiceDiscoverer.java | 54 + .../sca/extensibility/ServiceDiscovery.java | 288 ++++ .../tuscany/sca/extensibility/ServiceHelper.java | 226 ++++ .../extensibility/impl/ClassLoaderDelegate.java | 89 ++ .../extensibility/impl/InvalidSyntaxException.java | 86 ++ .../tuscany/sca/extensibility/impl/LDAPFilter.java | 1373 ++++++++++++++++++++ 20 files changed, 4050 insertions(+) create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultExtensionPointRegistry.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultFactoryExtensionPoint.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultModuleActivatorExtensionPoint.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultUtilityExtensionPoint.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/ExtensionPointRegistry.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/FactoryExtensionPoint.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/LifeCycleListener.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/ModuleActivator.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/ModuleActivatorExtensionPoint.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/UtilityExtensionPoint.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ClassLoaderContext.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ContextClassLoaderServiceDiscoverer.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDeclaration.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDeclarationParser.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDiscoverer.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDiscovery.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceHelper.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/impl/ClassLoaderDelegate.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/impl/InvalidSyntaxException.java create mode 100644 sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/impl/LDAPFilter.java (limited to 'sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache') diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultExtensionPointRegistry.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultExtensionPointRegistry.java new file mode 100644 index 0000000000..e1f9ac6ddd --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultExtensionPointRegistry.java @@ -0,0 +1,193 @@ +/* + * 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.core; + +import static org.apache.tuscany.sca.extensibility.ServiceHelper.newInstance; + +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.tuscany.sca.extensibility.ServiceDeclaration; +import org.apache.tuscany.sca.extensibility.ServiceDiscovery; +import org.apache.tuscany.sca.extensibility.ServiceHelper; + +/** + * Default implementation of a registry to hold all the Tuscany core extension + * points. As the point of contact for all extension artifacts this registry + * allows loaded extensions to find all other parts of the system and register + * themselves appropriately. + * + * @tuscany.spi.extension.asclient + * + * @version $Rev$ $Date$ + */ +public class DefaultExtensionPointRegistry implements ExtensionPointRegistry { + protected Map, Object> extensionPoints = new HashMap, Object>(); + private ServiceDiscovery discovery; + /** + * Constructs a new registry. + */ + public DefaultExtensionPointRegistry() { + this.discovery = ServiceDiscovery.getInstance(); + } + + public DefaultExtensionPointRegistry(ServiceDiscovery discovery) { + this.discovery = discovery; + } + + /** + * Add an extension point to the registry. This default implementation + * stores extensions against the interfaces that they implement. + * + * @param extensionPoint The instance of the extension point + * + * @throws IllegalArgumentException if extensionPoint is null + */ + public synchronized void addExtensionPoint(Object extensionPoint) { + addExtensionPoint(extensionPoint, null); + } + + public synchronized void addExtensionPoint(Object extensionPoint, ServiceDeclaration declaration) { + if (extensionPoint == null) { + throw new IllegalArgumentException("Cannot register null as an ExtensionPoint"); + } + ServiceHelper.start(extensionPoint); + + Set> interfaces = getAllInterfaces(extensionPoint.getClass()); + for (Class i : interfaces) { + registerExtensionPoint(i, extensionPoint, declaration); + } + } + + protected void registerExtensionPoint(Class i, Object extensionPoint, ServiceDeclaration declaration) { + extensionPoints.put(i, extensionPoint); + } + + /** + * Get the extension point by the interface that it implements + * + * @param extensionPointType The lookup key (extension point interface) + * @return The instance of the extension point + * + * @throws IllegalArgumentException if extensionPointType is null + */ + public synchronized T getExtensionPoint(Class extensionPointType) { + if (extensionPointType == null) { + throw new IllegalArgumentException("Cannot lookup ExtensionPoint of type null"); + } + + Object extensionPoint = findExtensionPoint(extensionPointType); + if (extensionPoint == null) { + + // Dynamically load an extension point class declared under META-INF/services + try { + ServiceDeclaration extensionPointDeclaration = + getServiceDiscovery().getServiceDeclaration(extensionPointType); + if (extensionPointDeclaration != null) { + extensionPoint = newInstance(this, extensionPointDeclaration); + // Cache the loaded extension point + addExtensionPoint(extensionPoint, extensionPointDeclaration); + } + } catch (Throwable e) { + throw new IllegalArgumentException(e); + } + } + return extensionPointType.cast(extensionPoint); + } + + protected Object findExtensionPoint(Class extensionPointType) { + return extensionPoints.get(extensionPointType); + } + + /** + * Remove an extension point based on the interface that it implements + * + * @param extensionPoint The extension point to remove + * + * @throws IllegalArgumentException if extensionPoint is null + */ + public synchronized void removeExtensionPoint(Object extensionPoint) { + if (extensionPoint == null) { + throw new IllegalArgumentException("Cannot remove null as an ExtensionPoint"); + } + + ServiceHelper.stop(extensionPoint); + + Set> interfaces = getAllInterfaces(extensionPoint.getClass()); + for (Class i : interfaces) { + unregisterExtensionPoint(i); + } + } + + protected void unregisterExtensionPoint(Class i) { + extensionPoints.remove(i); + } + + /** + * Returns the set of interfaces implemented by the given class and its + * ancestors or a blank set if none + */ + private static Set> getAllInterfaces(Class clazz) { + Set> implemented = new HashSet>(); + getAllInterfaces(clazz, implemented); + implemented.remove(LifeCycleListener.class); + return implemented; + } + + private static void getAllInterfaces(Class clazz, Set> implemented) { + Class[] interfaces = clazz.getInterfaces(); + for (Class interfaze : interfaces) { + if (Modifier.isPublic(interfaze.getModifiers())) { + implemented.add(interfaze); + } + } + Class superClass = clazz.getSuperclass(); + // Object has no superclass so check for null + if (superClass != null && !superClass.equals(Object.class)) { + getAllInterfaces(superClass, implemented); + } + } + + public synchronized void start() { + // Do nothing + } + + public synchronized void stop() { + // Get a unique map as an extension point may exist in the map by different keys + Map map = new IdentityHashMap(); + for (Object extp : extensionPoints.values()) { + if (extp instanceof LifeCycleListener) { + LifeCycleListener listener = (LifeCycleListener)extp; + map.put(listener, listener); + } + } + ServiceHelper.stop(map.values()); + extensionPoints.clear(); + } + + public ServiceDiscovery getServiceDiscovery() { + return discovery; + } + +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultFactoryExtensionPoint.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultFactoryExtensionPoint.java new file mode 100644 index 0000000000..1c49ea4ee4 --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultFactoryExtensionPoint.java @@ -0,0 +1,162 @@ +/* + * 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.core; + +import static org.apache.tuscany.sca.extensibility.ServiceHelper.newInstance; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.tuscany.sca.extensibility.ServiceDeclaration; + + + +/** + * Default implementation of a model factory extension point. + * + * @version $Rev$ $Date$ + */ +public class DefaultFactoryExtensionPoint implements FactoryExtensionPoint { + private ExtensionPointRegistry registry; + private Map, Object> factories = new ConcurrentHashMap, Object>(); + + /** + * Constructs a new DefaultModelFactoryExtensionPoint. + */ + public DefaultFactoryExtensionPoint(ExtensionPointRegistry extensionPointRegistry) { + this.registry = extensionPointRegistry; + } + + /** + * Add a model factory extension. + * + * @param factory The factory to add + */ + public void addFactory(Object factory) { + Class[] interfaces = factory.getClass().getInterfaces(); + if (interfaces.length == 0) { + Class sc = factory.getClass().getSuperclass(); + if (sc != Object.class) { + factories.put(sc, factory); + } + } else { + for (int i = 0; i[] interfaces = factory.getClass().getInterfaces(); + if (interfaces.length == 0) { + Class sc = factory.getClass().getSuperclass(); + if (sc != Object.class) { + factories.remove(sc); + } + } else { + for (int i = 0; i() { + public ClassLoader run() { + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + if (classLoader != null) { + Thread.currentThread().setContextClassLoader(classLoader); + } + return tccl; + } + }); + } + + /** + * Get a factory implementing the given interface. + * @param factoryInterface The lookup key (factory interface) + * @return The factory + */ + public T getFactory(Class factoryInterface) { + Object factory = factories.get(factoryInterface); + if (factory == null) { + + // Dynamically load a factory class declared under META-INF/services + try { + ServiceDeclaration factoryDeclaration = + registry.getServiceDiscovery().getServiceDeclaration(factoryInterface); + if (factoryDeclaration != null) { + try { + // Constructor taking the extension point registry + factory = newInstance(registry, factoryDeclaration); + } catch (NoSuchMethodException e) { + factory = newInstance(factoryDeclaration.loadClass(), FactoryExtensionPoint.class, this); + } + + // Cache the loaded factory + factories.put(factoryInterface, factory); + + return factoryInterface.cast(factory); + + } else { + + // If the input interface is an abstract class + if (!factoryInterface.isInterface() && Modifier.isAbstract(factoryInterface.getModifiers())) { + Method newInstanceMethod = factoryInterface.getDeclaredMethod("newInstance"); + ClassLoader tccl = setContextClassLoader(factoryInterface.getClassLoader()); + try { + + // Create a new instance + factory = newInstanceMethod.invoke(null); + + // Cache the factory + factories.put(factoryInterface, factory); + + return factoryInterface.cast(factory); + } catch (Exception e) { + // Sorry no factory found + return null; + } finally { + setContextClassLoader(tccl); + } + } else { + + // Sorry no factory found + return null; + } + } + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } else { + return factoryInterface.cast(factory); + } + } + +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultModuleActivatorExtensionPoint.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultModuleActivatorExtensionPoint.java new file mode 100644 index 0000000000..99792d0215 --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultModuleActivatorExtensionPoint.java @@ -0,0 +1,154 @@ +/* + * 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.core; + +import static org.apache.tuscany.sca.extensibility.ServiceHelper.newInstance; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.tuscany.sca.extensibility.ServiceDeclaration; + +/** + * Default implementation of an extension point to hold Tuscany module activators. + * + * @version $Rev$ $Date$ + */ +public class DefaultModuleActivatorExtensionPoint implements ModuleActivatorExtensionPoint { + private final static Logger logger = Logger.getLogger(DefaultModuleActivatorExtensionPoint.class.getName()); + private List activators = new ArrayList(); + private boolean loadedActivators; + private boolean started; + private ExtensionPointRegistry registry; + + /** + * Constructs a new extension point. + */ + public DefaultModuleActivatorExtensionPoint(ExtensionPointRegistry registry) { + this.registry = registry; + } + + public void addModuleActivator(ModuleActivator activator) { + activators.add(activator); + } + + public List getModuleActivators() { + loadModuleActivators(); + return activators; + } + + public void removeModuleActivator(ModuleActivator activator) { + if (activators.remove(activator)) { + activator.stop(); + } + } + + /** + * Dynamically load module activators declared under META-INF/services + */ + private synchronized void loadModuleActivators() { + if (loadedActivators) + return; + + // Get the activator service declarations + Collection activatorDeclarations; + try { + // Load the module activators by ranking + activatorDeclarations = registry.getServiceDiscovery().getServiceDeclarations(ModuleActivator.class.getName(), true); + } catch (IOException e) { + throw new IllegalStateException(e); + } + + // Load and instantiate module activators + for (ServiceDeclaration activatorDeclaration : activatorDeclarations) { + if (logger.isLoggable(Level.FINE)) { + logger.fine("Loading " + activatorDeclaration.getClassName()); + } + ModuleActivator activator = null; + try { + Class activatorClass = (Class)activatorDeclaration.loadClass(); + try { + activator = newInstance(activatorClass, ExtensionPointRegistry.class, registry); + } catch (NoSuchMethodException e) { + try { + activator = + newInstance(activatorClass, + new Class[] {ExtensionPointRegistry.class, Map.class}, + registry, + activatorDeclaration.getAttributes()); + + } catch (NoSuchMethodException e1) { + activator = newInstance(activatorClass); + + } + } + } catch (Throwable e) { + String optional = activatorDeclaration.getAttributes().get("optional"); + if ("true".equalsIgnoreCase(optional)) { + // If the optional flag is true, just log the error + logger.log(Level.SEVERE, e.getMessage(), e); + continue; + } else { + throw new IllegalArgumentException(e); + } + } + addModuleActivator(activator); + } + + loadedActivators = true; + } + + public void start() { + if (started) { + return; + } + getModuleActivators(); + for (ModuleActivator activator : activators) { + try { + activator.start(); + } catch (Throwable e) { + // Ignore the failing module for now + logger.log(Level.SEVERE, e.getMessage(), e); + } + } + started = true; + } + + public void stop() { + if (!started) { + return; + } + for (int i = activators.size() - 1; i >= 0; i--) { + try { + activators.get(i).stop(); + } catch (Throwable e) { + // Ignore the failing module for now + logger.log(Level.SEVERE, e.getMessage(), e); + } + } + started = false; + } + +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultUtilityExtensionPoint.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultUtilityExtensionPoint.java new file mode 100644 index 0000000000..40e4635d77 --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/DefaultUtilityExtensionPoint.java @@ -0,0 +1,219 @@ +/* + * 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.core; + +import static org.apache.tuscany.sca.extensibility.ServiceHelper.newInstance; + +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.tuscany.sca.extensibility.ServiceDeclaration; + +/** + * Default implementation of an extension point to hold Tuscany utility utilities. + * + * @version $Rev$ $Date$ + */ +public class DefaultUtilityExtensionPoint implements UtilityExtensionPoint { + private Map utilities = new ConcurrentHashMap(); + + private ExtensionPointRegistry registry; + /** + * Constructs a new extension point. + */ + public DefaultUtilityExtensionPoint(ExtensionPointRegistry extensionPoints) { + this.registry = extensionPoints; + } + + /** + * Add a utility to the extension point. This default implementation + * stores utilities against the interfaces that they implement. + * + * @param utility The instance of the utility + * + * @throws IllegalArgumentException if utility is null + */ + public void addUtility(Object utility) { + addUtility(null, utility); + } + + public void addUtility(Object key, Object utility) { + if (utility == null) { + throw new IllegalArgumentException("Cannot register null as a Service"); + } + + if (utility instanceof LifeCycleListener) { + ((LifeCycleListener)utility).start(); + } + + if (key == null) { + Class cls = utility.getClass(); + Set> interfaces = getAllInterfaces(cls); + for (Class i : interfaces) { + utilities.put(i, utility); + } + if (interfaces.isEmpty() || isConcreteClass(cls)) { + utilities.put(cls, utility); + } + } else { + utilities.put(key, utility); + } + } + + /** + * Get the utility by the interface that it implements + * + * @param utilityType The lookup key (utility interface) + * @return The instance of the utility + * + * @throws IllegalArgumentException if utilityType is null + */ + public T getUtility(Class utilityType) { + return getUtility(utilityType, null); + } + + /** + * Remove a utility based on the interface that it implements + * + * @param utility The utility to remove + * + * @throws IllegalArgumentException if utility is null + */ + public void removeUtility(Object utility) { + if (utility == null) { + throw new IllegalArgumentException("Cannot remove null as a Service"); + } + + if(utility instanceof LifeCycleListener) { + ((LifeCycleListener) utility).stop(); + } + + for (Iterator> i = utilities.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = i.next(); + if (entry.getValue() == utility) { + i.remove(); + } + } + } + + /** + * Returns the set of interfaces implemented by the given class and its + * ancestors or a blank set if none + */ + private static Set> getAllInterfaces(Class clazz) { + Set> implemented = new HashSet>(); + getAllInterfaces(clazz, implemented); + implemented.remove(LifeCycleListener.class); + return implemented; + } + + private static void getAllInterfaces(Class clazz, Set> implemented) { + Class[] interfaces = clazz.getInterfaces(); + for (Class interfaze : interfaces) { + if (Modifier.isPublic(interfaze.getModifiers())) { + implemented.add(interfaze); + } + } + Class superClass = clazz.getSuperclass(); + // Object has no superclass so check for null + if (superClass != null && !superClass.equals(Object.class)) { + getAllInterfaces(superClass, implemented); + } + } + + public T getUtility(Class utilityType, Object key) { + if (utilityType == null) { + throw new IllegalArgumentException("Cannot lookup Service of type null"); + } + + if (key == null) { + key = utilityType; + } + + Object utility = utilities.get(key); + + if (utility == null) { + + // Dynamically load a utility class declared under META-INF/services/"utilityType" + try { + ServiceDeclaration utilityDeclaration = + registry.getServiceDiscovery().getServiceDeclaration(utilityType.getName()); + Class utilityClass = null; + if (utilityDeclaration != null) { + utilityClass = utilityDeclaration.loadClass(); + } else if (isConcreteClass(utilityType)) { + utilityClass = utilityType; + key = utilityType; + } + if (utilityClass != null) { + // Construct the utility + if (utilityDeclaration != null) { + utility = newInstance(registry, utilityDeclaration); + } else { + try { + utility = newInstance(utilityClass, ExtensionPointRegistry.class, registry); + } catch (NoSuchMethodException e) { + utility = newInstance(utilityClass); + } + } + // Cache the loaded utility + if (key == utilityType) { + addUtility(utility); + } else { + addUtility(key, utility); + } + } + } catch (Throwable e) { + throw new IllegalArgumentException(e); + } + } + return utilityType.cast(utility); + } + + private boolean isConcreteClass(Class utilityType) { + int modifiers = utilityType.getModifiers(); + return !utilityType.isInterface() && Modifier.isPublic(modifiers) && !Modifier.isAbstract(modifiers); + } + + public void start() { + // NOOP + } + + public synchronized void stop() { + // Get a unique map as an extension point may exist in the map by different keys + Map map = new IdentityHashMap(); + for (Object util : utilities.values()) { + if (util instanceof LifeCycleListener) { + LifeCycleListener listener = (LifeCycleListener)util; + map.put(listener, listener); + } + } + for (LifeCycleListener listener : map.values()) { + listener.stop(); + } + utilities.clear(); + } + +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/ExtensionPointRegistry.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/ExtensionPointRegistry.java new file mode 100644 index 0000000000..8f9ab7ed3e --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/ExtensionPointRegistry.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.core; + +import org.apache.tuscany.sca.extensibility.ServiceDiscovery; + + +/** + * The registry for the Tuscany core extension points. As the point of contact + * for all extension artifacts this registry allows loaded extensions to find + * all other parts of the system and register themselves appropriately. + * + * + * @version $Rev$ $Date$ + * @tuscany.spi.extension.asclient + */ +public interface ExtensionPointRegistry extends LifeCycleListener { + + /** + * Add an extension point to the registry + * @param extensionPoint The instance of the extension point + * + * @throws IllegalArgumentException if extensionPoint is null + */ + void addExtensionPoint(Object extensionPoint); + + /** + * Get the extension point by the interface + * @param extensionPointType The lookup key (extension point interface) + * @return The instance of the extension point + * + * @throws IllegalArgumentException if extensionPointType is null + */ + T getExtensionPoint(Class extensionPointType); + + /** + * Remove an extension point + * @param extensionPoint The extension point to remove + * + * @throws IllegalArgumentException if extensionPoint is null + */ + void removeExtensionPoint(Object extensionPoint); + + /** + * Get an instance of the ServiceDiscovery + * @return an instance of the ServiceDiscovery associated with the environment + */ + ServiceDiscovery getServiceDiscovery(); +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/FactoryExtensionPoint.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/FactoryExtensionPoint.java new file mode 100644 index 0000000000..c3d2fd282a --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/FactoryExtensionPoint.java @@ -0,0 +1,55 @@ +/* + * 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.core; + +/** + * An extension point for model factories. Model factories are provided to + * abstract the classes that represent artifacts in the assembly model away + * from their creation mechanism. When the runtime needs to extend the model + * as it reads in contributed artifacts it looks up the factory for the + * artifact required in this registry + * + * @version $Rev$ $Date$ + * @tuscany.spi.extension.asclient + */ +public interface FactoryExtensionPoint { + + /** + * Add a model factory extension. + * + * @param factory The factory to add + */ + void addFactory(Object factory); + + /** + * Remove a model factory extension. + * + * @param factory The factory to remove + */ + void removeFactory(Object factory); + + /** + * Get a factory implementing the given interface. + * @param factoryInterface the lookup key (factory interface) + * @return The factory + */ + T getFactory(Class factoryInterface); + +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/LifeCycleListener.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/LifeCycleListener.java new file mode 100644 index 0000000000..ca92cb8129 --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/LifeCycleListener.java @@ -0,0 +1,37 @@ +/* + * 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.core; + +/** + * A listener that responds to the start/stop event of the ExtensionPointRegistry. Tuscany extension + * points or extensions can implement this interface to receive callbacks when the registry is started + * or stopped + * @tuscany.spi.extension.inheritfrom + */ +public interface LifeCycleListener { + /** + * The method will be invoked when the extension point registry is started + */ + void start(); + /** + * The method will be invoked when the extension point registry is stopped + */ + void stop(); +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/ModuleActivator.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/ModuleActivator.java new file mode 100644 index 0000000000..ddc9a6981c --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/ModuleActivator.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.core; + + +/** + * ModuleActivator represents a module that plugs into the Tuscany system. Each + * module should provide an implementation of this interface and register the + * ModuleActivator implementation class by defining a file named + * + * "META-INF/services/org.apache.tuscany.core.ModuleActivator" + * + * The content of the file is the class name of the ModuleActivator implementation. + * The implementation class can have different flavors of constructors. The following + * order will be searched: + *
    + *
  • (ExtensionRegistry.class) + *
  • (ExtensionRegistry.class, Map.class) + *
  • () + *
+ * + * + * + * + * The same instance + * will be used to invoke all the methods during different phases of the module + * activation. Note that the start and stop methods defined by this interface + * take a reference to the Tuscany SCA runtime ExtensionPointRegistry. This + * gives the ModuleActivator the opportunity to add extension points to the + * registry as it is requested to start up and remove them when it is requested + * to shut down. + * + * @version $Rev$ $Date$ + * @tuscany.spi.extension.inheritfrom + */ +public interface ModuleActivator extends LifeCycleListener { + + /** + * This method is invoked when the module is started by the Tuscany runtime. + * It can be used by this module to register extensions against extension + * points. + * + * @param registry The extension point registry + */ + void start(); + + /** + * This method is invoked when the module is stopped by the Tuscany runtime. + * It can be used by this module to unregister extensions against the + * extension points. + * + * @param registry The extension point registry + */ + void stop(); +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/ModuleActivatorExtensionPoint.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/ModuleActivatorExtensionPoint.java new file mode 100644 index 0000000000..7070d33f2c --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/ModuleActivatorExtensionPoint.java @@ -0,0 +1,53 @@ +/* + * 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.core; + +import java.util.List; + + +/** + * The extension point for the Tuscany module activator extensions. + * + * @version $Rev$ $Date$ + */ +public interface ModuleActivatorExtensionPoint extends LifeCycleListener { + + /** + * Add a module activator extension to the extension point + * @param activator The instance of the module activator + * + * @throws IllegalArgumentException if activator is null + */ + void addModuleActivator(ModuleActivator activator); + + /** + * Returns the module activator extensions. + * @return The module activator extensions + */ + List getModuleActivators(); + + /** + * Remove a module activator + * @param activator The module activator to remove + * + * @throws IllegalArgumentException if activator is null + */ + void removeModuleActivator(ModuleActivator activator); +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/UtilityExtensionPoint.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/UtilityExtensionPoint.java new file mode 100644 index 0000000000..7acc7fc409 --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/core/UtilityExtensionPoint.java @@ -0,0 +1,74 @@ +/* + * 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.core; + + +/** + * The extension point for the Tuscany core utility extensions. + * + * @version $Rev$ $Date$ + * @tuscany.spi.extension.asclient + */ +public interface UtilityExtensionPoint extends LifeCycleListener { + + /** + * Add a utility to the extension point + * @param utility The instance of the utility + * + * @throws IllegalArgumentException if utility is null + */ + void addUtility(Object utility); + + /** + * Add a utility to the extension point for a given key + * @param utility The instance of the utility + * + * @throws IllegalArgumentException if utility is null + */ + void addUtility(Object key, Object utility); + + /** + * Get the utility by the interface + * @param utilityType The lookup key (utility interface) + * @return The instance of the utility + * + * @throws IllegalArgumentException if utilityType is null + */ + T getUtility(Class utilityType); + + /** + * Get an instance of the utility by the interface and key + * @param utilityType The lookup key (utility interface) + * @param key A key associated with the utility, if it is null, + * then the utilityType is used as the key + * @return The instance of the utility + * + * @throws IllegalArgumentException if utilityType is null + */ + T getUtility(Class utilityType, Object key); + + /** + * Remove a utility + * @param utility The utility to remove + * + * @throws IllegalArgumentException if utility is null + */ + void removeUtility(Object utility); +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ClassLoaderContext.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ClassLoaderContext.java new file mode 100644 index 0000000000..f15e89767c --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ClassLoaderContext.java @@ -0,0 +1,213 @@ +/* + * 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.extensibility; + +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.tuscany.sca.extensibility.impl.ClassLoaderDelegate; + +/** + * A utility that controls context class loaders + * @tuscany.spi.extension.asclient + */ +public class ClassLoaderContext { + private ClassLoader classLoader; + + /** + * Create a context with the parent classloader and a list of service types that can be discovered + * by the {@link ServiceDiscovery} + * @param parent + * @param discovery + * @param serviceTypes + */ + public ClassLoaderContext(ClassLoader parent, ServiceDiscovery discovery, Class... serviceTypes) { + this(parent, getClassLoaders(discovery, serviceTypes)); + } + + private ClassLoaderContext(ClassLoader parent, List delegates) { + List loaders = new ArrayList(delegates); + loaders.remove(parent); + if (delegates.isEmpty()) { + classLoader = parent; + } else { + classLoader = new ClassLoaderDelegate(parent, loaders); + } + } + + /** + * Create a context that is visible to the parent classloader as well as the list of classloaders + * @param parent + * @param delegates + */ + public ClassLoaderContext(ClassLoader parent, ClassLoader... delegates) { + this(parent, Arrays.asList(delegates)); + } + + /** + * Create a context with the parent classloader and a list of service types that can be discovered + * by the {@link ServiceDiscovery} + * @param parent + * @param discovery + * @param serviceTypes + */ + public ClassLoaderContext(ClassLoader parent, ServiceDiscovery discovery, String... serviceTypes) { + this(parent, getClassLoaders(discovery, serviceTypes)); + } + + public T doPrivileged(PrivilegedAction action) { + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + if (tccl != classLoader) { + Thread.currentThread().setContextClassLoader(classLoader); + } + try { + return action.run(); + } finally { + if (tccl != classLoader) { + Thread.currentThread().setContextClassLoader(tccl); + } + } + } + + public T doPrivileged(PrivilegedExceptionAction action) throws PrivilegedActionException { + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + if (tccl != classLoader) { + Thread.currentThread().setContextClassLoader(classLoader); + } + try { + return action.run(); + } catch (Exception e) { + throw new PrivilegedActionException(e); + } finally { + if (tccl != classLoader) { + Thread.currentThread().setContextClassLoader(tccl); + } + } + } + + /** + * Set the thread context classloader (TCCL) to a classloader that delegates to a collection + * of classloaders + * @param parent The parent classloader + * @param delegates A list of classloaders to try + * @return The existing TCCL + */ + public static ClassLoader setContextClassLoader(ClassLoader parent, ClassLoader... delegates) { + ClassLoaderContext context = new ClassLoaderContext(parent, delegates); + return context.setContextClassLoader(); + } + + /** + * Set the context classloader so that it can access the list of service providers + * @param parent The parent classloader + * @param serviceNames A list of service provider names + * @return The old TCCL if a new one is set, otherwise null + */ + public static ClassLoader setContextClassLoader(ClassLoader parent, ServiceDiscovery discovery, String... serviceNames) { + ClassLoaderContext context = new ClassLoaderContext(parent, discovery, serviceNames); + return context.setContextClassLoader(); + } + + /** + * Set the context classloader so that it can access the list of service providers + * @param parent The parent classloader + * @param serviceNames A list of service provider names + * @return The old TCCL if a new one is set, otherwise null + */ + public static ClassLoader setContextClassLoader(ClassLoader parent, ServiceDiscovery discovery, Class... serviceTypes) { + ClassLoaderContext context = new ClassLoaderContext(parent, discovery, serviceTypes); + return context.setContextClassLoader(); + } + + public ClassLoader setContextClassLoader() { + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + if (tccl != classLoader) { + Thread.currentThread().setContextClassLoader(classLoader); + return tccl; + } else { + return null; + } + } + + private static ClassLoader getClassLoader(ServiceDiscovery discovery, String serviceProvider) { + try { + ServiceDeclaration sd = discovery.getServiceDeclaration(serviceProvider); + if (sd != null) { + return sd.loadClass().getClassLoader(); + } + } catch (Exception e) { + // Ignore + } + return null; + } + + private static List getClassLoaders(ServiceDiscovery discovery, String... serviceNames) { + List loaders = new ArrayList(); + for (String sp : serviceNames) { + ClassLoader loader = getClassLoader(discovery, sp); + if (loader != null) { + if (!loaders.contains(loader)) { + loaders.add(loader); + } + } + } + ClassLoader tccl = discovery.getContextClassLoader(); + if (!loaders.contains(tccl)) { + loaders.add(tccl); + } + return loaders; + } + + private static ClassLoader getClassLoader(ServiceDiscovery discovery, Class serviceType) { + try { + ServiceDeclaration sd = discovery.getServiceDeclaration(serviceType); + if (sd != null) { + return sd.loadClass().getClassLoader(); + } + } catch (Exception e) { + // Ignore + } + return null; + } + + private static List getClassLoaders(ServiceDiscovery discovery, Class... serviceTypes) { + List loaders = new ArrayList(); + for (Class serviceType : serviceTypes) { + ClassLoader classLoader = getClassLoader(discovery, serviceType); + if (classLoader != null && loaders.contains(classLoader)) { + loaders.add(classLoader); + } + } + ClassLoader tccl = discovery.getContextClassLoader(); + if (!loaders.contains(tccl)) { + loaders.add(tccl); + } + return loaders; + } + + public ClassLoader getClassLoader() { + return classLoader; + } + +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ContextClassLoaderServiceDiscoverer.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ContextClassLoaderServiceDiscoverer.java new file mode 100644 index 0000000000..eec4ff1734 --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ContextClassLoaderServiceDiscoverer.java @@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.extensibility; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A ServiceDiscoverer that find META-INF/services/... using the Context ClassLoader. + * + * @version $Rev$ $Date$ + */ +public class ContextClassLoaderServiceDiscoverer implements ServiceDiscoverer { + private static final Logger logger = Logger.getLogger(ContextClassLoaderServiceDiscoverer.class.getName()); + + public class ServiceDeclarationImpl implements ServiceDeclaration { + private URL url; + private String className; + private Class javaClass; + private Map attributes; + + public ServiceDeclarationImpl(URL url, String className, Map attributes) { + super(); + this.url = url; + this.className = className; + this.attributes = attributes; + } + + public Map getAttributes() { + return attributes; + } + + public String getClassName() { + return className; + } + + public URL getLocation() { + return url; + } + + public Class loadClass() throws ClassNotFoundException { + if (className == null) { + return null; + } + if (javaClass == null) { + javaClass = loadClass(className); + } + return javaClass; + } + + public Class loadClass(String className) throws ClassNotFoundException { + return Class.forName(className, false, classLoaderReference.get()); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("Location: ").append(url); + sb.append(" ClassLoader: ").append(classLoaderReference.get()); + sb.append(" Attributes: ").append(attributes); + return sb.toString(); + } + + public URL getResource(final String name) { + return AccessController.doPrivileged(new PrivilegedAction() { + public URL run() { + return classLoaderReference.get().getResource(name); + } + }); + } + + public boolean isAssignableTo(Class serviceType) { + try { + loadClass(); + } catch (ClassNotFoundException e) { + // Ignore + } + return (javaClass != null && serviceType.isAssignableFrom(javaClass)); + } + } + + private WeakReference classLoaderReference; + + public ContextClassLoaderServiceDiscoverer() { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + this.classLoaderReference = new WeakReference(classLoader); + } + + public ContextClassLoaderServiceDiscoverer(ClassLoader classLoader) { + if (classLoader == null) { + classLoader = Thread.currentThread().getContextClassLoader(); + } + this.classLoaderReference = new WeakReference(classLoader); + } + + public ClassLoader getContextClassLoader() { + //return classLoaderReference.get(); + return Thread.currentThread().getContextClassLoader(); + } + + private Collection getResources(final String name) throws IOException { + try { + return AccessController.doPrivileged(new PrivilegedExceptionAction>() { + public Collection run() throws IOException { + List urls = Collections.list(classLoaderReference.get().getResources(name)); + // Eliminate the duplicate URLs (which can be found from child/parent classloaders) + return new HashSet(urls); + } + }); + } catch (PrivilegedActionException e) { + throw (IOException)e.getException(); + } + } + + public ServiceDeclaration getServiceDeclaration(String name) throws IOException { + Collection declarations = getServiceDeclarations(name); + if (declarations.isEmpty()) { + return null; + } else { + return declarations.iterator().next(); + } + } + + public Collection getServiceDeclarations(String serviceName) { + Collection descriptors = new HashSet(); + + // http://java.sun.com/j2se/1.5.0/docs/api/javax/xml/xpath/XPathFactory.html + boolean isPropertyFile = "javax.xml.xpath.XPathFactory".equals(serviceName); + String name = "META-INF/services/" + serviceName; + boolean debug = logger.isLoggable(Level.FINE); + try { + for (final URL url : getResources(name)) { + if (debug) { + logger.fine("Reading service provider file: " + url.toExternalForm()); + } + + for (Map attributes : ServiceDeclarationParser.load(url, isPropertyFile)) { + String className = attributes.get("class"); + ServiceDeclarationImpl descriptor = new ServiceDeclarationImpl(url, className, attributes); + descriptors.add(descriptor); + } + } + } catch (IOException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + } + return descriptors; + + } + +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDeclaration.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDeclaration.java new file mode 100644 index 0000000000..47a91aabfe --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDeclaration.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.extensibility; + +import java.net.URL; +import java.util.Map; + +/** + * Service declaration using J2SE Jar service provider spec Classes specified + * inside this declaration are loaded using the ClassLoader used to read the + * configuration file corresponding to this declaration. + * + * @version $Rev$ $Date$ + * @tuscany.spi.extension.asclient + */ +public interface ServiceDeclaration { + /** + * Load a java class in the same context as the service definition + * @param className The class name + * @return The loaded class + * @throws ClassNotFoundException + */ + Class loadClass(String className) throws ClassNotFoundException; + + /** + * Get the java class for the service impl + * @return The java class + */ + Class loadClass() throws ClassNotFoundException; + + /** + * Get all attributes (name=value pairs) defined for the given entry + * @return All attributes keyed by name + */ + Map getAttributes(); + + /** + * Check if the service implementation class is a type of the service + * @param serviceType The java class of the service SPI + * @return true if the implementation class is a type of the service + */ + boolean isAssignableTo(Class serviceType); + + URL getLocation(); + + /** + * Return the class name for the service provider + * @return + */ + String getClassName(); + + URL getResource(String name); + + /** + * The service descriptor might be hashed + * @param obj Another object + * @return + */ + boolean equals(Object obj); + /** + * The service descriptor might be hashed + * @return + */ + int hashCode(); +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDeclarationParser.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDeclarationParser.java new file mode 100644 index 0000000000..f546e44dfb --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDeclarationParser.java @@ -0,0 +1,375 @@ +/* + * 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.extensibility; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.xml.namespace.QName; + +/** + * Parser for the service descriptors. The syntax of the service declaration is similar with the OSGi + * headers with the following exceptions: + *
    + *
  • Tuscany uses , and ; as the separator for attibutes + *
  • Tuscany + */ +public class ServiceDeclarationParser { + + // private static final String PATH_SEPARATOR = ","; // OSGi style + private static final String PATH_SEPARATOR = "|"; + + // private static final String SEGMENT_SEPARATOR = ";"; // OSGi style + private static final String SEGMENT_SEPARATOR = ";,"; + + private static final String ATTRIBUTE_SEPARATOR = "="; + private static final String DIRECTIVE_SEPARATOR = ":="; + + private static final char QUOTE_CHAR = '"'; + private static final String QUOTE = "\""; + + // Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2, + // path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2 + public static List parse(String header) { + + if (header != null) { + if (header.length() == 0) { + throw new IllegalArgumentException("A header cannot be an empty string."); + } + + String[] clauseStrings = parseDelimitedString(header, PATH_SEPARATOR); + + List completeList = new ArrayList(); + for (int i = 0; (clauseStrings != null) && (i < clauseStrings.length); i++) { + completeList.add(parseClause(clauseStrings[i])); + } + + return completeList; + } + + return null; + + } + + /** + * Parse the declaration into a map of name/value pairs. The class name is added under "class" + * and directives are added using @ as the key. + * @param declaration + * @return A map of attributes + */ + public static Map parseDeclaration(String declaration) { + List descriptors = parse(declaration); + Descriptor descriptor = descriptors.get(0); + Map map = new HashMap(); + map.putAll(descriptor.getAttributes()); + map.put("class", descriptor.getValue()); + for (Map.Entry e : descriptor.getDirectives().entrySet()) { + // For directives, add @ as the prefix for the key + map.put("@" + e.getKey(), e.getValue()); + } + return map; + } + + // Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2 + private static Descriptor parseClause(String clauseString) throws IllegalArgumentException { + // Break string into semi-colon delimited pieces. + String[] pieces = parseDelimitedString(clauseString, SEGMENT_SEPARATOR); + + // Count the number of different paths; paths + // will not have an '=' in their string. This assumes + // that paths come first, before directives and + // attributes. + int pathCount = 0; + for (int pieceIdx = 0; pieceIdx < pieces.length; pieceIdx++) { + if (pieces[pieceIdx].indexOf('=') >= 0) { + break; + } + pathCount++; + } + + // Create an array of paths. + String[] paths = new String[pathCount]; + System.arraycopy(pieces, 0, paths, 0, pathCount); + + // Parse the directives/attributes. + Map dirsMap = new HashMap(); + Map attrsMap = new HashMap(); + int idx = -1; + String sep = null; + for (int pieceIdx = pathCount; pieceIdx < pieces.length; pieceIdx++) { + // Check if it is a directive. + if ((idx = pieces[pieceIdx].indexOf(DIRECTIVE_SEPARATOR)) >= 0) { + sep = DIRECTIVE_SEPARATOR; + } + // Check if it is an attribute. + else if ((idx = pieces[pieceIdx].indexOf(ATTRIBUTE_SEPARATOR)) >= 0) { + sep = ATTRIBUTE_SEPARATOR; + } + // It is an error. + else { + throw new IllegalArgumentException("Not a directive/attribute: " + clauseString); + } + + String key = pieces[pieceIdx].substring(0, idx).trim(); + String value = pieces[pieceIdx].substring(idx + sep.length()).trim(); + + // Remove quotes, if value is quoted. + if (value.startsWith(QUOTE) && value.endsWith(QUOTE)) { + value = value.substring(1, value.length() - 1); + } + + // Save the directive/attribute in the appropriate array. + if (sep.equals(DIRECTIVE_SEPARATOR)) { + // Check for duplicates. + if (dirsMap.get(key) != null) { + throw new IllegalArgumentException("Duplicate directive: " + key); + } + dirsMap.put(key, value); + } else { + // Check for duplicates. + if (attrsMap.get(key) != null) { + throw new IllegalArgumentException("Duplicate attribute: " + key); + } + attrsMap.put(key, value); + } + } + + StringBuffer path = new StringBuffer(); + for (int i = 0; i < paths.length; i++) { + path.append(paths[i]); + if (i != paths.length - 1) { + path.append(';'); + } + } + + Descriptor descriptor = new Descriptor(); + descriptor.text = clauseString; + descriptor.value = path.toString(); + descriptor.valueComponents = paths; + descriptor.attributes = attrsMap; + descriptor.directives = dirsMap; + + return descriptor; + } + + /** + * Parses delimited string and returns an array containing the tokens. This + * parser obeys quotes, so the delimiter character will be ignored if it is + * inside of a quote. This method assumes that the quote character is not + * included in the set of delimiter characters. + * @param value the delimited string to parse. + * @param delim the characters delimiting the tokens. + * @return an array of string tokens or null if there were no tokens. + **/ + private static String[] parseDelimitedString(String value, String delim) { + if (value == null) { + value = ""; + } + + List list = new ArrayList(); + + int CHAR = 1; + int DELIMITER = 2; + int STARTQUOTE = 4; + int ENDQUOTE = 8; + + StringBuffer sb = new StringBuffer(); + + int expecting = (CHAR | DELIMITER | STARTQUOTE); + + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + + boolean isDelimiter = (delim.indexOf(c) >= 0); + boolean isQuote = (c == QUOTE_CHAR); + + if (isDelimiter && ((expecting & DELIMITER) > 0)) { + list.add(sb.toString().trim()); + sb.delete(0, sb.length()); + expecting = (CHAR | DELIMITER | STARTQUOTE); + } else if (isQuote && ((expecting & STARTQUOTE) > 0)) { + sb.append(c); + expecting = CHAR | ENDQUOTE; + } else if (isQuote && ((expecting & ENDQUOTE) > 0)) { + sb.append(c); + expecting = (CHAR | STARTQUOTE | DELIMITER); + } else if ((expecting & CHAR) > 0) { + sb.append(c); + } else { + throw new IllegalArgumentException("Invalid delimited string: " + value); + } + } + + if (sb.length() > 0) { + list.add(sb.toString().trim()); + } + + return (String[])list.toArray(new String[list.size()]); + } + + public static class Descriptor { + private String text; + private String value; + private String[] valueComponents; + private Map attributes; + private Map directives; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String[] getValueComponents() { + return valueComponents; + } + + public void setValueComponents(String[] valueComponents) { + this.valueComponents = valueComponents; + } + + public Map getAttributes() { + return attributes; + } + + public Map getDirectives() { + return directives; + } + + public String toString() { + return text; + } + + } + + /** + * Returns a QName object from a QName expressed as {ns}name + * or ns#name. + * + * @param qname + * @return + */ + public static QName getQName(String qname) { + if (qname == null) { + return null; + } + qname = qname.trim(); + if (qname.startsWith("{")) { + int h = qname.indexOf('}'); + if (h != -1) { + return new QName(qname.substring(1, h), qname.substring(h + 1)); + } + } else { + int h = qname.indexOf('#'); + if (h != -1) { + return new QName(qname.substring(0, h), qname.substring(h + 1)); + } + } + return new QName(qname); + } + + public static Collection> load(final URL url, boolean isPropertyFile) throws IOException { + Collection> descriptors = new ArrayList>(); + + // Allow privileged access to open URL stream. Add FilePermission to added to security + // policy file. + InputStream is; + try { + is = AccessController.doPrivileged(new PrivilegedExceptionAction() { + public InputStream run() throws IOException { + URLConnection connection = url.openConnection(); + // TUSCANY-2539 + // Don't cache connections by default to stop Tuscany locking contribution jar files + // done here as this is one of the first places we open a stream and the only way to + // set the default is to set it on an instance of URLConnection + connection.setDefaultUseCaches(false); + connection.setUseCaches(false); + return url.openStream(); + } + }); + } catch (PrivilegedActionException e) { + throw (IOException)e.getException(); + } + if (isPropertyFile) { + // Load as a property file + Properties props = new Properties(); + props.load(is); + is.close(); + for (Map.Entry e : props.entrySet()) { + Map attributes = new HashMap(); + String key = (String)e.getKey(); + String value = (String)e.getValue(); + // Unfortunately, the xalan file only has the classname + if (value == null || "".equals(value)) { + value = key; + key = ""; + } + if (!"".equals(key)) { + attributes.put(key, value); + attributes.put("uri", key); + } + attributes.putAll(parseDeclaration(value)); + descriptors.add(attributes); + } + return descriptors; + } + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(is)); + while (true) { + String line = reader.readLine(); + if (line == null) + break; + line = line.trim(); + if (!line.startsWith("#") && !"".equals(line)) { + String reg = line.trim(); + + Map attributes = parseDeclaration(reg); + descriptors.add(attributes); + } + } + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + // Ignore + } + } + } + return descriptors; + } + +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDiscoverer.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDiscoverer.java new file mode 100644 index 0000000000..aebd6cd3de --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDiscoverer.java @@ -0,0 +1,54 @@ +/* + * 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.extensibility; + +import java.io.IOException; +import java.util.Collection; + +/** + * A SPI that allows different implementations of discovering service declarations + */ +public interface ServiceDiscoverer { + + /** + * Get all service declarations for this interface + * + * @param name + * @return set of service declarations + * @throws IOException + */ + public Collection getServiceDeclarations(String name) throws IOException; + + /** + * Get first service declaration class for the given interface + * + * @param name + * @return service implementation class + * @throws IOException + * @throws ClassNotFoundException + */ + public ServiceDeclaration getServiceDeclaration(String name) throws IOException; + + /** + * Get a classloader that can be used for thread context loader + * @return A classloader that can provide access to public classes and resources + */ + public ClassLoader getContextClassLoader(); +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDiscovery.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDiscovery.java new file mode 100644 index 0000000000..f99f682e07 --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceDiscovery.java @@ -0,0 +1,288 @@ +/* + * 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.extensibility; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.tuscany.sca.extensibility.impl.LDAPFilter; + +/** + * Service discovery for Tuscany based on J2SE Jar service provider spec. + * Services are described using configuration files in META-INF/services. + * Service description specifies a class name followed by optional properties. + * + * TODO: this is broken as it uses a static INSTANCE but non-static serviceAttributes + * and discoverer so the same INSTANCE gets used across NodeFactories and picks up + * old values + * + * @version $Rev$ $Date$ + * @tuscany.spi.extension.asclient + */ +public final class ServiceDiscovery implements ServiceDiscoverer { + private final static Logger logger = Logger.getLogger(ServiceDiscovery.class.getName()); + private final static ServiceDiscovery INSTANCE = new ServiceDiscovery(); + + private final Map> serviceAttributes = new HashMap>(); + private ServiceDiscoverer discoverer; + + private ServiceDiscovery() { + super(); + } + + private ServiceDiscovery(ServiceDiscoverer discoverer) { + super(); + this.discoverer = discoverer; + } + + /** + * Get an instance of Service discovery, one instance is created per + * ClassLoader that this class is loaded from + * + * @return + */ + public static ServiceDiscovery getInstance() { + return INSTANCE; + } + + public static ServiceDiscovery getInstance(ServiceDiscoverer discoverer) { + return new ServiceDiscovery(discoverer); + } + + public ServiceDiscoverer getServiceDiscoverer() { + if (discoverer != null) { + return discoverer; + } + try { + // FIXME: This is a hack to trigger the activation of the extensibility-equinox bundle in OSGi + Class.forName("org.apache.tuscany.sca.extensibility.equinox.EquinoxServiceDiscoverer"); + if (discoverer != null) { + return discoverer; + } + } catch (Throwable e) { + } + discoverer = new ContextClassLoaderServiceDiscoverer(getClass().getClassLoader()); + return discoverer; + } + + public void setServiceDiscoverer(ServiceDiscoverer sd) { + if (discoverer != null && sd != null) { + logger.warning("ServiceDiscoverer is reset to " + sd); + } + discoverer = sd; + } + + public Collection getServiceDeclarations(String name) throws IOException { + return getServiceDeclarations(name, false); + } + + public Collection getServiceDeclarations(String name, boolean byRanking) throws IOException { + Collection declarations = getServiceDiscoverer().getServiceDeclarations(name); + // Check if any of the service declarations has attributes that are overrided + if (!serviceAttributes.isEmpty()) { + for (ServiceDeclaration declaration : declarations) { + Map attrs = getAttributes(name); + if (attrs != null) { + declaration.getAttributes().putAll(attrs); + } + } + } + if (!byRanking) { + return declarations; + } + if (!declarations.isEmpty()) { + List declarationList = new ArrayList(declarations); + /* + for (ServiceDeclaration sd1 : declarations) { + for (Iterator i = declarationList.iterator(); i.hasNext();) { + ServiceDeclaration sd2 = i.next(); + if (sd1 != sd2 && sd1.getAttributes().equals(sd2.getAttributes())) { + logger + .warning("Duplicate service declarations: " + sd1.getLocation() + "," + sd2.getLocation()); + i.remove(); + } + } + } + */ + Collections.sort(declarationList, ServiceComparator.DESCENDING_ORDER); + return declarationList; + } else { + return Collections.emptyList(); + } + } + + /** + * Get the service declaration. If there are more than one services, the one with highest ranking will + * be returned. + */ + public ServiceDeclaration getServiceDeclaration(final String name) throws IOException { + Collection declarations = getServiceDeclarations(name, true); + if (!declarations.isEmpty()) { + // List declarationList = new ArrayList(declarations); + // Collections.sort(declarationList, ServiceComparator.DESCENDING_ORDER); + return declarations.iterator().next(); + } else { + return null; + } + } + + /** + * Get service declarations that are filtered by the service type. In an OSGi runtime, there + * might be different versions of the services + * @param serviceType + * @return + * @throws IOException + */ + public Collection getServiceDeclarations(Class serviceType, boolean byRanking) + throws IOException { + Collection sds = getServiceDeclarations(serviceType.getName(), byRanking); + for (Iterator i = sds.iterator(); i.hasNext();) { + ServiceDeclaration sd = i.next(); + if (!sd.isAssignableTo(serviceType)) { + logger.log(Level.WARNING, "Service provider {0} is not a type of {1}", new Object[] {sd,serviceType.getName()}); + i.remove(); + } + } + return sds; + } + + /** + * Discover all service providers that are compatible with the service type + * @param serviceType + * @return + * @throws IOException + */ + public Collection getServiceDeclarations(Class serviceType) throws IOException { + return getServiceDeclarations(serviceType, false); + } + + /** + * Discover all service providers that are compatible with the service type and match the filter + * @param serviceType + * @param filter + * @return + * @throws IOException + */ + public Collection getServiceDeclarations(Class serviceType, String filter) throws IOException { + Collection sds = getServiceDeclarations(serviceType, false); + Collection filtered = new ArrayList(); + LDAPFilter filterImpl = LDAPFilter.newInstance(filter); + for(ServiceDeclaration sd: sds) { + if(filterImpl.match(sd.getAttributes())) { + filtered.add(sd); + } + } + return filtered; + } + + /** + * @param serviceName + * @param filter + * @return + * @throws IOException + */ + public Collection getServiceDeclarations(String serviceName, String filter) throws IOException { + Collection sds = getServiceDeclarations(serviceName, false); + Collection filtered = new ArrayList(); + LDAPFilter filterImpl = LDAPFilter.newInstance(filter); + for(ServiceDeclaration sd: sds) { + if(filterImpl.match(sd.getAttributes())) { + filtered.add(sd); + } + } + return filtered; + } + + public ServiceDeclaration getServiceDeclaration(Class serviceType) throws IOException { + Collection sds = getServiceDeclarations(serviceType, true); + if (sds.isEmpty()) { + return null; + } else { + return sds.iterator().next(); + } + } + + /** + * Compare service declarations by ranking + */ + private static class ServiceComparator implements Comparator { + private final static Comparator DESCENDING_ORDER = new ServiceComparator(); + + public int compare(ServiceDeclaration o1, ServiceDeclaration o2) { + int rank1 = 0; + String r1 = o1.getAttributes().get("ranking"); + if (r1 != null) { + rank1 = Integer.parseInt(r1); + } + int rank2 = 0; + String r2 = o2.getAttributes().get("ranking"); + if (r2 != null) { + rank2 = Integer.parseInt(r2); + } + return rank2 - rank1; // descending + } + } + + public ClassLoader getContextClassLoader() { + return discoverer.getContextClassLoader(); + } + + /** + * Set the attributes for a given service type + * @param serviceType + * @param attributes + */ + public void setAttribute(String serviceType, Map attributes) { + serviceAttributes.put(serviceType, attributes); + } + + /** + * Set an attribute to the given value for a service type + * @param serviceType The service type + * @param attribute The attribute name + * @param value The attribute value + */ + public void setAttribute(String serviceType, String attribute, String value) { + Map attributes = serviceAttributes.get(serviceType); + if (attributes == null) { + attributes = new HashMap(); + serviceAttributes.put(serviceType, attributes); + } + attributes.put(attribute, value); + } + + /** + * Return a map of attributes for a given service type + * @param serviceType + * @return + */ + public Map getAttributes(String serviceType) { + return serviceAttributes.get(serviceType); + } +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceHelper.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceHelper.java new file mode 100644 index 0000000000..7f38aa6d06 --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/ServiceHelper.java @@ -0,0 +1,226 @@ +/* + * 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.extensibility; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collection; +import java.util.Map; + +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.core.LifeCycleListener; + +/** + * A helper for handling service lifecycle and instantiations + * @tuscany.spi.extension.asclient + */ +public class ServiceHelper { + private ServiceHelper() { + } + + /** + * Start the service instance + * @param instance + */ + public static boolean start(Object instance) { + if (instance instanceof LifeCycleListener) { + ((LifeCycleListener)instance).start(); + return true; + } + return false; + } + + /** + * Stop the service instance + * @param instance + */ + public static boolean stop(Object instance) { + if (instance instanceof LifeCycleListener) { + ((LifeCycleListener)instance).stop(); + return true; + } + return false; + } + + /** + * Stop a collection of service instances + * @param instances + */ + public static void stop(Collection instances) { + if (instances == null) { + return; + } + for (Object instance : instances) { + if (instance instanceof LifeCycleListener) { + ((LifeCycleListener)instance).stop(); + } + } + } + + /** + * Create a service instance with one parameter + * @param cls The service type + * @param parameterType The parameter type + * @param parameter The parameter value + * @return The newly created service instance + * @throws Exception + */ + public static T newInstance(Class cls, Class parameterType, Object parameter) throws Exception { + Constructor constructor = cls.getConstructor(parameterType); + return constructor.newInstance(parameter); + } + + /** + * Create a service instance with an array of parameters + * @param cls The service type + * @param parameterTypes An array of parameter types + * @param parameters An array of parameter values + * @return The newly created service instance + * @throws Exception + */ + public static T newInstance(Class cls, Class parameterTypes[], Object... parameters) throws Exception { + Constructor constructor = cls.getConstructor(parameterTypes); + return constructor.newInstance(parameters); + } + + /** + * Create a service instance with the default no-arg constructor + * @param cls The service type + * @return The newly created service instance + * @throws Exception + */ + public static T newInstance(Class cls) throws Exception { + Constructor constructor = cls.getConstructor(); + return constructor.newInstance(); + } + + private final static Class[] ARG_TYPES = new Class[] {ExtensionPointRegistry.class, Map.class}; + + /** + * Create a service instance from the service declaration + * @param + * @param registry The extension point registry + * @param sd The service declaration + * @return The newly created service instance + * @throws Exception + */ + public static T newInstance(ExtensionPointRegistry registry, ServiceDeclaration sd) throws Exception { + Class cls = (Class)sd.loadClass(); + T instance = null; + try { + // Try constructor(ExtensionPointRegistry.class) + instance = newInstance(cls, ExtensionPointRegistry.class, registry); + } catch (NoSuchMethodException e) { + try { + // Try Try constructor(ExtensionPointRegistry.class, Map.class) + instance = newInstance(cls, ARG_TYPES, registry, sd.getAttributes()); + } catch (NoSuchMethodException e1) { + // Try constructor() + instance = newInstance(cls); + } + } + return instance; + } + + public static T newLazyInstance(ExtensionPointRegistry registry, ServiceDeclaration sd, Class serviceType) { + return serviceType.cast(Proxy.newProxyInstance(serviceType.getClassLoader(), + new Class[] {serviceType, LifeCycleListener.class}, + new InvocationHandlerImpl(registry, serviceType, sd))); + } + + private static class InvocationHandlerImpl implements InvocationHandler { + private ExtensionPointRegistry registry; + private Class type; + private ServiceDeclaration sd; + private Object instance; + + private final static Method STOP_METHOD = getMethod(LifeCycleListener.class, "stop"); + private final static Method START_METHOD = getMethod(LifeCycleListener.class, "start"); + private final static Method EQUALS_METHOD = getMethod(Object.class, "equals"); + private final static Method HASHCODE_METHOD = getMethod(Object.class, "hashCode"); + private final static Method TOSTRING_METHOD = getMethod(Object.class, "toString"); + + private static Method getMethod(Class type, String name) { + Method[] methods = type.getMethods(); + for (Method method : methods) { + if (name.equals(method.getName())) { + return method; + } + } + return null; + } + + public InvocationHandlerImpl(ExtensionPointRegistry registry, Class type, ServiceDeclaration sd) { + super(); + this.registry = registry; + this.sd = sd; + this.type = type; + } + + private Object getAttribute(Method method) throws Exception { + if (method.getParameterTypes().length != 0) { + return null; + } + String name = method.getName(); + if (name.equals("getModelType") && method.getReturnType() == Class.class) { + return sd.loadClass(sd.getAttributes().get("model")); + } else if (name.equals("getArtifactType")) { + return ServiceDeclarationParser.getQName(sd.getAttributes().get("qname")); + } + return null; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + synchronized (this) { + // Check if the method is to get the qname/model attribute + Object value = getAttribute(method); + if (value != null) { + return value; + } + if (instance == null && method.getDeclaringClass() == type) { + // Only initialize the instance when a method on the service type is invoked + instance = newInstance(registry, sd); + start(instance); + } + if (method.equals(EQUALS_METHOD)) { + return proxy == args[0]; + } else if (method.equals(HASHCODE_METHOD)) { + return System.identityHashCode(proxy); + } else if (method.equals(TOSTRING_METHOD)) { + return "Proxy: " + sd.toString(); + } + if (instance == null) { + return null; + } + } + if (method.equals(STOP_METHOD)) { + stop(instance); + return null; + } else if (method.equals(START_METHOD)) { + // Skip the start() + return null; + } else { + return method.invoke(instance, args); + } + } + } +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/impl/ClassLoaderDelegate.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/impl/ClassLoaderDelegate.java new file mode 100644 index 0000000000..d052d6b10f --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/impl/ClassLoaderDelegate.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.extensibility.impl; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * A classloader that can delegate to a list of other classloaders + */ +public class ClassLoaderDelegate extends ClassLoader { + private final List classLoaders = new ArrayList(); + + /** + * @param parent The parent classloaders + * @param loaders A list of classloaders to be used to load classes or resources + */ + public ClassLoaderDelegate(ClassLoader parent, Collection loaders) { + super(parent); + if (loaders != null) { + for (ClassLoader cl : loaders) { + if (cl != null && cl != parent && !classLoaders.contains(cl)) { + this.classLoaders.add(cl); + } + } + } + } + + @Override + protected Class findClass(String className) throws ClassNotFoundException { + for (ClassLoader delegate : classLoaders) { + try { + return delegate.loadClass(className); + } catch (ClassNotFoundException e) { + continue; + } + } + throw new ClassNotFoundException(className); + } + + @Override + protected URL findResource(String resName) { + for (ClassLoader delegate : classLoaders) { + URL url = delegate.getResource(resName); + if (url != null) { + return url; + } + } + return null; + } + + @Override + protected Enumeration findResources(String resName) throws IOException { + Set urlSet = new HashSet(); + for (ClassLoader delegate : classLoaders) { + Enumeration urls = delegate.getResources(resName); + if (urls != null) { + while (urls.hasMoreElements()) { + urlSet.add(urls.nextElement()); + } + } + } + return Collections.enumeration(urlSet); + } +} diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/impl/InvalidSyntaxException.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/impl/InvalidSyntaxException.java new file mode 100644 index 0000000000..77f6f3e8db --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/impl/InvalidSyntaxException.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.extensibility.impl; + +/** + * A Framework exception used to indicate that a filter string has an invalid + * syntax. + * + *

    + * An InvalidSyntaxException object indicates that a filter + * string parameter has an invalid syntax and cannot be parsed. + * + *

    + * This exception conforms to the general purpose exception chaining mechanism. + * + */ + +public class InvalidSyntaxException extends RuntimeException { + static final long serialVersionUID = -4295194420816491875L; + /** + * The invalid filter string. + */ + private final String filter; + + /** + * Creates an exception of type InvalidSyntaxException. + * + *

    + * This method creates an InvalidSyntaxException object with + * the specified message and the filter string which generated the + * exception. + * + * @param msg The message. + * @param filter The invalid filter string. + */ + public InvalidSyntaxException(String msg, String filter) { + super(msg); + this.filter = filter; + } + + /** + * Creates an exception of type InvalidSyntaxException. + * + *

    + * This method creates an InvalidSyntaxException object with + * the specified message and the filter string which generated the + * exception. + * + * @param msg The message. + * @param filter The invalid filter string. + * @param cause The cause of this exception. + * @since 1.3 + */ + public InvalidSyntaxException(String msg, String filter, Throwable cause) { + super(msg, cause); + this.filter = filter; + } + + /** + * Returns the filter string that generated the + * InvalidSyntaxException object. + * + * @return The invalid filter string. + * @see BundleContext#getServiceReferences + * @see BundleContext#addServiceListener(ServiceListener,String) + */ + public String getFilter() { + return filter; + } +} \ No newline at end of file diff --git a/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/impl/LDAPFilter.java b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/impl/LDAPFilter.java new file mode 100644 index 0000000000..8d3630baa4 --- /dev/null +++ b/sandbox/sebastien/java/extend/modules/extensibility/src/main/java/org/apache/tuscany/sca/extensibility/impl/LDAPFilter.java @@ -0,0 +1,1373 @@ +/* + * Copyright (c) OSGi Alliance (2005, 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.extensibility.impl; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.apache.tuscany.sca.extensibility.ServiceDeclaration; + + +/** + * This code is derived from FrameworkUtil + *

    + * RFC 1960-based Filter. Filter objects can be created by calling the + * constructor with the desired filter string. A Filter object can be called + * numerous times to determine if the match argument matches the filter + * string that was used to create the Filter object. + * + *

    + * The syntax of a filter string is the string representation of LDAP search + * filters as defined in RFC 1960: A String Representation of LDAP Search + * Filters (available at http://www.ietf.org/rfc/rfc1960.txt). It should + * be noted that RFC 2254: A String Representation of LDAP Search + * Filters (available at http://www.ietf.org/rfc/rfc2254.txt) supersedes + * RFC 1960 but only adds extensible matching and is not applicable for this + * API. + * + *

    + * The string representation of an LDAP search filter is defined by the + * following grammar. It uses a prefix format. + * + *

    + *   <filter> ::= '(' <filtercomp> ')'
    + *   <filtercomp> ::= <and> | <or> | <not> | <item>
    + *   <and> ::= '&' <filterlist>
    + *   <or> ::= '|' <filterlist>
    + *   <not> ::= '!' <filter>
    + *   <filterlist> ::= <filter> | <filter> <filterlist>
    + *   <item> ::= <simple> | <present> | <substring>
    + *   <simple> ::= <attr> <filtertype> <value>
    + *   <filtertype> ::= <equal> | <approx> | <greater> | <less>
    + *   <equal> ::= '='
    + *   <approx> ::= '˜='
    + *   <greater> ::= '>='
    + *   <less> ::= '<='
    + *   <present> ::= <attr> '=*'
    + *   <substring> ::= <attr> '=' <initial> <any> <final>
    + *   <initial> ::= NULL | <value>
    + *   <any> ::= '*' <starval>
    + *   <starval> ::= NULL | <value> '*' <starval>
    + *   <final> ::= NULL | <value>
    + * 
    + * + * <attr> is a string representing an attribute, or key, + * in the properties objects of the registered services. Attribute names are + * not case sensitive; that is cn and CN both refer to the same attribute. + * <value> is a string representing the value, or part of + * one, of a key in the properties objects of the registered services. If a + * <value> must contain one of the characters ' + * *' or '(' or ')', these characters + * should be escaped by preceding them with the backslash '\' + * character. Note that although both the <substring> and + * <present> productions can produce the 'attr=*' + * construct, this construct is used only to denote a presence filter. + * + *

    + * Examples of LDAP filters are: + * + *

    + *   "(cn=Babs Jensen)"
    + *   "(!(cn=Tim Howes))"
    + *   "(&(" + Constants.OBJECTCLASS + "=Person)(|(sn=Jensen)(cn=Babs J*)))"
    + *   "(o=univ*of*mich*)"
    + * 
    + * + *

    + * The approximate match (~=) is implementation specific but + * should at least ignore case and white space differences. Optional are + * codes like soundex or other smart "closeness" comparisons. + * + *

    + * Comparison of values is not straightforward. Strings are compared + * differently than numbers and it is possible for a key to have multiple + * values. Note that that keys in the match argument must always be strings. + * The comparison is defined by the object type of the key's value. The + * following rules apply for comparison: + * + *

    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Property Value Type Comparison Type
    StringString comparison
    Integer, Long, Float, Double, Byte, Short, BigInteger, BigDecimalnumerical comparison
    Charactercharacter comparison
    Booleanequality comparisons only
    [] (array)recursively applied to values
    Collectionrecursively applied to values
    + * Note: arrays of primitives are also supported.
    + * + * A filter matches a key that has multiple values if it matches at least + * one of those values. For example, + * + *
    + * Dictionary d = new Hashtable();
    + * d.put("cn", new String[] {"a", "b", "c"});
    + * 
    + * + * d will match (cn=a) and also (cn=b) + * + *

    + * A filter component that references a key having an unrecognizable data + * type will evaluate to false . + */ +public class LDAPFilter { + /* filter operators */ + private static final int EQUAL = 1; + private static final int APPROX = 2; + private static final int GREATER = 3; + private static final int LESS = 4; + private static final int PRESENT = 5; + private static final int SUBSTRING = 6; + private static final int AND = 7; + private static final int OR = 8; + private static final int NOT = 9; + + /** filter operation */ + private final int op; + /** filter attribute or null if operation AND, OR or NOT */ + private final String attr; + /** filter operands */ + private final Object value; + + /* normalized filter string for Filter object */ + private transient volatile String filterString; + + /** + * Constructs a {@link LDAPFilter} object. This filter object may be + * used to match a {@link ServiceReference} or a Dictionary. + * + *

    + * If the filter cannot be parsed, an {@link InvalidSyntaxException} + * will be thrown with a human readable message where the filter became + * unparsable. + * + * @param filterString the filter string. + * @exception InvalidSyntaxException If the filter parameter contains an + * invalid filter string that cannot be parsed. + */ + public static LDAPFilter newInstance(String filterString) throws InvalidSyntaxException { + return new Parser(filterString).parse(); + } + + LDAPFilter(int operation, String attr, Object value) { + this.op = operation; + this.attr = attr; + this.value = value; + } + + /** + * Filter using a Dictionary. This Filter is + * executed using the specified Dictionary's keys and + * values. The keys are case insensitively matched with this + * Filter. + * + * @param dictionary The Dictionary whose keys are used in + * the match. + * @return true if the Dictionary's keys and + * values match this filter; false otherwise. + * @throws IllegalArgumentException If dictionary contains + * case variants of the same key name. + */ + public boolean match(Dictionary dictionary) { + return match0(new CaseInsensitiveDictionary(dictionary)); + } + + public boolean match(Map map) { + Properties props = new Properties(); + props.putAll(map); + return match0(new CaseInsensitiveDictionary(props)); + } + + public static boolean matches(ServiceDeclaration declaration, String filter) { + if (filter == null) { + return true; + } + LDAPFilter filterImpl = newInstance(filter); + return filterImpl.match(declaration.getAttributes()); + } + + /** + * Filter with case sensitivity using a Dictionary. This + * Filter is executed using the specified + * Dictionary's keys and values. The keys are case + * sensitively matched with this Filter. + * + * @param dictionary The Dictionary whose keys are used in + * the match. + * @return true if the Dictionary's keys and + * values match this filter; false otherwise. + * @since 1.3 + */ + public boolean matchCase(Dictionary dictionary) { + return match0(dictionary); + } + + /** + * Returns this Filter's filter string. + *

    + * The filter string is normalized by removing whitespace which does not + * affect the meaning of the filter. + * + * @return This Filter's filter string. + */ + public String toString() { + String result = filterString; + if (result == null) { + filterString = result = normalize(); + } + return result; + } + + /** + * Returns this Filter's normalized filter string. + *

    + * The filter string is normalized by removing whitespace which does not + * affect the meaning of the filter. + * + * @return This Filter's filter string. + */ + private String normalize() { + StringBuffer sb = new StringBuffer(); + sb.append('('); + + switch (op) { + case AND: { + sb.append('&'); + + LDAPFilter[] filters = (LDAPFilter[])value; + for (int i = 0, size = filters.length; i < size; i++) { + sb.append(filters[i].normalize()); + } + + break; + } + + case OR: { + sb.append('|'); + + LDAPFilter[] filters = (LDAPFilter[])value; + for (int i = 0, size = filters.length; i < size; i++) { + sb.append(filters[i].normalize()); + } + + break; + } + + case NOT: { + sb.append('!'); + LDAPFilter filter = (LDAPFilter)value; + sb.append(filter.normalize()); + + break; + } + + case SUBSTRING: { + sb.append(attr); + sb.append('='); + + String[] substrings = (String[])value; + + for (int i = 0, size = substrings.length; i < size; i++) { + String substr = substrings[i]; + + if (substr == null) /* * */{ + sb.append('*'); + } else /* xxx */{ + sb.append(encodeValue(substr)); + } + } + + break; + } + case EQUAL: { + sb.append(attr); + sb.append('='); + sb.append(encodeValue((String)value)); + + break; + } + case GREATER: { + sb.append(attr); + sb.append(">="); + sb.append(encodeValue((String)value)); + + break; + } + case LESS: { + sb.append(attr); + sb.append("<="); + sb.append(encodeValue((String)value)); + + break; + } + case APPROX: { + sb.append(attr); + sb.append("~="); + sb.append(encodeValue(approxString((String)value))); + + break; + } + + case PRESENT: { + sb.append(attr); + sb.append("=*"); + + break; + } + } + + sb.append(')'); + + return sb.toString(); + } + + /** + * Compares this Filter to another Filter. + * + *

    + * This implementation returns the result of calling + * this.toString().equals(obj.toString(). + * + * @param obj The object to compare against this Filter. + * @return If the other object is a Filter object, then + * returns the result of calling + * this.toString().equals(obj.toString(); + * false otherwise. + */ + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof LDAPFilter)) { + return false; + } + + return this.toString().equals(obj.toString()); + } + + /** + * Returns the hashCode for this Filter. + * + *

    + * This implementation returns the result of calling + * this.toString().hashCode(). + * + * @return The hashCode of this Filter. + */ + public int hashCode() { + return this.toString().hashCode(); + } + + /** + * Internal match routine. Dictionary parameter must support + * case-insensitive get. + * + * @param properties A dictionary whose keys are used in the match. + * @return If the Dictionary's keys match the filter, return + * true. Otherwise, return false. + */ + private boolean match0(Dictionary properties) { + switch (op) { + case AND: { + LDAPFilter[] filters = (LDAPFilter[])value; + for (int i = 0, size = filters.length; i < size; i++) { + if (!filters[i].match0(properties)) { + return false; + } + } + + return true; + } + + case OR: { + LDAPFilter[] filters = (LDAPFilter[])value; + for (int i = 0, size = filters.length; i < size; i++) { + if (filters[i].match0(properties)) { + return true; + } + } + + return false; + } + + case NOT: { + LDAPFilter filter = (LDAPFilter)value; + + return !filter.match0(properties); + } + + case SUBSTRING: + case EQUAL: + case GREATER: + case LESS: + case APPROX: { + Object prop = (properties == null) ? null : properties.get(attr); + + return compare(op, prop, value); + } + + case PRESENT: { + Object prop = (properties == null) ? null : properties.get(attr); + + return prop != null; + } + } + + return false; + } + + /** + * Encode the value string such that '(', '*', ')' and '\' are escaped. + * + * @param value unencoded value string. + * @return encoded value string. + */ + private static String encodeValue(String value) { + boolean encoded = false; + int inlen = value.length(); + int outlen = inlen << 1; /* inlen 2 */ + + char[] output = new char[outlen]; + value.getChars(0, inlen, output, inlen); + + int cursor = 0; + for (int i = inlen; i < outlen; i++) { + char c = output[i]; + + switch (c) { + case '(': + case '*': + case ')': + case '\\': { + output[cursor] = '\\'; + cursor++; + encoded = true; + + break; + } + } + + output[cursor] = c; + cursor++; + } + + return encoded ? new String(output, 0, cursor) : value; + } + + private boolean compare(int operation, Object value1, Object value2) { + if (value1 == null) { + return false; + } + if (value1 instanceof String) { + return compare_String(operation, (String)value1, value2); + } + + Class clazz = value1.getClass(); + if (clazz.isArray()) { + Class type = clazz.getComponentType(); + if (type.isPrimitive()) { + return compare_PrimitiveArray(operation, type, value1, value2); + } + return compare_ObjectArray(operation, (Object[])value1, value2); + } + if (value1 instanceof Collection) { + return compare_Collection(operation, (Collection)value1, value2); + } + if (value1 instanceof Integer) { + return compare_Integer(operation, ((Integer)value1).intValue(), value2); + } + if (value1 instanceof Long) { + return compare_Long(operation, ((Long)value1).longValue(), value2); + } + if (value1 instanceof Byte) { + return compare_Byte(operation, ((Byte)value1).byteValue(), value2); + } + if (value1 instanceof Short) { + return compare_Short(operation, ((Short)value1).shortValue(), value2); + } + if (value1 instanceof Character) { + return compare_Character(operation, ((Character)value1).charValue(), value2); + } + if (value1 instanceof Float) { + return compare_Float(operation, ((Float)value1).floatValue(), value2); + } + if (value1 instanceof Double) { + return compare_Double(operation, ((Double)value1).doubleValue(), value2); + } + if (value1 instanceof Boolean) { + return compare_Boolean(operation, ((Boolean)value1).booleanValue(), value2); + } + if (value1 instanceof Comparable) { + return compare_Comparable(operation, (Comparable)value1, value2); + } + return compare_Unknown(operation, value1, value2); // RFC 59 + } + + private boolean compare_Collection(int operation, Collection collection, Object value2) { + for (Iterator iterator = collection.iterator(); iterator.hasNext();) { + if (compare(operation, iterator.next(), value2)) { + return true; + } + } + return false; + } + + private boolean compare_ObjectArray(int operation, Object[] array, Object value2) { + for (int i = 0, size = array.length; i < size; i++) { + if (compare(operation, array[i], value2)) { + return true; + } + } + return false; + } + + private boolean compare_PrimitiveArray(int operation, Class type, Object primarray, Object value2) { + if (Integer.TYPE.isAssignableFrom(type)) { + int[] array = (int[])primarray; + for (int i = 0, size = array.length; i < size; i++) { + if (compare_Integer(operation, array[i], value2)) { + return true; + } + } + return false; + } + if (Long.TYPE.isAssignableFrom(type)) { + long[] array = (long[])primarray; + for (int i = 0, size = array.length; i < size; i++) { + if (compare_Long(operation, array[i], value2)) { + return true; + } + } + return false; + } + if (Byte.TYPE.isAssignableFrom(type)) { + byte[] array = (byte[])primarray; + for (int i = 0, size = array.length; i < size; i++) { + if (compare_Byte(operation, array[i], value2)) { + return true; + } + } + return false; + } + if (Short.TYPE.isAssignableFrom(type)) { + short[] array = (short[])primarray; + for (int i = 0, size = array.length; i < size; i++) { + if (compare_Short(operation, array[i], value2)) { + return true; + } + } + return false; + } + if (Character.TYPE.isAssignableFrom(type)) { + char[] array = (char[])primarray; + for (int i = 0, size = array.length; i < size; i++) { + if (compare_Character(operation, array[i], value2)) { + return true; + } + } + return false; + } + if (Float.TYPE.isAssignableFrom(type)) { + float[] array = (float[])primarray; + for (int i = 0, size = array.length; i < size; i++) { + if (compare_Float(operation, array[i], value2)) { + return true; + } + } + return false; + } + if (Double.TYPE.isAssignableFrom(type)) { + double[] array = (double[])primarray; + for (int i = 0, size = array.length; i < size; i++) { + if (compare_Double(operation, array[i], value2)) { + return true; + } + } + return false; + } + if (Boolean.TYPE.isAssignableFrom(type)) { + boolean[] array = (boolean[])primarray; + for (int i = 0, size = array.length; i < size; i++) { + if (compare_Boolean(operation, array[i], value2)) { + return true; + } + } + return false; + } + return false; + } + + private boolean compare_String(int operation, String string, Object value2) { + switch (operation) { + case SUBSTRING: { + String[] substrings = (String[])value2; + int pos = 0; + for (int i = 0, size = substrings.length; i < size; i++) { + String substr = substrings[i]; + + if (i + 1 < size) /* if this is not that last substr */{ + if (substr == null) /* * */{ + String substr2 = substrings[i + 1]; + + if (substr2 == null) /* ** */ + continue; /* ignore first star */ + /* xxx */ + int index = string.indexOf(substr2, pos); + if (index == -1) { + return false; + } + + pos = index + substr2.length(); + if (i + 2 < size) // if there are more + // substrings, increment + // over the string we just + // matched; otherwise need + // to do the last substr + // check + i++; + } else /* xxx */{ + int len = substr.length(); + if (string.regionMatches(pos, substr, 0, len)) { + pos += len; + } else { + return false; + } + } + } else /* last substr */{ + if (substr == null) /* * */{ + return true; + } + /* xxx */ + return string.endsWith(substr); + } + } + + return true; + } + case EQUAL: { + return string.equals(value2); + } + case APPROX: { + string = approxString(string); + String string2 = approxString((String)value2); + + return string.equalsIgnoreCase(string2); + } + case GREATER: { + return string.compareTo((String)value2) >= 0; + } + case LESS: { + return string.compareTo((String)value2) <= 0; + } + } + return false; + } + + private boolean compare_Integer(int operation, int intval, Object value2) { + if (operation == SUBSTRING) { + return false; + } + int intval2 = Integer.parseInt(((String)value2).trim()); + switch (operation) { + case APPROX: + case EQUAL: { + return intval == intval2; + } + case GREATER: { + return intval >= intval2; + } + case LESS: { + return intval <= intval2; + } + } + return false; + } + + private boolean compare_Long(int operation, long longval, Object value2) { + if (operation == SUBSTRING) { + return false; + } + long longval2 = Long.parseLong(((String)value2).trim()); + switch (operation) { + case APPROX: + case EQUAL: { + return longval == longval2; + } + case GREATER: { + return longval >= longval2; + } + case LESS: { + return longval <= longval2; + } + } + return false; + } + + private boolean compare_Byte(int operation, byte byteval, Object value2) { + if (operation == SUBSTRING) { + return false; + } + byte byteval2 = Byte.parseByte(((String)value2).trim()); + switch (operation) { + case APPROX: + case EQUAL: { + return byteval == byteval2; + } + case GREATER: { + return byteval >= byteval2; + } + case LESS: { + return byteval <= byteval2; + } + } + return false; + } + + private boolean compare_Short(int operation, short shortval, Object value2) { + if (operation == SUBSTRING) { + return false; + } + short shortval2 = Short.parseShort(((String)value2).trim()); + switch (operation) { + case APPROX: + case EQUAL: { + return shortval == shortval2; + } + case GREATER: { + return shortval >= shortval2; + } + case LESS: { + return shortval <= shortval2; + } + } + return false; + } + + private boolean compare_Character(int operation, char charval, Object value2) { + if (operation == SUBSTRING) { + return false; + } + char charval2 = (((String)value2).trim()).charAt(0); + switch (operation) { + case EQUAL: { + return charval == charval2; + } + case APPROX: { + return (charval == charval2) || (Character.toUpperCase(charval) == Character.toUpperCase(charval2)) + || (Character.toLowerCase(charval) == Character.toLowerCase(charval2)); + } + case GREATER: { + return charval >= charval2; + } + case LESS: { + return charval <= charval2; + } + } + return false; + } + + private boolean compare_Boolean(int operation, boolean boolval, Object value2) { + if (operation == SUBSTRING) { + return false; + } + boolean boolval2 = Boolean.valueOf(((String)value2).trim()).booleanValue(); + switch (operation) { + case APPROX: + case EQUAL: + case GREATER: + case LESS: { + return boolval == boolval2; + } + } + return false; + } + + private boolean compare_Float(int operation, float floatval, Object value2) { + if (operation == SUBSTRING) { + return false; + } + float floatval2 = Float.parseFloat(((String)value2).trim()); + switch (operation) { + case APPROX: + case EQUAL: { + return Float.compare(floatval, floatval2) == 0; + } + case GREATER: { + return Float.compare(floatval, floatval2) >= 0; + } + case LESS: { + return Float.compare(floatval, floatval2) <= 0; + } + } + return false; + } + + private boolean compare_Double(int operation, double doubleval, Object value2) { + if (operation == SUBSTRING) { + return false; + } + double doubleval2 = Double.parseDouble(((String)value2).trim()); + switch (operation) { + case APPROX: + case EQUAL: { + return Double.compare(doubleval, doubleval2) == 0; + } + case GREATER: { + return Double.compare(doubleval, doubleval2) >= 0; + } + case LESS: { + return Double.compare(doubleval, doubleval2) <= 0; + } + } + return false; + } + + private static final Class[] constructorType = new Class[] {String.class}; + + private boolean compare_Comparable(int operation, Comparable value1, Object value2) { + if (operation == SUBSTRING) { + return false; + } + Constructor constructor; + try { + constructor = value1.getClass().getConstructor(constructorType); + } catch (NoSuchMethodException e) { + return false; + } + try { + if (!constructor.isAccessible()) + AccessController.doPrivileged(new SetAccessibleAction(constructor)); + value2 = constructor.newInstance(new Object[] {((String)value2).trim()}); + } catch (IllegalAccessException e) { + return false; + } catch (InvocationTargetException e) { + return false; + } catch (InstantiationException e) { + return false; + } + + switch (operation) { + case APPROX: + case EQUAL: { + return value1.compareTo(value2) == 0; + } + case GREATER: { + return value1.compareTo(value2) >= 0; + } + case LESS: { + return value1.compareTo(value2) <= 0; + } + } + return false; + } + + private boolean compare_Unknown(int operation, Object value1, Object value2) { + if (operation == SUBSTRING) { + return false; + } + Constructor constructor; + try { + constructor = value1.getClass().getConstructor(constructorType); + } catch (NoSuchMethodException e) { + return false; + } + try { + if (!constructor.isAccessible()) + AccessController.doPrivileged(new SetAccessibleAction(constructor)); + value2 = constructor.newInstance(new Object[] {((String)value2).trim()}); + } catch (IllegalAccessException e) { + return false; + } catch (InvocationTargetException e) { + return false; + } catch (InstantiationException e) { + return false; + } + + switch (operation) { + case APPROX: + case EQUAL: + case GREATER: + case LESS: { + return value1.equals(value2); + } + } + return false; + } + + /** + * Map a string for an APPROX (~=) comparison. + * + * This implementation removes white spaces. This is the minimum + * implementation allowed by the OSGi spec. + * + * @param input Input string. + * @return String ready for APPROX comparison. + */ + private static String approxString(String input) { + boolean changed = false; + char[] output = input.toCharArray(); + int cursor = 0; + for (int i = 0, length = output.length; i < length; i++) { + char c = output[i]; + + if (Character.isWhitespace(c)) { + changed = true; + continue; + } + + output[cursor] = c; + cursor++; + } + + return changed ? new String(output, 0, cursor) : input; + } + + /** + * Parser class for OSGi filter strings. This class parses the complete + * filter string and builds a tree of Filter objects rooted at the + * parent. + */ + private static class Parser { + private final String filterstring; + private final char[] filterChars; + private int pos; + + Parser(String filterstring) { + this.filterstring = filterstring; + filterChars = filterstring.toCharArray(); + pos = 0; + } + + LDAPFilter parse() throws InvalidSyntaxException { + LDAPFilter filter; + try { + filter = parse_filter(); + } catch (ArrayIndexOutOfBoundsException e) { + throw new InvalidSyntaxException("Filter ended abruptly", filterstring); + } + + if (pos != filterChars.length) { + throw new InvalidSyntaxException("Extraneous trailing characters: " + filterstring.substring(pos), + filterstring); + } + return filter; + } + + private LDAPFilter parse_filter() throws InvalidSyntaxException { + LDAPFilter filter; + skipWhiteSpace(); + + if (filterChars[pos] != '(') { + throw new InvalidSyntaxException("Missing '(': " + filterstring.substring(pos), filterstring); + } + + pos++; + + filter = parse_filtercomp(); + + skipWhiteSpace(); + + if (filterChars[pos] != ')') { + throw new InvalidSyntaxException("Missing ')': " + filterstring.substring(pos), filterstring); + } + + pos++; + + skipWhiteSpace(); + + return filter; + } + + private LDAPFilter parse_filtercomp() throws InvalidSyntaxException { + skipWhiteSpace(); + + char c = filterChars[pos]; + + switch (c) { + case '&': { + pos++; + return parse_and(); + } + case '|': { + pos++; + return parse_or(); + } + case '!': { + pos++; + return parse_not(); + } + } + return parse_item(); + } + + private LDAPFilter parse_and() throws InvalidSyntaxException { + skipWhiteSpace(); + + if (filterChars[pos] != '(') { + throw new InvalidSyntaxException("Missing '(': " + filterstring.substring(pos), filterstring); + } + + List operands = new ArrayList(10); + + while (filterChars[pos] == '(') { + LDAPFilter child = parse_filter(); + operands.add(child); + } + + return new LDAPFilter(LDAPFilter.AND, null, operands.toArray(new LDAPFilter[operands.size()])); + } + + private LDAPFilter parse_or() throws InvalidSyntaxException { + skipWhiteSpace(); + + if (filterChars[pos] != '(') { + throw new InvalidSyntaxException("Missing '(': " + filterstring.substring(pos), filterstring); + } + + List operands = new ArrayList(10); + + while (filterChars[pos] == '(') { + LDAPFilter child = parse_filter(); + operands.add(child); + } + + return new LDAPFilter(LDAPFilter.OR, null, operands.toArray(new LDAPFilter[operands.size()])); + } + + private LDAPFilter parse_not() throws InvalidSyntaxException { + skipWhiteSpace(); + + if (filterChars[pos] != '(') { + throw new InvalidSyntaxException("Missing '(': " + filterstring.substring(pos), filterstring); + } + + LDAPFilter child = parse_filter(); + + return new LDAPFilter(LDAPFilter.NOT, null, child); + } + + private LDAPFilter parse_item() throws InvalidSyntaxException { + String attr = parse_attr(); + + skipWhiteSpace(); + + switch (filterChars[pos]) { + case '~': { + if (filterChars[pos + 1] == '=') { + pos += 2; + return new LDAPFilter(LDAPFilter.APPROX, attr, parse_value()); + } + break; + } + case '>': { + if (filterChars[pos + 1] == '=') { + pos += 2; + return new LDAPFilter(LDAPFilter.GREATER, attr, parse_value()); + } + break; + } + case '<': { + if (filterChars[pos + 1] == '=') { + pos += 2; + return new LDAPFilter(LDAPFilter.LESS, attr, parse_value()); + } + break; + } + case '=': { + if (filterChars[pos + 1] == '*') { + int oldpos = pos; + pos += 2; + skipWhiteSpace(); + if (filterChars[pos] == ')') { + return new LDAPFilter(LDAPFilter.PRESENT, attr, null); + } + pos = oldpos; + } + + pos++; + Object string = parse_substring(); + + if (string instanceof String) { + return new LDAPFilter(LDAPFilter.EQUAL, attr, string); + } + return new LDAPFilter(LDAPFilter.SUBSTRING, attr, string); + } + } + + throw new InvalidSyntaxException("Invalid operator: " + filterstring.substring(pos), filterstring); + } + + private String parse_attr() throws InvalidSyntaxException { + skipWhiteSpace(); + + int begin = pos; + int end = pos; + + char c = filterChars[pos]; + + while (c != '~' && c != '<' && c != '>' && c != '=' && c != '(' && c != ')') { + pos++; + + if (!Character.isWhitespace(c)) { + end = pos; + } + + c = filterChars[pos]; + } + + int length = end - begin; + + if (length == 0) { + throw new InvalidSyntaxException("Missing attr: " + filterstring.substring(pos), filterstring); + } + + return new String(filterChars, begin, length); + } + + private String parse_value() throws InvalidSyntaxException { + StringBuffer sb = new StringBuffer(filterChars.length - pos); + + parseloop: while (true) { + char c = filterChars[pos]; + + switch (c) { + case ')': { + break parseloop; + } + + case '(': { + throw new InvalidSyntaxException("Invalid value: " + filterstring.substring(pos), + filterstring); + } + + case '\\': { + pos++; + c = filterChars[pos]; + /* fall through into default */ + } + + default: { + sb.append(c); + pos++; + break; + } + } + } + + if (sb.length() == 0) { + throw new InvalidSyntaxException("Missing value: " + filterstring.substring(pos), filterstring); + } + + return sb.toString(); + } + + private Object parse_substring() throws InvalidSyntaxException { + StringBuffer sb = new StringBuffer(filterChars.length - pos); + + List operands = new ArrayList(10); + + parseloop: while (true) { + char c = filterChars[pos]; + + switch (c) { + case ')': { + if (sb.length() > 0) { + operands.add(sb.toString()); + } + + break parseloop; + } + + case '(': { + throw new InvalidSyntaxException("Invalid value: " + filterstring.substring(pos), + filterstring); + } + + case '*': { + if (sb.length() > 0) { + operands.add(sb.toString()); + } + + sb.setLength(0); + + operands.add(null); + pos++; + + break; + } + + case '\\': { + pos++; + c = filterChars[pos]; + /* fall through into default */ + } + + default: { + sb.append(c); + pos++; + break; + } + } + } + + int size = operands.size(); + + if (size == 0) { + throw new InvalidSyntaxException("Missing value: " + filterstring.substring(pos), filterstring); + } + + if (size == 1) { + Object single = operands.get(0); + + if (single != null) { + return single; + } + } + + return operands.toArray(new String[size]); + } + + private void skipWhiteSpace() { + for (int length = filterChars.length; (pos < length) && Character.isWhitespace(filterChars[pos]);) { + pos++; + } + } + } + + /** + * This Dictionary is used for case-insensitive key lookup during filter + * evaluation. This Dictionary implementation only supports the get + * operation using a String key as no other operations are used by the + * Filter implementation. + */ + static class CaseInsensitiveDictionary extends Dictionary { + private final Dictionary dictionary; + private final String[] keys; + + /** + * Create a case insensitive dictionary from the specified dictionary. + * + * @param dictionary + * @throws IllegalArgumentException If dictionary contains + * case variants of the same key name. + */ + CaseInsensitiveDictionary(Dictionary dictionary) { + if (dictionary == null) { + this.dictionary = null; + this.keys = new String[0]; + return; + } + this.dictionary = dictionary; + List keyList = new ArrayList(dictionary.size()); + for (Enumeration e = dictionary.keys(); e.hasMoreElements();) { + Object k = e.nextElement(); + if (k instanceof String) { + String key = (String)k; + for (Iterator i = keyList.iterator(); i.hasNext();) { + if (key.equalsIgnoreCase((String)i.next())) { + throw new IllegalArgumentException(); + } + } + keyList.add(key); + } + } + this.keys = (String[])keyList.toArray(new String[keyList.size()]); + } + + public Object get(Object o) { + String k = (String)o; + for (int i = 0, length = keys.length; i < length; i++) { + String key = keys[i]; + if (key.equalsIgnoreCase(k)) { + return dictionary.get(key); + } + } + return null; + } + + public boolean isEmpty() { + throw new UnsupportedOperationException(); + } + + public Enumeration keys() { + throw new UnsupportedOperationException(); + } + + public Enumeration elements() { + throw new UnsupportedOperationException(); + } + + public Object put(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + public int size() { + throw new UnsupportedOperationException(); + } + } + + static class SetAccessibleAction implements PrivilegedAction { + private final AccessibleObject accessible; + + SetAccessibleAction(AccessibleObject accessible) { + this.accessible = accessible; + } + + public Object run() { + accessible.setAccessible(true); + return null; + } + } +} \ No newline at end of file -- cgit v1.2.3