Committed the initial version of the dependency manager.
Added a "pom.xml" but since I'm fairly new at maven, this was copied from other examples.
Changed the package name to o.a.f.dependencymanager.
Modified a couple of classes so the code compiles with Java 1.4 (but we want to go for the minimum execution environment ultimately).
git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@388822 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/DefaultNullObject.java b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/DefaultNullObject.java
new file mode 100644
index 0000000..5b1acd1
--- /dev/null
+++ b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/DefaultNullObject.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * 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.felix.dependencymanager;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+
+
+/**
+ * Default null object implementation. Uses a dynamic proxy. Null objects are used
+ * as placeholders for services that are not available.
+ *
+ * @author Marcel Offermans
+ */
+public class DefaultNullObject implements InvocationHandler {
+ private static final Boolean DEFAULT_BOOLEAN = Boolean.FALSE;
+ private static final Byte DEFAULT_BYTE = new Byte((byte) 0);
+ private static final Short DEFAULT_SHORT = new Short((short) 0);
+ private static final Integer DEFAULT_INT = new Integer(0);
+ private static final Long DEFAULT_LONG = new Long(0);
+ private static final Float DEFAULT_FLOAT = new Float(0.0f);
+ private static final Double DEFAULT_DOUBLE = new Double(0.0);
+
+ /**
+ * Invokes a method on this null object. The method will return a default
+ * value without doing anything.
+ */
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ Class returnType = method.getReturnType();
+ if (returnType.equals(Boolean.class) || returnType.equals(Boolean.TYPE)) {
+ return DEFAULT_BOOLEAN;
+ }
+ else if (returnType.equals(Byte.class) || returnType.equals(Byte.TYPE)) {
+ return DEFAULT_BYTE;
+ }
+ else if (returnType.equals(Short.class) || returnType.equals(Short.TYPE)) {
+ return DEFAULT_SHORT;
+ }
+ else if (returnType.equals(Integer.class) || returnType.equals(Integer.TYPE)) {
+ return DEFAULT_INT;
+ }
+ else if (returnType.equals(Long.class) || returnType.equals(Long.TYPE)) {
+ return DEFAULT_LONG;
+ }
+ else if (returnType.equals(Float.class) || returnType.equals(Float.TYPE)) {
+ return DEFAULT_FLOAT;
+ }
+ else if (returnType.equals(Double.class) || returnType.equals(Double.TYPE)) {
+ return DEFAULT_DOUBLE;
+ }
+ else {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/Dependency.java b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/Dependency.java
new file mode 100644
index 0000000..97a0866
--- /dev/null
+++ b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/Dependency.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * 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.felix.dependencymanager;
+
+/**
+ * Generic dependency for a service. A dependency can be required or not.
+ * A dependency will be activated by the service it belongs to. The service
+ * will call the <code>start(Service service)</code> and
+ * <code>stop(Service service)</code> methods.
+ *
+ * After it has been started, a dependency must callback
+ * the associated service's <code>dependencyAvailable()</code> and
+ * <code>dependencyUnavailable()</code>
+ * methods. State changes of the dependency itself may only be made as long as
+ * the dependency is not 'active', meaning it is associated with a running service.
+ *
+ * @author Marcel Offermans
+ */
+public interface Dependency {
+ /**
+ * Returns <code>true</code> if this a required dependency. Required dependencies
+ * are dependencies that must be available before the service can be activated.
+ *
+ * @return <code>true</code> if the dependency is required
+ */
+ public boolean isRequired();
+
+ /**
+ * Returns <code>true</code> if the dependency is available.
+ *
+ * @return <code>true</code> if the dependency is available
+ */
+ public boolean isAvailable();
+
+ /**
+ * Starts tracking the dependency. This activates some implementation
+ * specific mechanism to do the actual tracking. If the tracking discovers
+ * that the dependency becomes available, it should call
+ * <code>dependencyAvailable()</code> on the service.
+ *
+ * @param service the service that is associated with this dependency
+ */
+ public void start(Service service);
+
+ /**
+ * Stops tracking the dependency. This deactivates the tracking. If the
+ * dependency was available, the tracker should call
+ * <code>dependencyUnavaible()</code> before stopping itself to ensure
+ * that dependencies that aren't "active" are unavailable.
+ */
+ public void stop(Service service);
+}
diff --git a/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/DependencyActivatorBase.java b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/DependencyActivatorBase.java
new file mode 100644
index 0000000..38847b0
--- /dev/null
+++ b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/DependencyActivatorBase.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * 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.felix.dependencymanager;
+
+import java.util.List;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Base bundle activator class. Subclass this activator if you want to use dependency
+ * management in your bundle. There are two methods you should implement:
+ * <code>init()</code> and <code>destroy()</code>. Both methods take two arguments,
+ * the bundle context and the dependency manager. The dependency manager can be used
+ * to define all the dependencies.
+ *
+ * @author Marcel Offermans
+ */
+public abstract class DependencyActivatorBase implements BundleActivator {
+ private BundleContext m_context;
+ private DependencyManager m_manager;
+
+ /**
+ * Initialize the dependency manager. Here you can add all services and their dependencies.
+ * If something goes wrong and you do not want your bundle to be started, you can throw an
+ * exception. This exception will be passed on to the <code>start()</code> method of the
+ * bundle activator, causing the bundle not to start.
+ *
+ * @param context the bundle context
+ * @param manager the dependency manager
+ * @throws Exception if the initialization fails
+ */
+ public abstract void init(BundleContext context, DependencyManager manager) throws Exception;
+
+ /**
+ * Destroy the dependency manager. Here you can remove all services and their dependencies.
+ * Actually, the base class will clean up your dependencies anyway, so most of the time you
+ * don't need to do anything here.
+ * If something goes wrong and you do not want your bundle to be stopped, you can throw an
+ * exception. This exception will be passed on to the <code>stop()</code> method of the
+ * bundle activator, causing the bundle not to stop.
+ *
+ * @param context the bundle context
+ * @param manager the dependency manager
+ * @throws Exception if the destruction fails
+ */
+ public abstract void destroy(BundleContext context, DependencyManager manager) throws Exception;
+
+ /**
+ * Start method of the bundle activator. Initializes the dependency manager
+ * and calls <code>init()</code>.
+ *
+ * @param context the bundle context
+ */
+ public void start(BundleContext context) throws Exception {
+ m_context = context;
+ m_manager = new DependencyManager(context);
+ init(m_context, m_manager);
+ }
+
+ /**
+ * Stop method of the bundle activator. Calls the <code>destroy()</code> method
+ * and cleans up all left over dependencies.
+ *
+ * @param context the bundle context
+ */
+ public void stop(BundleContext context) throws Exception {
+ destroy(m_context, m_manager);
+ cleanup(m_manager);
+ m_manager = null;
+ m_context = null;
+ }
+
+ /**
+ * Creates a new service.
+ *
+ * @return the new service
+ */
+ public Service createService() {
+ return new ServiceImpl(m_context);
+ }
+
+ /**
+ * Creates a new service dependency.
+ *
+ * @return the service dependency
+ */
+ public ServiceDependency createServiceDependency() {
+ return new ServiceDependency(m_context);
+ }
+
+ /**
+ * Cleans up all services and their dependencies.
+ *
+ * @param manager the dependency manager
+ */
+ private void cleanup(DependencyManager manager) {
+ List services = manager.getServices();
+ for (int i = services.size() - 1; i >= 0; i--) {
+ Service service = (Service) services.get(i);
+ manager.remove(service);
+ // remove any state listeners that are still registered
+ if (service instanceof ServiceImpl) {
+ ServiceImpl si = (ServiceImpl) service;
+ si.removeStateListeners();
+ }
+ }
+ }
+}
diff --git a/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/DependencyManager.java b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/DependencyManager.java
new file mode 100644
index 0000000..e641615
--- /dev/null
+++ b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/DependencyManager.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * 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.felix.dependencymanager;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.osgi.framework.BundleContext;
+
+/**
+ * The dependency manager. Manages all services and their dependencies.
+ *
+ * @author Marcel Offermans
+ */
+public class DependencyManager {
+ private BundleContext m_context;
+ private List m_services = Collections.synchronizedList(new ArrayList());
+
+ /**
+ * Creates a new dependency manager.
+ *
+ * @param context the bundle context
+ */
+ public DependencyManager(BundleContext context) {
+ m_context = context;
+ }
+
+ /**
+ * Adds a new service to the dependency manager. After the service was added
+ * it will be started immediately.
+ *
+ * @param service the service to add
+ */
+ public void add(Service service) {
+ m_services.add(service);
+ service.start();
+ }
+
+ /**
+ * Removes a service from the dependency manager. Before the service is removed
+ * it is stopped first.
+ *
+ * @param service the service to remove
+ */
+ public void remove(Service service) {
+ service.stop();
+ m_services.remove(service);
+ }
+
+ /**
+ * Creates a new service.
+ *
+ * @return the new service
+ */
+ public Service createService() {
+ return new ServiceImpl(m_context);
+ }
+
+ /**
+ * Creates a new service dependency.
+ *
+ * @return the service dependency
+ */
+ public ServiceDependency createServiceDependency() {
+ return new ServiceDependency(m_context);
+ }
+
+ /**
+ * Returns a list of services.
+ *
+ * @return a list of services
+ */
+ public List getServices() {
+ return Collections.unmodifiableList(m_services);
+ }
+
+}
diff --git a/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/Service.java b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/Service.java
new file mode 100644
index 0000000..85a6c4a
--- /dev/null
+++ b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/Service.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * 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.felix.dependencymanager;
+
+import java.util.Dictionary;
+import java.util.List;
+
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Service interface.
+ *
+ * @author Marcel Offermans
+ */
+public interface Service {
+ /**
+ * Adds a new dependency to this service.
+ *
+ * @param dependency the dependency to add
+ * @return this service
+ */
+ public Service add(Dependency dependency);
+
+ /**
+ * Removes a dependency from this service.
+ *
+ * @param dependency the dependency to remove
+ * @return this service
+ */
+ public Service remove(Dependency dependency);
+
+ /**
+ * Sets the public interface under which this service should be registered
+ * in the OSGi service registry.
+ *
+ * @param serviceName the name of the service interface
+ * @param properties the properties for this service
+ * @return this service
+ */
+ public Service setInterface(String serviceName, Dictionary properties);
+ /**
+ * Sets the public interfaces under which this service should be registered
+ * in the OSGi service registry.
+ *
+ * @param serviceNames the names of the service interface
+ * @param properties the properties for this service
+ * @return this service
+ */
+ public Service setInterface(String[] serviceNames, Dictionary properties);
+
+ /**
+ * Sets the implementation for this service. You can actually specify
+ * an instance you have instantiated manually, or a <code>Class</code>
+ * that will be instantiated using its default constructor when the
+ * required dependencies are resolved (effectively giving you a lazy
+ * instantiation mechanism).
+ *
+ * There are four special methods that are called when found through
+ * reflection to give you some life-cycle management options:
+ * <ol>
+ * <li><code>init()</code> when the implementation should be initialized,
+ * before it is actually registered as a service (if at all)</li>
+ * <li><code>start()</code> when the implementation has been registered
+ * as a service (if at all)</li>
+ * <li><code>stop()</code> when the implementation will be stopped, just
+ * before the service will go away (if it had been registered)</li>
+ * <li><code>destroy()</code>after the service has gone away (if it had
+ * been registered)</li>
+ * </ol>
+ * In short, this allows you to initialize your instance before it is
+ * registered, perform some post-initialization and pre-destruction code
+ * as well as final cleanup. If a method is not defined, it simply is not
+ * called, so you can decide which one(s) you need.
+ *
+ * @param implementation the implementation
+ * @return this service
+ */
+ public Service setImplementation(Object implementation);
+
+ /**
+ * Returns a list of dependencies.
+ *
+ * @return a list of dependencies
+ */
+ public List getDependencies();
+
+ /**
+ * Returns the service registration for this service. The method
+ * will return <code>null</code> if no service registration is
+ * available.
+ *
+ * @return the service registration
+ */
+ public ServiceRegistration getServiceRegistration();
+
+ /**
+ * Returns the service instance for this service. The method will
+ * return <code>null</code> if no service instance is available.
+ *
+ * @return the service instance
+ */
+ public Object getService();
+
+ /**
+ * Returns the service properties associated with the service.
+ *
+ * @return the properties or <code>null</code> if there are none
+ */
+ public Dictionary getServiceProperties();
+
+ /**
+ * Sets the service properties associated with the service. If the service
+ * was already registered, it will be updated.
+ *
+ * @param serviceProperties the properties
+ */
+ public void setServiceProperties(Dictionary serviceProperties);
+
+ /**
+ * Sets the names of the methods used as callbacks. These methods, when found, are
+ * invoked as part of the life-cycle management of the service implementation. The
+ * methods should not have any parameters.
+ *
+ * @param init the name of the init method
+ * @param start the name of the start method
+ * @param stop the name of the stop method
+ * @param destroy the name of the destroy method
+ * @return the service instance
+ */
+ public Service setCallbacks(String init, String start, String stop, String destroy);
+
+ // listener
+ /**
+ * Adds a service state listener to this service.
+ *
+ * @param listener the state listener
+ */
+ public void addStateListener(ServiceStateListener listener);
+
+ /**
+ * Removes a service state listener from this service.
+ *
+ * @param listener the state listener
+ */
+ public void removeStateListener(ServiceStateListener listener);
+
+ // events, must be fired when the dependency is started/active
+
+ /**
+ * Will be called when the dependency becomes available.
+ *
+ * @param dependency the dependency
+ */
+ public void dependencyAvailable(Dependency dependency);
+
+ /**
+ * Will be called when the dependency changes.
+ *
+ * @param dependency the dependency
+ */
+ public void dependencyUnavailable(Dependency dependency);
+
+ /**
+ * Will be called when the dependency becomes unavailable.
+ *
+ * @param dependency the dependency
+ */
+ public void dependencyChanged(Dependency dependency);
+
+ /**
+ * Starts the service. This activates the dependency tracking mechanism
+ * for this service.
+ */
+ public void start();
+
+ /**
+ * Stops the service. This deactivates the dependency tracking mechanism
+ * for this service.
+ */
+ public void stop();
+}
diff --git a/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceDependency.java b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceDependency.java
new file mode 100644
index 0000000..6f0d97f
--- /dev/null
+++ b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceDependency.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * 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.felix.dependencymanager;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Service dependency that can track an OSGi service.
+ *
+ * @author Marcel Offermans
+ */
+public class ServiceDependency implements Dependency, ServiceTrackerCustomizer {
+ private boolean m_isRequired;
+ private Service m_service;
+ private ServiceTracker m_tracker;
+ private BundleContext m_context;
+ private boolean m_isAvailable;
+ private Class m_trackedServiceName;
+ private Object m_nullObject;
+ private String m_trackedServiceFilter;
+ private ServiceReference m_trackedServiceReference;
+ private boolean m_isStarted;
+ private Object m_callbackInstance;
+ private String m_callbackAdded;
+ private String m_callbackChanged;
+ private String m_callbackRemoved;
+ private boolean m_autoConfig;
+ private ServiceReference m_reference;
+ private Object m_serviceInstance;
+
+ /**
+ * Creates a new service dependency.
+ *
+ * @param context the bundle context
+ */
+ public ServiceDependency(BundleContext context) {
+ m_context = context;
+ m_autoConfig = true;
+ }
+
+ public boolean isRequired() {
+ return m_isRequired;
+ }
+
+ public boolean isAvailable() {
+ return m_isAvailable;
+ }
+
+ public boolean isAutoConfig() {
+ return m_autoConfig;
+ }
+
+ public synchronized Object getService() {
+ Object service = null;
+ if (m_isStarted) {
+ service = m_tracker.getService();
+ }
+ if (service == null) {
+ service = getNullObject();
+ }
+ return service;
+ }
+
+ private Object getNullObject() {
+ if (m_nullObject == null) {
+ m_nullObject = Proxy.newProxyInstance(m_trackedServiceName.getClassLoader(), new Class[] {m_trackedServiceName}, new DefaultNullObject());
+ }
+ return m_nullObject;
+ }
+
+ public Class getInterface() {
+ return m_trackedServiceName;
+ }
+
+ public synchronized void start(Service service) {
+ if (m_isStarted) {
+ throw new IllegalStateException("Service dependency was already started." + m_trackedServiceName);
+ }
+ m_service = service;
+ if (m_trackedServiceName != null) {
+ if (m_trackedServiceFilter != null) {
+ try {
+ m_tracker = new ServiceTracker(m_context, m_context.createFilter(m_trackedServiceFilter), this);
+ }
+ catch (InvalidSyntaxException e) {
+ throw new IllegalStateException("Invalid filter definition for dependency.");
+ }
+ }
+ else if (m_trackedServiceReference != null) {
+ m_tracker = new ServiceTracker(m_context, m_trackedServiceReference, this);
+ }
+ else {
+ m_tracker = new ServiceTracker(m_context, m_trackedServiceName.getName(), this);
+ }
+ }
+ else {
+ throw new IllegalStateException("Could not create tracker for dependency, no service name specified.");
+ }
+ m_isStarted = true;
+ m_tracker.open();
+ }
+
+ public synchronized void stop(Service service) {
+ if (!m_isStarted) {
+ throw new IllegalStateException("Service dependency was not started.");
+ }
+ m_tracker.close();
+ m_isStarted = false;
+ m_tracker = null;
+ }
+
+ public Object addingService(ServiceReference ref) {
+ Object service = m_context.getService(ref);
+ // we remember these for future reference, needed for required service callbacks
+ m_reference = ref;
+ m_serviceInstance = service;
+ return service;
+ }
+
+ public void addedService(ServiceReference ref, Object service) {
+ if (makeAvailable()) {
+ m_service.dependencyAvailable(this);
+ }
+ else {
+ m_service.dependencyChanged(this);
+ }
+ // try to invoke callback, if specified, but only for optional dependencies
+ // because callbacks for required dependencies are handled differently
+ if (!isRequired()) {
+ invokeAdded(ref, service);
+ }
+ }
+
+ public void invokeAdded() {
+ invokeAdded(m_reference, m_serviceInstance);
+ }
+
+ public void invokeAdded(ServiceReference reference, Object serviceInstance) {
+ Object callbackInstance = getCallbackInstance();
+ if ((callbackInstance != null) && (m_callbackAdded != null)) {
+ try {
+ invokeCallbackMethod(callbackInstance, m_callbackAdded, reference, serviceInstance);
+ } catch (NoSuchMethodException e) {
+ // silently ignore this
+ }
+ }
+ }
+
+ public void modifiedService(ServiceReference ref, Object service) {
+ m_reference = ref;
+ m_serviceInstance = service;
+ m_service.dependencyChanged(this);
+ // only invoke the changed callback if the service itself is "active"
+ if (((ServiceImpl) m_service).isRegistered()) {
+ invokeChanged(ref, service);
+ }
+ }
+
+ public void invokeChanged(ServiceReference reference, Object serviceInstance) {
+ Object callbackInstance = getCallbackInstance();
+ if ((callbackInstance != null) && (m_callbackChanged != null)) {
+ try {
+ if (m_reference == null) {
+ Thread.dumpStack();
+ }
+ invokeCallbackMethod(callbackInstance, m_callbackChanged, reference, serviceInstance);
+ }
+ catch (NoSuchMethodException e) {
+ // ignore when the service has no such method
+ }
+ }
+ }
+
+ public void removedService(ServiceReference ref, Object service) {
+ if (makeUnavailable()) {
+ m_service.dependencyUnavailable(this);
+ }
+ // try to invoke callback, if specified, but only for optional dependencies
+ // because callbacks for required dependencies are handled differently
+ if (!isRequired()) {
+ invokeRemoved(ref, service);
+ }
+ m_context.ungetService(ref);
+ }
+
+ public void invokeRemoved() {
+ invokeRemoved(m_reference, m_serviceInstance);
+ }
+
+ public void invokeRemoved(ServiceReference reference, Object serviceInstance) {
+ Object callbackInstance = getCallbackInstance();
+ if ((callbackInstance != null) && (m_callbackRemoved != null)) {
+ try {
+ if (m_reference == null) {
+ Thread.dumpStack();
+ }
+ invokeCallbackMethod(callbackInstance, m_callbackRemoved, reference, serviceInstance);
+ }
+ catch (NoSuchMethodException e) {
+ // ignore when the service has no such method
+ }
+ }
+ }
+
+ private synchronized boolean makeAvailable() {
+ if (!m_isAvailable) {
+ m_isAvailable = true;
+ return true;
+ }
+ return false;
+ }
+
+ private synchronized boolean makeUnavailable() {
+ if ((m_isAvailable) && (m_tracker.getServiceReference() == null)) {
+ m_isAvailable = false;
+ return true;
+ }
+ return false;
+ }
+
+ private Object getCallbackInstance() {
+ Object callbackInstance = m_callbackInstance;
+ if (callbackInstance == null) {
+ callbackInstance = m_service.getService();
+ }
+ return callbackInstance;
+ }
+
+ // TODO a lot of things in this method can be cached instead of done each time
+ // TODO Richard had an example where he could not invoke a private method
+ private void invokeCallbackMethod(Object instance, String methodName, ServiceReference reference, Object service) throws NoSuchMethodException {
+ Method method = null;
+ Class clazz = instance.getClass();
+ AccessibleObject.setAccessible(clazz.getDeclaredMethods(), true);
+ try {
+ try {
+ method = clazz.getDeclaredMethod(methodName, new Class[] {ServiceReference.class, Object.class});
+ method.invoke(instance, new Object[] {reference, service});
+ }
+ catch (NoSuchMethodException e) {
+ try {
+ method = clazz.getDeclaredMethod(methodName, new Class[] {ServiceReference.class});
+ method.invoke(instance, new Object[] {reference});
+ }
+ catch (NoSuchMethodException e1) {
+ try {
+ method = clazz.getDeclaredMethod(methodName, new Class[] {Object.class});
+ method.invoke(instance, new Object[] {service});
+ }
+ catch (NoSuchMethodException e2) {
+ try {
+ method = clazz.getDeclaredMethod(methodName, new Class[] {m_trackedServiceName});
+ method.invoke(instance, new Object[] {service});
+ }
+ catch (NoSuchMethodException e3) {
+ method = clazz.getDeclaredMethod(methodName, null);
+ method.invoke(instance, null);
+ }
+ }
+ }
+ }
+ } catch (IllegalArgumentException e1) {
+ // TODO handle this exception, probably best to ignore it
+ e1.printStackTrace();
+ } catch (IllegalAccessException e1) {
+ // TODO handle this exception, probably best to ignore it
+ e1.printStackTrace();
+ } catch (InvocationTargetException e1) {
+ // TODO handle this exception, probably best to ignore it
+ e1.printStackTrace();
+ }
+ }
+
+ // ----- CREATION
+
+ /**
+ * Sets the name of the service that should be tracked.
+ *
+ * @param serviceName the name of the service
+ * @return this service dependency
+ */
+ public synchronized ServiceDependency setService(Class serviceName) {
+ ensureNotActive();
+ if (serviceName == null) {
+ throw new IllegalArgumentException("Service name cannot be null.");
+ }
+ m_trackedServiceName = serviceName;
+ m_trackedServiceReference = null;
+ m_trackedServiceFilter = null;
+ return this;
+ }
+
+ /**
+ * Sets the name of the service that should be tracked. You can either specify
+ * only the name, or the name and a filter. In the latter case, the filter is used
+ * to track the service and should only return services of the type that was specified
+ * in the name.
+ *
+ * @param serviceName the name of the service
+ * @param serviceFilter the filter condition
+ * @return this service dependency
+ */
+ public synchronized ServiceDependency setService(Class serviceName, String serviceFilter) {
+ ensureNotActive();
+ if (serviceName == null) {
+ throw new IllegalArgumentException("Service name cannot be null.");
+ }
+ m_trackedServiceName = serviceName;
+ m_trackedServiceFilter = serviceFilter;
+ m_trackedServiceReference = null;
+ return this;
+ }
+
+ /**
+ * Sets the name of the service that should be tracked. You can either specify
+ * only the name, or the name and a reference. In the latter case, the service reference
+ * is used to track the service and should only return services of the type that was
+ * specified in the name.
+ *
+ * @param serviceName the name of the service
+ * @param serviceReference the service reference to track
+ * @return this service dependency
+ */
+ public synchronized ServiceDependency setService(Class serviceName, ServiceReference serviceReference) {
+ ensureNotActive();
+ if (serviceName == null) {
+ throw new IllegalArgumentException("Service name cannot be null.");
+ }
+ m_trackedServiceName = serviceName;
+ m_trackedServiceReference = serviceReference;
+ m_trackedServiceFilter = null;
+ return this;
+ }
+
+ /**
+ * Sets the required flag which determines if this service is required or not.
+ *
+ * @param required the required flag
+ * @return this service dependency
+ */
+ public synchronized ServiceDependency setRequired(boolean required) {
+ ensureNotActive();
+ m_isRequired = required;
+ return this;
+ }
+
+ /**
+ * Sets auto configuration for this service. Auto configuration allows the
+ * dependency to fill in any attributes in the service implementation that
+ * are of the same type as this dependency. Default is on.
+ *
+ * @param autoConfig the value of auto config
+ * @return this service dependency
+ */
+ public synchronized ServiceDependency setAutoConfig(boolean autoConfig) {
+ ensureNotActive();
+ m_autoConfig = autoConfig;
+ return this;
+ }
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever
+ * a dependency is added or removed. They are called on the service implementation.
+ *
+ * @param added the method to call when a service was added
+ * @param removed the method to call when a service was removed
+ * @return this service dependency
+ */
+ public synchronized ServiceDependency setCallbacks(String added, String removed) {
+ return setCallbacks(null, added, null, removed);
+ }
+ public synchronized ServiceDependency setCallbacks(String added, String changed, String removed) {
+ return setCallbacks(null, added, changed, removed);
+ }
+ public synchronized ServiceDependency setCallbacks(Object instance, String added, String removed) {
+ return setCallbacks(instance, added, null, removed);
+ }
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever
+ * a dependency is added or removed. They are called on the instance you provide.
+ *
+ * @param instance the instance to call the callbacks on
+ * @param added the method to call when a service was added
+ * @param changed the method to call when a service was changed
+ * @param removed the method to call when a service was removed
+ * @return this service dependency
+ */
+ public synchronized ServiceDependency setCallbacks(Object instance, String added, String changed, String removed) {
+ ensureNotActive();
+ m_callbackInstance = instance;
+ m_callbackAdded = added;
+ m_callbackChanged = changed;
+ m_callbackRemoved = removed;
+ return this;
+ }
+
+
+ private void ensureNotActive() {
+ if (m_tracker != null) {
+ throw new IllegalStateException("Cannot modify state while active.");
+ }
+ }
+
+ public String toString() {
+ return "ServiceDependency[" + m_trackedServiceName + " " + m_trackedServiceFilter + " " + m_isRequired + "] for " + m_service;
+ }
+}
diff --git a/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceImpl.java b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceImpl.java
new file mode 100644
index 0000000..26a6a4f
--- /dev/null
+++ b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceImpl.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * 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.felix.dependencymanager;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Service implementation.
+ *
+ * @author Marcel Offermans
+ */
+public class ServiceImpl implements Service {
+ private static final ServiceRegistration NULL_REGISTRATION;
+ private static final int STARTING = 1;
+ private static final int WAITING_FOR_REQUIRED = 2;
+ private static final int TRACKING_OPTIONAL = 3;
+ private static final int STOPPING = 4;
+ private static final String[] STATE_NAMES = {
+ "(unknown)",
+ "starting",
+ "waiting for required dependencies",
+ "tracking optional dependencies",
+ "stopping"};
+
+ private BundleContext m_context;
+ private ServiceRegistration m_registration;
+
+ private String m_callbackInit;
+ private String m_callbackStart;
+ private String m_callbackStop;
+ private String m_callbackDestroy;
+
+ private List m_listeners = new ArrayList();
+ private ArrayList m_dependencies = new ArrayList();
+ private int m_state;
+
+ private Object m_serviceInstance;
+ private Object m_implementation;
+ private Object m_serviceName;
+ private Dictionary m_serviceProperties;
+
+ public ServiceImpl(BundleContext context) {
+ m_state = STARTING;
+ m_context = context;
+ m_callbackInit = "init";
+ m_callbackStart = "start";
+ m_callbackStop = "stop";
+ m_callbackDestroy = "destroy";
+ }
+
+ public Service add(Dependency dependency) {
+ synchronized (m_dependencies) {
+ m_dependencies.add(dependency);
+ }
+ if (m_state == WAITING_FOR_REQUIRED) {
+ // if we're waiting for required dependencies, and
+ // this is a required dependency, start tracking it
+ // ...otherwise, we don't need to do anything yet
+ if (dependency.isRequired()) {
+ dependency.start(this);
+ }
+ }
+ else if (m_state == TRACKING_OPTIONAL) {
+ // start tracking the dependency
+ dependency.start(this);
+ if (dependency.isRequired() && !dependency.isAvailable()) {
+ // if this is a required dependency and it can not
+ // be resolved right away, then we need to go back to
+ // the waiting for required state, until this
+ // dependency is available
+ deactivateService();
+ }
+ }
+ return this;
+ }
+
+ public Service remove(Dependency dependency) {
+ synchronized (m_dependencies) {
+ m_dependencies.remove(dependency);
+ }
+ if (m_state == TRACKING_OPTIONAL) {
+ // if we're tracking optional dependencies, then any
+ // dependency that is removed can be stopped without
+ // causing state changes
+ dependency.stop(this);
+ }
+ else if (m_state == WAITING_FOR_REQUIRED) {
+ // if we're waiting for required dependencies, then
+ // we only need to stop tracking the dependency if it
+ // too is required; this might trigger a state change
+ dependency.stop(this);
+ if (allRequiredDependenciesAvailable()) {
+ activateService();
+ }
+ }
+ return this;
+ }
+
+ public List getDependencies() {
+ List list;
+ synchronized (m_dependencies) {
+ list = (List) m_dependencies.clone();
+ }
+ return list;
+ }
+
+ public ServiceRegistration getServiceRegistration() {
+ return m_registration;
+ }
+
+ public Object getService() {
+ return m_serviceInstance;
+ }
+
+ public void dependencyAvailable(Dependency dependency) {
+ if ((dependency.isRequired())
+ && (m_state == WAITING_FOR_REQUIRED)
+ && (allRequiredDependenciesAvailable())) {
+ activateService();
+ }
+ if ((!dependency.isRequired()) && (m_state == TRACKING_OPTIONAL)) {
+ updateInstance(dependency);
+ }
+ }
+
+ public void dependencyChanged(Dependency dependency) {
+ if (m_state == TRACKING_OPTIONAL) {
+ updateInstance(dependency);
+ }
+ }
+
+ public void dependencyUnavailable(Dependency dependency) {
+ if (dependency.isRequired()) {
+ if (m_state == TRACKING_OPTIONAL) {
+ if (!allRequiredDependenciesAvailable()) {
+ deactivateService();
+ }
+ }
+ }
+ else {
+ // optional dependency
+ }
+ if (m_state == TRACKING_OPTIONAL) {
+ updateInstance(dependency);
+ }
+ }
+
+ public synchronized void start() {
+ if ((m_state != STARTING) && (m_state != STOPPING)) {
+ throw new IllegalStateException("Cannot start from state " + STATE_NAMES[m_state]);
+ }
+ startTrackingRequired();
+ if (allRequiredDependenciesAvailable() && (m_state == WAITING_FOR_REQUIRED)) {
+ activateService();
+ }
+ }
+
+ public synchronized void stop() {
+ if ((m_state != WAITING_FOR_REQUIRED) && (m_state != TRACKING_OPTIONAL)) {
+ if ((m_state > 0) && (m_state < STATE_NAMES.length)) {
+ throw new IllegalStateException("Cannot stop from state " + STATE_NAMES[m_state]);
+ }
+ throw new IllegalStateException("Cannot stop from unknown state.");
+ }
+ if (m_state == TRACKING_OPTIONAL) {
+ deactivateService();
+ }
+ stopTrackingRequired();
+ }
+
+ private void activateService() {
+ // service activation logic, first we initialize the service instance itself
+ // meaning it is created if necessary and the bundle context is set
+ initService();
+ // then we invoke the init callback so the service can further initialize
+ // itself
+ invoke(m_callbackInit);
+ // now is the time to configure the service, meaning all required
+ // dependencies will be set and any callbacks called
+ configureService();
+ // inform the state listeners we're starting
+ stateListenersStarting();
+ // start tracking optional services
+ startTrackingOptional();
+ // invoke the start callback, since we're now ready to be used
+ invoke(m_callbackStart);
+ // register the service in the framework's service registry
+ registerService();
+ // inform the state listeners we've started
+ stateListenersStarted();
+ }
+
+ private void deactivateService() {
+ // service deactivation logic, first inform the state listeners
+ // we're stopping
+ stateListenersStopping();
+ // then, unregister the service from the framework
+ unregisterService();
+ // invoke the stop callback
+ invoke(m_callbackStop);
+ // stop tracking optional services
+ stopTrackingOptional();
+ // inform the state listeners we've stopped
+ stateListenersStopped();
+ // invoke the destroy callback
+ invoke(m_callbackDestroy);
+ // destroy the service instance
+ destroyService();
+ }
+
+ private void invoke(String name) {
+ if (name != null) {
+ // invoke method if it exists
+ AccessibleObject.setAccessible(m_serviceInstance.getClass().getDeclaredMethods(), true);
+ try {
+ m_serviceInstance.getClass().getDeclaredMethod(name, null).invoke(m_serviceInstance, null);
+ }
+ catch (NoSuchMethodException e) {
+ // ignore this, we don't care if the method does not exist
+ }
+ catch (Exception e) {
+ // TODO handle this exception
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private synchronized void stateListenersStarting() {
+ Iterator i = m_listeners.iterator();
+ while (i.hasNext()) {
+ ServiceStateListener ssl = (ServiceStateListener) i.next();
+ ssl.starting(this);
+ }
+ }
+
+ private synchronized void stateListenersStarted() {
+ Iterator i = m_listeners.iterator();
+ while (i.hasNext()) {
+ ServiceStateListener ssl = (ServiceStateListener) i.next();
+ ssl.started(this);
+ }
+ }
+
+ private synchronized void stateListenersStopping() {
+ Iterator i = m_listeners.iterator();
+ while (i.hasNext()) {
+ ServiceStateListener ssl = (ServiceStateListener) i.next();
+ ssl.stopping(this);
+ }
+ }
+
+ private synchronized void stateListenersStopped() {
+ Iterator i = m_listeners.iterator();
+ while (i.hasNext()) {
+ ServiceStateListener ssl = (ServiceStateListener) i.next();
+ ssl.stopped(this);
+ }
+ }
+
+ private boolean allRequiredDependenciesAvailable() {
+ Iterator i = getDependencies().iterator();
+ while (i.hasNext()) {
+ Dependency dependency = (Dependency) i.next();
+ if (dependency.isRequired() && !dependency.isAvailable()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void startTrackingOptional() {
+ m_state = TRACKING_OPTIONAL;
+ Iterator i = getDependencies().iterator();
+ while (i.hasNext()) {
+ Dependency dependency = (Dependency) i.next();
+ if (!dependency.isRequired()) {
+ dependency.start(this);
+ }
+ }
+ }
+
+ private void stopTrackingOptional() {
+ m_state = WAITING_FOR_REQUIRED;
+ Iterator i = getDependencies().iterator();
+ while (i.hasNext()) {
+ Dependency dependency = (Dependency) i.next();
+ if (!dependency.isRequired()) {
+ dependency.stop(this);
+ }
+ }
+ }
+
+ private void startTrackingRequired() {
+ m_state = WAITING_FOR_REQUIRED;
+ Iterator i = getDependencies().iterator();
+ while (i.hasNext()) {
+ Dependency dependency = (Dependency) i.next();
+ if (dependency.isRequired()) {
+ dependency.start(this);
+ }
+ }
+ }
+
+ private void stopTrackingRequired() {
+ m_state = STOPPING;
+ Iterator i = getDependencies().iterator();
+ while (i.hasNext()) {
+ Dependency dependency = (Dependency) i.next();
+ if (dependency.isRequired()) {
+ dependency.stop(this);
+ }
+ }
+ }
+
+ private void initService() {
+ if (m_implementation instanceof Class) {
+ // instantiate
+ try {
+ m_serviceInstance = ((Class) m_implementation).newInstance();
+ } catch (InstantiationException e) {
+ // TODO handle this exception
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO handle this exception
+ e.printStackTrace();
+ }
+ }
+ else {
+ m_serviceInstance = m_implementation;
+ }
+ // configure the bundle context
+ configureImplementation(BundleContext.class, m_context);
+ configureImplementation(ServiceRegistration.class, NULL_REGISTRATION);
+
+ }
+
+ private void configureService() {
+ // configure all services (the optional dependencies might be configured
+ // as null objects but that's what we want at this point)
+ configureServices();
+ }
+
+ private void destroyService() {
+ unconfigureServices();
+ m_serviceInstance = null;
+ }
+
+ private void registerService() {
+ if (m_serviceName != null) {
+ ServiceRegistrationImpl wrapper = new ServiceRegistrationImpl();
+ m_registration = wrapper;
+ configureImplementation(ServiceRegistration.class, wrapper);
+ // service name can either be a string or an array of strings
+ ServiceRegistration registration;
+ if (m_serviceName instanceof String) {
+ registration = m_context.registerService((String) m_serviceName, m_serviceInstance, m_serviceProperties);
+ }
+ else {
+ registration = m_context.registerService((String[]) m_serviceName, m_serviceInstance, m_serviceProperties);
+ }
+ wrapper.setServiceRegistration(registration);
+ }
+ }
+
+ private void unregisterService() {
+ if (m_serviceName != null) {
+ m_registration.unregister();
+ configureImplementation(ServiceRegistration.class, NULL_REGISTRATION);
+ }
+ }
+
+ private void updateInstance(Dependency dependency) {
+ if (dependency instanceof ServiceDependency) {
+ ServiceDependency sd = (ServiceDependency) dependency;
+ // update the dependency in the service instance (it will use
+ // a null object if necessary)
+ if (sd.isAutoConfig()) {
+ configureImplementation(sd.getInterface(), sd.getService());
+ }
+ }
+ }
+
+ /**
+ * Configure a field in the service implementation. The service implementation
+ * is searched for fields that have the same type as the class that was specified
+ * and for each of these fields, the specified instance is filled in.
+ *
+ * @param clazz the class to search for
+ * @param instance the instance to fill in
+ */
+ private void configureImplementation(Class clazz, Object instance) {
+ Class serviceClazz = m_serviceInstance.getClass();
+ while (serviceClazz != null) {
+ Field[] fields = serviceClazz.getDeclaredFields();
+ AccessibleObject.setAccessible(fields, true);
+ for (int j = 0; j < fields.length; j++) {
+ if (fields[j].getType().equals(clazz)) {
+ try {
+ // synchronized makes sure the field is actually written to immediately
+ synchronized (new Object()) {
+ fields[j].set(m_serviceInstance, instance);
+ }
+ }
+ catch (Exception e) {
+ System.err.println("Exception while trying to set " + fields[j].getName() +
+ " of type " + fields[j].getType().getName() +
+ " by classloader " + fields[j].getType().getClassLoader() +
+ " which should equal type " + clazz.getName() +
+ " by classloader " + clazz.getClassLoader() +
+ " of type " + serviceClazz.getName() +
+ " by classloader " + serviceClazz.getClassLoader() +
+ " on " + m_serviceInstance +
+ " by classloader " + m_serviceInstance.getClass().getClassLoader() +
+ "\nDumping stack:"
+ );
+ e.printStackTrace();
+ System.out.println("C: " + clazz);
+ System.out.println("I: " + instance);
+ System.out.println("I:C: " + instance.getClass().getClassLoader());
+ Class[] classes = instance.getClass().getInterfaces();
+ for (int i = 0; i < classes.length; i++) {
+ Class c = classes[i];
+ System.out.println("I:C:I: " + c);
+ System.out.println("I:C:I:C: " + c.getClassLoader());
+ }
+ System.out.println("F: " + fields[j]);
+ throw new IllegalStateException("Could not set field " + fields[j].getName() + " on " + m_serviceInstance);
+ }
+ }
+ }
+ serviceClazz = serviceClazz.getSuperclass();
+ }
+ }
+
+ private void configureServices() {
+ Iterator i = getDependencies().iterator();
+ while (i.hasNext()) {
+ Dependency dependency = (Dependency) i.next();
+ if (dependency instanceof ServiceDependency) {
+ ServiceDependency sd = (ServiceDependency) dependency;
+ if (sd.isAutoConfig()) {
+ configureImplementation(sd.getInterface(), sd.getService());
+ }
+ // for required dependencies, we invoke any callbacks here
+ if (sd.isRequired()) {
+ sd.invokeAdded();
+ }
+ }
+ }
+ }
+
+ private void unconfigureServices() {
+ Iterator i = getDependencies().iterator();
+ while (i.hasNext()) {
+ Dependency dependency = (Dependency) i.next();
+ if (dependency instanceof ServiceDependency) {
+ ServiceDependency sd = (ServiceDependency) dependency;
+ // for required dependencies, we invoke any callbacks here
+ if (sd.isRequired()) {
+ sd.invokeRemoved();
+ }
+ }
+ }
+ }
+
+ public synchronized void addStateListener(ServiceStateListener listener) {
+ m_listeners.add(listener);
+ if (m_state == TRACKING_OPTIONAL) {
+ listener.starting(this);
+ listener.started(this);
+ }
+ }
+
+ public synchronized void removeStateListener(ServiceStateListener listener) {
+ m_listeners.remove(listener);
+ }
+
+ synchronized void removeStateListeners() {
+ m_listeners.clear();
+ }
+
+ public synchronized Service setInterface(String serviceName, Dictionary properties) {
+ ensureNotActive();
+ m_serviceName = serviceName;
+ m_serviceProperties = properties;
+ return this;
+ }
+
+ public synchronized Service setInterface(String[] serviceName, Dictionary properties) {
+ ensureNotActive();
+ m_serviceName = serviceName;
+ m_serviceProperties = properties;
+ return this;
+ }
+
+ public synchronized Service setCallbacks(String init, String start, String stop, String destroy) {
+ ensureNotActive();
+ m_callbackInit = init;
+ m_callbackStart = start;
+ m_callbackStop = stop;
+ m_callbackDestroy = destroy;
+ return this;
+ }
+
+ public synchronized Service setImplementation(Object implementation) {
+ ensureNotActive();
+ m_implementation = implementation;
+ return this;
+ }
+
+ private void ensureNotActive() {
+ if ((m_state == TRACKING_OPTIONAL) || (m_state == WAITING_FOR_REQUIRED)) {
+ throw new IllegalStateException("Cannot modify state while active.");
+ }
+ }
+ boolean isRegistered() {
+ return (m_state == TRACKING_OPTIONAL);
+ }
+
+ public String toString() {
+ return "ServiceImpl[" + m_serviceName + " " + m_implementation + "]";
+ }
+
+ public synchronized Dictionary getServiceProperties() {
+ if (m_serviceProperties != null) {
+ return (Dictionary) ((Hashtable) m_serviceProperties).clone();
+ }
+ return null;
+ }
+
+ public synchronized void setServiceProperties(Dictionary serviceProperties) {
+ m_serviceProperties = serviceProperties;
+ if (isRegistered() && (m_serviceName != null) && (m_serviceProperties != null)) {
+ m_registration.setProperties(m_serviceProperties);
+ }
+ }
+
+ static {
+ NULL_REGISTRATION = (ServiceRegistration) Proxy.newProxyInstance(ServiceImpl.class.getClassLoader(), new Class[] {ServiceRegistration.class}, new DefaultNullObject());
+ }
+}
diff --git a/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceRegistrationImpl.java b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceRegistrationImpl.java
new file mode 100644
index 0000000..6e5189d
--- /dev/null
+++ b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceRegistrationImpl.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * 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.felix.dependencymanager;
+
+import java.util.Dictionary;
+
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * A wrapper around a service registration that blocks until the
+ * service registration is available.
+ *
+ * @author Marcel Offermans
+ */
+public class ServiceRegistrationImpl implements ServiceRegistration {
+ private ServiceRegistration m_registration;
+
+ public ServiceRegistrationImpl() {
+ m_registration = null;
+ }
+
+ public ServiceReference getReference() {
+ ensureRegistration();
+ return m_registration.getReference();
+ }
+
+ public void setProperties(Dictionary dictionary) {
+ ensureRegistration();
+ m_registration.setProperties(dictionary);
+ }
+
+ public void unregister() {
+ ensureRegistration();
+ m_registration.unregister();
+ }
+
+ public boolean equals(Object obj) {
+ ensureRegistration();
+ return m_registration.equals(obj);
+ }
+
+ public int hashCode() {
+ ensureRegistration();
+ return m_registration.hashCode();
+ }
+
+ public String toString() {
+ ensureRegistration();
+ return m_registration.toString();
+ }
+
+ private synchronized void ensureRegistration() {
+ while (m_registration == null) {
+ try {
+ wait();
+ }
+ catch (InterruptedException ie) {
+ // we were interrupted so hopefully we will now have a
+ // service registration ready; if not we wait again
+ }
+ }
+ }
+
+ void setServiceRegistration(ServiceRegistration registration) {
+ m_registration = registration;
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+}
diff --git a/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceStateListener.java b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceStateListener.java
new file mode 100644
index 0000000..468a266
--- /dev/null
+++ b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceStateListener.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * 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.felix.dependencymanager;
+
+/**
+ * This interface can be used to register a service state listener. Service
+ * state listeners are called whenever a service state changes. You get notified
+ * when the service is starting, started, stopping and stopped. Each callback
+ * includes a reference to the service in question.
+ *
+ * @author Marcel Offermans
+ */
+public interface ServiceStateListener {
+ /**
+ * Called when the service is starting.
+ *
+ * @param service the service
+ */
+ public void starting(Service service);
+ /**
+ * Called when the service is started.
+ *
+ * @param service the service
+ */
+ public void started(Service service);
+ /**
+ * Called when the service is stopping.
+ *
+ * @param service the service
+ */
+ public void stopping(Service service);
+ /**
+ * Called when the service is stopped.
+ *
+ * @param service the service
+ */
+ public void stopped(Service service);
+}
diff --git a/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceTracker.java b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceTracker.java
new file mode 100644
index 0000000..1135c80
--- /dev/null
+++ b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceTracker.java
@@ -0,0 +1,847 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * 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.felix.dependencymanager;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * TODO copied this from the OSGi specification, but it's not clear if that
+ * is allowed or not, for now I modified as little as possible but I might
+ * integrate only the parts I want as soon as this code is finished. Perhaps
+ * it would be better to borrow the Knopflerfish implementation here.
+ *
+ * @author Marcel Offermans
+ */
+public class ServiceTracker implements ServiceTrackerCustomizer
+{
+ /**
+ * Bundle context this <tt>ServiceTracker</tt> object is tracking against.
+ */
+ protected final BundleContext context;
+
+ /**
+ * Filter specifying search criteria for the services to track.
+ * @since 1.1
+ */
+ protected final Filter filter;
+
+ /**
+ * Tracked services: <tt>ServiceReference</tt> object -> customized Object
+ * and <tt>ServiceListener</tt> object
+ */
+ private Tracked tracked;
+
+ /** <tt>ServiceTrackerCustomizer</tt> object for this tracker. */
+ private ServiceTrackerCustomizer customizer;
+
+ /**
+ * Create a <tt>ServiceTracker</tt> object on the specified <tt>ServiceReference</tt> object.
+ *
+ * <p>The service referenced by the specified <tt>ServiceReference</tt> object
+ * will be tracked by this <tt>ServiceTracker</tt> object.
+ *
+ * @param context <tt>BundleContext</tt> object against which the tracking is done.
+ * @param reference <tt>ServiceReference</tt> object for the service to be tracked.
+ * @param customizer The customizer object to call when services are
+ * added, modified, or removed in this <tt>ServiceTracker</tt> object.
+ * If customizer is <tt>null</tt>, then this <tt>ServiceTracker</tt> object will be used
+ * as the <tt>ServiceTrackerCustomizer</tt> object and the <tt>ServiceTracker</tt>
+ * object will call the <tt>ServiceTrackerCustomizer</tt> methods on itself.
+ */
+ public ServiceTracker(BundleContext context, ServiceReference reference,
+ ServiceTrackerCustomizer customizer)
+ {
+ this.context = context;
+ this.customizer = (customizer == null) ? this : customizer;
+
+ try
+ {
+ this.filter = context.createFilter("("+Constants.SERVICE_ID+"="+reference.getProperty(Constants.SERVICE_ID).toString()+")");
+ }
+ catch (InvalidSyntaxException e)
+ {
+ throw new RuntimeException("unexpected InvalidSyntaxException: "+e.getMessage());
+ }
+ }
+
+ /**
+ * Create a <tt>ServiceTracker</tt> object on the specified class name.
+ *
+ * <p>Services registered under the specified class name will be tracked
+ * by this <tt>ServiceTracker</tt> object.
+ *
+ * @param context <tt>BundleContext</tt> object against which the tracking is done.
+ * @param clazz Class name of the services to be tracked.
+ * @param customizer The customizer object to call when services are
+ * added, modified, or removed in this <tt>ServiceTracker</tt> object.
+ * If customizer is <tt>null</tt>, then this <tt>ServiceTracker</tt> object will be used
+ * as the <tt>ServiceTrackerCustomizer</tt> object and the <tt>ServiceTracker</tt> object
+ * will call the <tt>ServiceTrackerCustomizer</tt> methods on itself.
+ */
+ public ServiceTracker(BundleContext context, String clazz,
+ ServiceTrackerCustomizer customizer)
+ {
+ this.context = context;
+ this.customizer = (customizer == null) ? this : customizer;
+
+ try
+ {
+ this.filter = context.createFilter("("+Constants.OBJECTCLASS+"="+clazz+")");
+ }
+ catch (InvalidSyntaxException e)
+ {
+ throw new RuntimeException("unexpected InvalidSyntaxException: "+e.getMessage());
+ }
+
+ if (clazz == null)
+ {
+ throw new NullPointerException();
+ }
+ }
+
+ /**
+ * Create a <tt>ServiceTracker</tt> object on the specified <tt>Filter</tt> object.
+ *
+ * <p>Services which match the specified <tt>Filter</tt> object will be tracked
+ * by this <tt>ServiceTracker</tt> object.
+ *
+ * @param context <tt>BundleContext</tt> object against which the tracking is done.
+ * @param filter <tt>Filter</tt> object to select the services to be tracked.
+ * @param customizer The customizer object to call when services are
+ * added, modified, or removed in this <tt>ServiceTracker</tt> object.
+ * If customizer is null, then this <tt>ServiceTracker</tt> object will be used
+ * as the <tt>ServiceTrackerCustomizer</tt> object and the <tt>ServiceTracker</tt>
+ * object will call the <tt>ServiceTrackerCustomizer</tt> methods on itself.
+ * @since 1.1
+ */
+ public ServiceTracker(BundleContext context, Filter filter,
+ ServiceTrackerCustomizer customizer)
+ {
+ this.context = context;
+ this.filter = filter;
+ this.customizer = (customizer == null) ? this : customizer;
+
+ if ((context == null) || (filter == null))
+ {
+ throw new NullPointerException();
+ }
+ }
+
+ /**
+ * Open this <tt>ServiceTracker</tt> object and begin tracking services.
+ *
+ * <p>Services which match the search criteria specified when
+ * this <tt>ServiceTracker</tt> object was created are now tracked
+ * by this <tt>ServiceTracker</tt> object.
+ *
+ * @throws java.lang.IllegalStateException if the <tt>BundleContext</tt>
+ * object with which this <tt>ServiceTracker</tt> object was created is no longer valid.
+ */
+ public synchronized void open()
+ {
+ if (tracked == null)
+ {
+ tracked = new Tracked(customizer, filter);
+
+ ServiceReference[] references;
+
+ synchronized (tracked)
+ {
+ context.addServiceListener(tracked);
+
+ try
+ {
+ references = context.getServiceReferences(null, filter.toString());
+ }
+ catch (InvalidSyntaxException e)
+ {
+ throw new RuntimeException("unexpected InvalidSyntaxException");
+ }
+ }
+
+ /* Call tracked outside of synchronized region */
+ if (references != null)
+ {
+ int size = references.length;
+
+ for (int i=0; i < size; i++)
+ {
+ ServiceReference reference = references[i];
+
+ /* if the service is still registered */
+ if (reference.getBundle() != null)
+ {
+ tracked.track(reference);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Close this <tt>ServiceTracker</tt> object.
+ *
+ * <p>This method should be called when this <tt>ServiceTracker</tt> object
+ * should end the tracking of services.
+ */
+
+ public synchronized void close()
+ {
+ if (tracked != null)
+ {
+ tracked.close();
+
+ ServiceReference references[] = getServiceReferences();
+
+ Tracked outgoing = tracked;
+ tracked = null;
+
+ try
+ {
+ context.removeServiceListener(outgoing);
+ }
+ catch (IllegalStateException e)
+ {
+ /* In case the context was stopped. */
+ }
+
+ if (references != null)
+ {
+ for (int i = 0; i < references.length; i++)
+ {
+ outgoing.untrack(references[i]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Properly close this <tt>ServiceTracker</tt> object when finalized.
+ * This method calls the <tt>close</tt> method to close this <tt>ServiceTracker</tt> object
+ * if it has not already been closed.
+ *
+ */
+ protected void finalize() throws Throwable
+ {
+ close();
+ }
+
+ /**
+ * Default implementation of the <tt>ServiceTrackerCustomizer.addingService</tt> method.
+ *
+ * <p>This method is only called when this <tt>ServiceTracker</tt> object
+ * has been constructed with a <tt>null ServiceTrackerCustomizer</tt> argument.
+ *
+ * The default implementation returns the result of
+ * calling <tt>getService</tt>, on the
+ * <tt>BundleContext</tt> object with which this <tt>ServiceTracker</tt> object was created,
+ * passing the specified <tt>ServiceReference</tt> object.
+ * <p>This method can be overridden in a subclass to customize
+ * the service object to be tracked for the service
+ * being added. In that case, take care not
+ * to rely on the default implementation of removedService that will unget the service.
+ *
+ * @param reference Reference to service being added to this
+ * <tt>ServiceTracker</tt> object.
+ * @return The service object to be tracked for the service
+ * added to this <tt>ServiceTracker</tt> object.
+ * @see ServiceTrackerCustomizer
+ */
+ public Object addingService(ServiceReference reference)
+ {
+ return context.getService(reference);
+ }
+ public void addedService(ServiceReference ref, Object service) {
+ // do nothing
+ }
+
+ /**
+ * Default implementation of the <tt>ServiceTrackerCustomizer.modifiedService</tt> method.
+ *
+ * <p>This method is only called when this <tt>ServiceTracker</tt> object
+ * has been constructed with a <tt>null ServiceTrackerCustomizer</tt> argument.
+ *
+ * The default implementation does nothing.
+ *
+ * @param reference Reference to modified service.
+ * @param service The service object for the modified service.
+ * @see ServiceTrackerCustomizer
+ */
+ public void modifiedService(ServiceReference reference, Object service)
+ {
+ }
+
+ /**
+ * Default implementation of the <tt>ServiceTrackerCustomizer.removedService</tt> method.
+ *
+ * <p>This method is only called when this <tt>ServiceTracker</tt> object
+ * has been constructed with a <tt>null ServiceTrackerCustomizer</tt> argument.
+ *
+ * The default implementation
+ * calls <tt>ungetService</tt>, on the
+ * <tt>BundleContext</tt> object with which this <tt>ServiceTracker</tt> object was created,
+ * passing the specified <tt>ServiceReference</tt> object.
+ * <p>This method can be overridden in a subclass. If the default
+ * implementation of <tt>addingService</tt> method was used, this method must unget the service.
+ *
+ * @param reference Reference to removed service.
+ * @param object The service object for the removed service.
+ * @see ServiceTrackerCustomizer
+ */
+ public void removedService(ServiceReference reference, Object object)
+ {
+ context.ungetService(reference);
+ }
+
+ /**
+ * Wait for at least one service to be tracked by this <tt>ServiceTracker</tt> object.
+ * <p>It is strongly recommended that <tt>waitForService</tt> is not used
+ * during the calling of the <tt>BundleActivator</tt> methods. <tt>BundleActivator</tt> methods are
+ * expected to complete in a short period of time.
+ *
+ * @param timeout time interval in milliseconds to wait. If zero,
+ * the method will wait indefinately.
+ * @return Returns the result of <tt>getService()</tt>.
+ * @throws IllegalArgumentException If the value of timeout is
+ * negative.
+ */
+ public Object waitForService(long timeout) throws InterruptedException
+ {
+ if (timeout < 0)
+ {
+ throw new IllegalArgumentException("timeout value is negative");
+ }
+
+ Object object = getService();
+
+ while (object == null)
+ {
+ Tracked tracked = this.tracked; /* use local var since we are not synchronized */
+
+ if (tracked == null) /* if ServiceTracker is not open */
+ {
+ return null;
+ }
+
+ synchronized (tracked)
+ {
+ if (tracked.size() == 0)
+ {
+ tracked.wait(timeout);
+ }
+ }
+
+ object = getService();
+
+ if (timeout > 0)
+ {
+ return object;
+ }
+ }
+
+ return object;
+ }
+
+ /**
+ * Return an array of <tt>ServiceReference</tt> objects for all services
+ * being tracked by this <tt>ServiceTracker</tt> object.
+ *
+ * @return Array of <tt>ServiceReference</tt> objects or <tt>null</tt> if no service
+ * are being tracked.
+ */
+ public ServiceReference[] getServiceReferences()
+ {
+ Tracked tracked = this.tracked; /* use local var since we are not synchronized */
+
+ if (tracked == null) /* if ServiceTracker is not open */
+ {
+ return null;
+ }
+
+ synchronized (tracked)
+ {
+ int size = tracked.size();
+
+ if (size == 0)
+ {
+ return null;
+ }
+
+ ServiceReference references[] = new ServiceReference[size];
+
+ Enumeration trackedServiceRefs = tracked.keys();
+
+ for (int i = 0; i < size; i++)
+ {
+ references[i] = (ServiceReference)trackedServiceRefs.nextElement();
+ }
+
+ return references;
+ }
+ }
+
+ /**
+ * Return an array of service objects for all services
+ * being tracked by this <tt>ServiceTracker</tt> object.
+ *
+ * @return Array of service objects or <tt>null</tt> if no service
+ * are being tracked.
+ */
+ public Object[] getServices()
+ {
+ Tracked tracked = this.tracked; /* use local var since we are not synchronized */
+
+ if (tracked == null) /* if ServiceTracker is not open */
+ {
+ return null;
+ }
+
+ synchronized (tracked)
+ {
+ int size = tracked.size();
+
+ if (size == 0)
+ {
+ return null;
+ }
+
+ Object objects[] = new Object[size];
+
+ Enumeration trackedServices = tracked.elements();
+
+ for (int i = 0; i < size; i++)
+ {
+ objects[i] = trackedServices.nextElement();
+ }
+
+ return objects;
+ }
+ }
+
+ /**
+ * Returns a <tt>ServiceReference</tt> object for one of the services
+ * being tracked by this <tt>ServiceTracker</tt> object.
+ *
+ * <p>If multiple services are being tracked, the service
+ * with the highest ranking (as specified in its <tt>service.ranking</tt> property) is
+ * returned.
+ *
+ * <p>If there is a tie in ranking, the service with the lowest
+ * service ID (as specified in its <tt>service.id</tt> property); that is,
+ * the service that was registered first is returned.
+ * <p>This is the same algorithm used by <tt>BundleContext.getServiceReference</tt>.
+ *
+ * @return <tt>ServiceReference</tt> object or <tt>null</tt> if no service is being tracked.
+ * @since 1.1
+ */
+ public ServiceReference getServiceReference()
+ {
+ ServiceReference[] references = getServiceReferences();
+
+ int length = (references == null) ? 0 : references.length;
+
+ if (length > 0) /* if a service is being tracked */
+ {
+ int index = 0;
+
+ if (length > 1) /* if more than one service, select highest ranking */
+ {
+ int rankings[] = new int[length];
+ int count = 0;
+ int maxRanking = Integer.MIN_VALUE;
+
+ for (int i = 0 ; i < length; i++)
+ {
+ Object property = references[i].getProperty(Constants.SERVICE_RANKING);
+
+ int ranking = (property instanceof Integer)
+ ? ((Integer)property).intValue() : 0;
+
+ rankings[i] = ranking;
+
+ if (ranking > maxRanking)
+ {
+ index = i;
+ maxRanking = ranking;
+ count = 1;
+ }
+ else
+ {
+ if (ranking == maxRanking)
+ {
+ count++;
+ }
+ }
+ }
+
+ if (count > 1) /* if still more than one service, select lowest id */
+ {
+ long minId = Long.MAX_VALUE;
+
+ for (int i = 0 ; i < length; i++)
+ {
+ if (rankings[i] == maxRanking)
+ {
+ long id = ((Long)(references[i].getProperty(Constants.SERVICE_ID))).longValue();
+
+ if (id < minId)
+ {
+ index = i;
+ minId = id;
+ }
+ }
+ }
+ }
+ }
+
+ return references[index];
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the service object for the specified <tt>ServiceReference</tt> object
+ * if the referenced service is
+ * being tracked by this <tt>ServiceTracker</tt> object.
+ *
+ * @param reference Reference to the desired service.
+ * @return Service object or <tt>null</tt> if the service referenced by the
+ * specified <tt>ServiceReference</tt> object is not being tracked.
+ */
+ public Object getService(ServiceReference reference)
+ {
+ Tracked tracked = this.tracked; /* use local var since we are not synchronized */
+
+ if (tracked == null) /* if ServiceTracker is not open */
+ {
+ return null;
+ }
+
+ return tracked.get(reference);
+ }
+
+ /**
+ * Returns a service object for one of the services
+ * being tracked by this <tt>ServiceTracker</tt> object.
+ *
+ * <p>If any services are being tracked, this method returns the result
+ * of calling <tt>getService(getServiceReference())</tt>.
+ *
+ * @return Service object or <tt>null</tt> if no service is being tracked.
+ */
+ public Object getService()
+ {
+ ServiceReference reference = getServiceReference();
+
+ if (reference != null)
+ {
+ return getService(reference);
+ }
+
+ return null;
+ }
+
+ /**
+ * Remove a service from this <tt>ServiceTracker</tt> object.
+ *
+ * The specified service will be removed from this
+ * <tt>ServiceTracker</tt> object.
+ * If the specified service was being tracked then the
+ * <tt>ServiceTrackerCustomizer.removedService</tt> method will be
+ * called for that service.
+ *
+ * @param reference Reference to the service to be removed.
+ */
+ public void remove(ServiceReference reference)
+ {
+ Tracked tracked = this.tracked; /* use local var since we are not synchronized */
+
+ if (tracked == null) /* if ServiceTracker is not open */
+ {
+ return;
+ }
+
+ tracked.untrack(reference);
+ }
+
+ /**
+ * Return the number of services being tracked by this <tt>ServiceTracker</tt> object.
+ *
+ * @return Number of services being tracked.
+ */
+
+ public int size()
+ {
+ Tracked tracked = this.tracked; /* use local var since we are not synchronized */
+
+ if (tracked == null) /* if ServiceTracker is not open */
+ {
+ return 0;
+ }
+
+ return tracked.size();
+ }
+
+ /**
+ * Returns the tracking count for this <tt>ServiceTracker</tt> object.
+ *
+ * The tracking count is initialized to 0 when this
+ * <tt>ServiceTracker</tt> object is opened. Every time a service is
+ * added or removed from this <tt>ServiceTracker</tt> object
+ * the tracking count is incremented.
+ *
+ * <p>The tracking count can
+ * be used to determine if this <tt>ServiceTracker</tt> object
+ * has added or removed a service by comparing a tracking count value
+ * previously collected with the current tracking count value. If the value
+ * has not changed, then no service has been added or removed from
+ * this <tt>ServiceTracker</tt> object
+ * since the previous tracking count was collected.
+ *
+ * @since 1.2
+ * @return The tracking count for this <tt>ServiceTracker</tt> object
+ * or -1 if this <tt>ServiceTracker</tt> object is not open.
+ */
+ public int getTrackingCount()
+ {
+ Tracked tracked = this.tracked; /* use local var since we are not synchronized */
+
+ if (tracked == null) /* if ServiceTracker is not open */
+ {
+ return -1;
+ }
+
+ return tracked.getTrackingCount();
+ }
+
+ /**
+ * Inner class to track the services.
+ * This class is a hashtable mapping <tt>ServiceReference</tt> object -> customized Object.
+ * This class also implements the <tt>ServiceListener</tt> interface for the tracker.
+ * This is not a public class. It is only for use by the implementation
+ * of the <tt>ServiceTracker</tt> class.
+ *
+ */
+ static class Tracked extends Hashtable implements ServiceListener {
+ private ServiceTrackerCustomizer customizer; /** ServiceTrackerCustomizer object for this tracker. */
+ private Filter filter; /** The filter used to track */
+ private Vector adding; /** list of ServiceReferences currently being added */
+ private boolean closed; /** true if the tracked object is closed */
+ private int trackingCount; /** modification count */
+
+
+ /**
+ * Tracked constructor.
+ *
+ * @param customizer Customizer object from parent <tt>ServiceTracker</tt> object.
+ * @param filter <tt>Filter</tt> object from parent <tt>ServiceTracker</tt> object.
+ */
+ protected Tracked(ServiceTrackerCustomizer customizer, Filter filter)
+ {
+ super();
+ this.customizer = customizer;
+ this.filter = filter;
+ closed = false;
+ trackingCount = 0;
+ adding = new Vector(10, 10);
+ }
+
+ /**
+ * Called by the parent <tt>ServiceTracker</tt> object when it is closed.
+ */
+ protected void close()
+ {
+ closed = true;
+ }
+
+ /**
+ * Called by the parent <tt>ServiceTracker</tt> object to get
+ * the modification count.
+ *
+ * @since 1.2
+ * @return modification count.
+ */
+ protected int getTrackingCount()
+ {
+ return trackingCount;
+ }
+
+ /**
+ * <tt>ServiceListener</tt> method for the <tt>ServiceTracker</tt> class.
+ * This method must NOT be synchronized to avoid deadlock potential.
+ *
+ * @param event <tt>ServiceEvent</tt> object from the framework.
+ */
+ public void serviceChanged(ServiceEvent event)
+ {
+ /* Check if we had a delayed call (which could happen when we close). */
+ if (closed)
+ {
+ return;
+ }
+
+ ServiceReference reference = event.getServiceReference();
+
+ switch (event.getType())
+ {
+ case ServiceEvent.REGISTERED:
+ case ServiceEvent.MODIFIED:
+ if (filter.match(reference))
+ {
+ track(reference);
+ /* If the customizer throws an unchecked exception, it is safe to let it propagate */
+ }
+ else
+ {
+ untrack(reference);
+ /* If the customizer throws an unchecked exception, it is safe to let it propagate */
+ }
+
+ break;
+
+ case ServiceEvent.UNREGISTERING:
+ untrack(reference);
+ /* If the customizer throws an unchecked exception, it is safe to let it propagate */
+
+ break;
+ }
+ }
+
+ /**
+ * Begin to track the referenced service.
+ *
+ * @param reference Reference to a service to be tracked.
+ */
+ protected void track(ServiceReference reference)
+ {
+ Object object = get(reference);
+
+ if (object != null) /* we are already tracking the service */
+ {
+ /* Call customizer outside of synchronized region */
+ customizer.modifiedService(reference, object);
+ /* If the customizer throws an unchecked exception, it is safe to let it propagate */
+
+ return;
+ }
+
+ synchronized (this)
+ {
+ if (adding.indexOf(reference, 0) != -1) /* if this service is already
+ * in the process of being added. */
+ {
+ return;
+ }
+
+ adding.addElement(reference); /* mark this service is being added */
+ }
+
+ boolean becameUntracked = false;
+
+ /* Call customizer outside of synchronized region */
+ try
+ {
+ object = customizer.addingService(reference);
+ /* If the customizer throws an unchecked exception, it will propagate after the finally */
+ }
+ finally
+ {
+ boolean needToCallback = false;
+ synchronized (this)
+ {
+ if (adding.removeElement(reference)) /* if the service was not untracked
+ * during the customizer callback */
+ {
+ if (object != null)
+ {
+ put(reference, object);
+
+ trackingCount++; /* increment modification count */
+
+ notifyAll();
+
+ // Marrs: extra callback added, will be invoked after the synchronized block
+ needToCallback = true;
+ }
+ }
+ else
+ {
+ becameUntracked = true;
+ }
+ }
+ if (needToCallback) {
+ customizer.addedService(reference, object);
+ }
+ }
+
+ /* The service became untracked during
+ * the customizer callback.
+ */
+ if (becameUntracked)
+ {
+ /* Call customizer outside of synchronized region */
+ customizer.removedService(reference, object);
+ /* If the customizer throws an unchecked exception, it is safe to let it propagate */
+ }
+ }
+
+ /**
+ * Discontinue tracking the referenced service.
+ *
+ * @param reference Reference to the tracked service.
+ */
+ protected void untrack(ServiceReference reference)
+ {
+ Object object;
+
+ synchronized (this)
+ {
+ if (adding.removeElement(reference)) /* if the service is in the process
+ * of being added */
+ {
+ return; /* in case the service is untracked
+ * while in the process of adding */
+ }
+
+ object = this.remove(reference); /* must remove from tracker before calling
+ * customizer callback */
+
+ if (object == null) /* are we actually tracking the service */
+ {
+ return;
+ }
+
+ trackingCount++; /* increment modification count */
+ }
+
+ /* Call customizer outside of synchronized region */
+ customizer.removedService(reference, object);
+ /* If the customizer throws an unchecked exception, it is safe to let it propagate */
+ }
+ }
+}
diff --git a/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceTrackerCustomizer.java b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceTrackerCustomizer.java
new file mode 100644
index 0000000..0a8c778
--- /dev/null
+++ b/org.apache.felix.dependencymanager/src/main/java/org/apache/felix/dependencymanager/ServiceTrackerCustomizer.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * 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.felix.dependencymanager;
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * TODO modified version of a normal service tracker customizer, this one has an
+ * extra callback "addedservice" that is invoked after the service has been added
+ * to the tracker (and therefore is accessible through the tracker API)
+ *
+ * @author Marcel Offermans
+ */
+public interface ServiceTrackerCustomizer {
+ public Object addingService(ServiceReference ref);
+ public void addedService(ServiceReference ref, Object service);
+ public void modifiedService(ServiceReference ref, Object service);
+ public void removedService(ServiceReference ref, Object service);
+}