You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by gn...@apache.org on 2009/09/23 20:07:38 UTC

svn commit: r818182 - in /camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi: Activator.java tracker/ tracker/AbstractTracked.java tracker/BundleTracker.java tracker/BundleTrackerCustomizer.java

Author: gnodet
Date: Wed Sep 23 18:07:38 2009
New Revision: 818182

URL: http://svn.apache.org/viewvc?rev=818182&view=rev
Log:
The current activator use excessive synchronization which leads to deadlocks in osgi

Added:
    camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/
    camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/AbstractTracked.java
    camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/BundleTracker.java
    camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/BundleTrackerCustomizer.java
Modified:
    camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/Activator.java

Modified: camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/Activator.java
URL: http://svn.apache.org/viewvc/camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/Activator.java?rev=818182&r1=818181&r2=818182&view=diff
==============================================================================
--- camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/Activator.java (original)
+++ camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/Activator.java Wed Sep 23 18:07:38 2009
@@ -23,15 +23,17 @@
 import java.net.URL;
 import java.util.Collection;
 import java.util.Enumeration;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 import java.util.StringTokenizer;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.camel.Component;
 import org.apache.camel.TypeConverter;
+import org.apache.camel.osgi.tracker.BundleTracker;
+import org.apache.camel.osgi.tracker.BundleTrackerCustomizer;
 import org.apache.camel.spi.Language;
 import org.apache.camel.spi.LanguageResolver;
 import org.apache.camel.util.ObjectHelper;
@@ -44,19 +46,21 @@
 import org.osgi.framework.SynchronousBundleListener;
 import org.springframework.osgi.util.BundleDelegatingClassLoader;
 
-public class Activator implements BundleActivator, SynchronousBundleListener {
+public class Activator implements BundleActivator, BundleTrackerCustomizer {
     public static final String META_INF_TYPE_CONVERTER = "META-INF/services/org/apache/camel/TypeConverter";
     public static final String META_INF_COMPONENT = "META-INF/services/org/apache/camel/component/";
     public static final String META_INF_LANGUAGE = "META-INF/services/org/apache/camel/language/";
     public static final String META_INF_LANGUAGE_RESOLVER = "META-INF/services/org/apache/camel/language/resolver/";
     
     private static final transient Log LOG = LogFactory.getLog(Activator.class);    
-    private static final Map<String, ComponentEntry> COMPONENTS = new HashMap<String, ComponentEntry>();
-    private static final Map<URL, TypeConverterEntry> TYPE_CONVERTERS = new HashMap<URL, TypeConverterEntry>();
-    private static final Map<String, ComponentEntry> LANGUAGES = new HashMap<String, ComponentEntry>();
-    private static final Map<String, ComponentEntry> LANGUAGE_RESOLVERS = new HashMap<String, ComponentEntry>();
+    private static final Map<String, ComponentEntry> COMPONENTS = new ConcurrentHashMap<String, ComponentEntry>();
+    private static final Map<URL, TypeConverterEntry> TYPE_CONVERTERS = new ConcurrentHashMap<URL, TypeConverterEntry>();
+    private static final Map<String, ComponentEntry> LANGUAGES = new ConcurrentHashMap<String, ComponentEntry>();
+    private static final Map<String, ComponentEntry> LANGUAGE_RESOLVERS = new ConcurrentHashMap<String, ComponentEntry>();
     private static Bundle bundle;
     
+    private BundleTracker tracker;
+
     private class ComponentEntry {
         Bundle bundle;
         String path;
@@ -70,32 +74,28 @@
         Set<String> converterPackages;
     }
     
-    public void bundleChanged(BundleEvent event) {
-        if (LOG.isTraceEnabled()) {
-            LOG.trace("Bundle changed: " + event);
+	public Object addingBundle(Bundle bundle, BundleEvent event) {
+		modifiedBundle(bundle, event, null);
+		return bundle;
+	}
+
+	public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Bundle started: " + bundle.getSymbolicName());
         }
+        mayBeAddComponentAndLanguageFor(bundle);                
+        mayBeAddTypeConverterFor(bundle);
+	}
 
-        try {
-            Bundle bundle = event.getBundle();
-            if (event.getType() == BundleEvent.RESOLVED) {
-                if (LOG.isDebugEnabled()) {
-                    LOG.debug("Bundle resolved: " + bundle.getSymbolicName());
-                }
-                mayBeAddComponentAndLanguageFor(bundle);                
-                mayBeAddTypeConverterFor(bundle);
-            } else if (event.getType() == BundleEvent.UNRESOLVED) {
-                if (LOG.isDebugEnabled()) {
-                    LOG.debug("Bundle unresolved: " + bundle.getSymbolicName());
-                }
-                mayBeRemoveComponentAndLanguageFor(bundle);                
-                mayBeRemoveTypeConverterFor(bundle);
-            }
-        } catch (Throwable e) {
-            LOG.fatal("Exception occured during bundleChanged for event: " + event, e);
+	public void removedBundle(Bundle bundle, BundleEvent event, Object object) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Bundle stopped: " + bundle.getSymbolicName());
         }
-    }
+        mayBeRemoveComponentAndLanguageFor(bundle);                
+        mayBeRemoveTypeConverterFor(bundle);
+	}
 
-    protected synchronized void addComponentEntry(String entryPath, Bundle bundle, Map<String, ComponentEntry> entries, Class clazz) {
+    protected void addComponentEntry(String entryPath, Bundle bundle, Map<String, ComponentEntry> entries, Class clazz) {
         // Check bundle compatibility
         try {
             if (bundle.loadClass(clazz.getName()) != clazz) {
@@ -127,7 +127,7 @@
         addComponentEntry(META_INF_LANGUAGE_RESOLVER, bundle, LANGUAGE_RESOLVERS, LanguageResolver.class);
     }
     
-    protected synchronized void mayBeAddTypeConverterFor(Bundle bundle) {
+    protected void mayBeAddTypeConverterFor(Bundle bundle) {
         // Check bundle compatibility
         try {
             Class clazz = TypeConverter.class;
@@ -175,7 +175,7 @@
         }        
     }
     
-    protected synchronized void mayBeRemoveTypeConverterFor(Bundle bundle) {
+    protected void mayBeRemoveTypeConverterFor(Bundle bundle) {
         TypeConverterEntry[] entriesArray = TYPE_CONVERTERS.values().toArray(new TypeConverterEntry[0]);
         for (TypeConverterEntry entry : entriesArray) {
             if (entry.bundle == bundle) {
@@ -194,19 +194,9 @@
         if (LOG.isDebugEnabled()) {
             LOG.debug("Using bundle: " + bundle);
         }
-        context.addBundleListener(this);
-
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Checking existing bundles for Camel components, languages and type converters");
-        }
-        for (Bundle bundle : context.getBundles()) {
-            if (bundle.getState() == Bundle.RESOLVED || bundle.getState() == Bundle.STARTING
-                || bundle.getState() == Bundle.ACTIVE || bundle.getState() == Bundle.STOPPING) {
-                mayBeAddComponentAndLanguageFor(bundle);
-                mayBeAddTypeConverterFor(bundle);
-            }
-        }
 
+        tracker = new BundleTracker(context, Bundle.ACTIVE, this);
+        tracker.open();
         LOG.info("Camel activator started");
     }
 
@@ -216,14 +206,7 @@
         if (LOG.isDebugEnabled()) {
             LOG.debug("Removing Camel bundles");
         }
-        for (Bundle bundle : context.getBundles()) {
-            if (bundle.getState() == Bundle.RESOLVED || bundle.getState() == Bundle.STARTING 
-                || bundle.getState() == Bundle.ACTIVE || bundle.getState() == Bundle.STOPPING) {
-                mayBeRemoveComponentAndLanguageFor(bundle);
-                mayBeRemoveTypeConverterFor(bundle);
-            }
-        }
-
+        tracker.close();
         LOG.info("Camel activator stopped");
     }
     
@@ -269,27 +252,27 @@
         return bundle;
     }
     
-    protected static synchronized TypeConverterEntry[] getTypeConverterEntries() {
+    protected static TypeConverterEntry[] getTypeConverterEntries() {
         Collection<TypeConverterEntry> entries = TYPE_CONVERTERS.values();
         return entries.toArray(new TypeConverterEntry[entries.size()]);
     }
         
-    public static synchronized Class getComponent(String name) throws Exception {
+    public static Class getComponent(String name) throws Exception {
         LOG.trace("Finding Component: " + name);
         return getClassFromEntries(name, COMPONENTS);
     }
     
-    public static synchronized Class getLanguage(String name) throws Exception {
+    public static Class getLanguage(String name) throws Exception {
         LOG.trace("Finding Language: " + name);
         return getClassFromEntries(name, LANGUAGES);
     }
     
-    public static synchronized Class getLanguageResolver(String name) throws Exception {
+    public static Class getLanguageResolver(String name) throws Exception {
         LOG.trace("Finding LanguageResolver: " + name);
         return getClassFromEntries(name, LANGUAGE_RESOLVERS);
     }
     
-    protected static synchronized Class getClassFromEntries(String name, Map<String, ComponentEntry> entries) throws Exception {
+    protected static Class getClassFromEntries(String name, Map<String, ComponentEntry> entries) throws Exception {
         ComponentEntry entry = entries.get(name);
         if (entry == null) {
             return null;

Added: camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/AbstractTracked.java
URL: http://svn.apache.org/viewvc/camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/AbstractTracked.java?rev=818182&view=auto
==============================================================================
--- camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/AbstractTracked.java (added)
+++ camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/AbstractTracked.java Wed Sep 23 18:07:38 2009
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) OSGi Alliance (2007, 2008). 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.camel.osgi.tracker;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Abstract class to track items. If a Tracker is reused (closed then reopened),
+ * then a new AbstractTracked object is used. This class acts a map of tracked
+ * item -> customized object. Subclasses of this class will act as the listener
+ * object for the tracker. This class is used to synchronize access to the
+ * tracked items. This is not a public class. It is only for use by the
+ * implementation of the Tracker class.
+ * 
+ * @ThreadSafe
+ * @version $Revision$
+ * @since 1.4
+ */
+abstract class AbstractTracked {
+	/* set this to true to compile in debug messages */
+	static final boolean		DEBUG	= false;
+
+	/**
+	 * Map of tracked items to customized objects.
+	 * 
+	 * @GuardedBy this
+	 */
+	private final Map			tracked;
+
+	/**
+	 * Modification count. This field is initialized to zero and incremented by
+	 * modified.
+	 * 
+	 * @GuardedBy this
+	 */
+	private int					trackingCount;
+
+	/**
+	 * List of items in the process of being added. This is used to deal with
+	 * nesting of events. Since events may be synchronously delivered, events
+	 * can be nested. For example, when processing the adding of a service and
+	 * the customizer causes the service to be unregistered, notification to the
+	 * nested call to untrack that the service was unregistered can be made to
+	 * the track method.
+	 * 
+	 * Since the ArrayList implementation is not synchronized, all access to
+	 * this list must be protected by the same synchronized object for
+	 * thread-safety.
+	 * 
+	 * @GuardedBy this
+	 */
+	private final List			adding;
+
+	/**
+	 * true if the tracked object is closed.
+	 * 
+	 * This field is volatile because it is set by one thread and read by
+	 * another.
+	 */
+	volatile boolean			closed;
+
+	/**
+	 * Initial list of items for the tracker. This is used to correctly process
+	 * the initial items which could be modified before they are tracked. This
+	 * is necessary since the initial set of tracked items are not "announced"
+	 * by events and therefore the event which makes the item untracked could be
+	 * delivered before we track the item.
+	 * 
+	 * An item must not be in both the initial and adding lists at the same
+	 * time. An item must be moved from the initial list to the adding list
+	 * "atomically" before we begin tracking it.
+	 * 
+	 * Since the LinkedList implementation is not synchronized, all access to
+	 * this list must be protected by the same synchronized object for
+	 * thread-safety.
+	 * 
+	 * @GuardedBy this
+	 */
+	private final LinkedList	initial;
+
+	/**
+	 * AbstractTracked constructor.
+	 */
+	AbstractTracked() {
+		tracked = new HashMap();
+		trackingCount = 0;
+		adding = new ArrayList(6);
+		initial = new LinkedList();
+		closed = false;
+	}
+
+	/**
+	 * Set initial list of items into tracker before events begin to be
+	 * received.
+	 * 
+	 * This method must be called from Tracker's open method while synchronized
+	 * on this object in the same synchronized block as the add listener call.
+	 * 
+	 * @param list The initial list of items to be tracked. <code>null</code>
+	 *        entries in the list are ignored.
+	 * @GuardedBy this
+	 */
+	void setInitial(Object[] list) {
+		if (list == null) {
+			return;
+		}
+		int size = list.length;
+		for (int i = 0; i < size; i++) {
+			Object item = list[i];
+			if (item == null) {
+				continue;
+			}
+			if (DEBUG) {
+				System.out.println("AbstractTracked.setInitial: " + item); //$NON-NLS-1$
+			}
+			initial.add(item);
+		}
+	}
+
+	/**
+	 * Track the initial list of items. This is called after events can begin to
+	 * be received.
+	 * 
+	 * This method must be called from Tracker's open method while not
+	 * synchronized on this object after the add listener call.
+	 * 
+	 */
+	void trackInitial() {
+		while (true) {
+			Object item;
+			synchronized (this) {
+				if (closed || (initial.size() == 0)) {
+					/*
+					 * if there are no more initial items
+					 */
+					return; /* we are done */
+				}
+				/*
+				 * move the first item from the initial list to the adding list
+				 * within this synchronized block.
+				 */
+				item = initial.removeFirst();
+				if (tracked.get(item) != null) {
+					/* if we are already tracking this item */
+					if (DEBUG) {
+						System.out
+								.println("AbstractTracked.trackInitial[already tracked]: " + item); //$NON-NLS-1$
+					}
+					continue; /* skip this item */
+				}
+				if (adding.contains(item)) {
+					/*
+					 * if this item is already in the process of being added.
+					 */
+					if (DEBUG) {
+						System.out
+								.println("AbstractTracked.trackInitial[already adding]: " + item); //$NON-NLS-1$
+					}
+					continue; /* skip this item */
+				}
+				adding.add(item);
+			}
+			if (DEBUG) {
+				System.out.println("AbstractTracked.trackInitial: " + item); //$NON-NLS-1$
+			}
+			trackAdding(item, null); /*
+									 * Begin tracking it. We call trackAdding
+									 * since we have already put the item in the
+									 * adding list.
+									 */
+		}
+	}
+
+	/**
+	 * Called by the owning Tracker object when it is closed.
+	 */
+	void close() {
+		closed = true;
+	}
+
+	/**
+	 * Begin to track an item.
+	 * 
+	 * @param item Item to be tracked.
+	 * @param related Action related object.
+	 */
+	void track(final Object item, final Object related) {
+		final Object object;
+		synchronized (this) {
+			if (closed) {
+				return;
+			}
+			object = tracked.get(item);
+			if (object == null) { /* we are not tracking the item */
+				if (adding.contains(item)) {
+					/* if this item is already in the process of being added. */
+					if (DEBUG) {
+						System.out
+								.println("AbstractTracked.track[already adding]: " + item); //$NON-NLS-1$
+					}
+					return;
+				}
+				adding.add(item); /* mark this item is being added */
+			}
+			else { /* we are currently tracking this item */
+				if (DEBUG) {
+					System.out
+							.println("AbstractTracked.track[modified]: " + item); //$NON-NLS-1$
+				}
+				modified(); /* increment modification count */
+			}
+		}
+
+		if (object == null) { /* we are not tracking the item */
+			trackAdding(item, related);
+		}
+		else {
+			/* Call customizer outside of synchronized region */
+			customizerModified(item, related, object);
+			/*
+			 * If the customizer throws an unchecked exception, it is safe to
+			 * let it propagate
+			 */
+		}
+	}
+
+	/**
+	 * Common logic to add an item to the tracker used by track and
+	 * trackInitial. The specified item must have been placed in the adding list
+	 * before calling this method.
+	 * 
+	 * @param item Item to be tracked.
+	 * @param related Action related object.
+	 */
+	private void trackAdding(final Object item, final Object related) {
+		if (DEBUG) {
+			System.out.println("AbstractTracked.trackAdding: " + item); //$NON-NLS-1$
+		}
+		Object object = null;
+		boolean becameUntracked = false;
+		/* Call customizer outside of synchronized region */
+		try {
+			object = customizerAdding(item, related);
+			/*
+			 * If the customizer throws an unchecked exception, it will
+			 * propagate after the finally
+			 */
+		}
+		finally {
+			synchronized (this) {
+				if (adding.remove(item) && !closed) {
+					/*
+					 * if the item was not untracked during the customizer
+					 * callback
+					 */
+					if (object != null) {
+						tracked.put(item, object);
+						modified(); /* increment modification count */
+						notifyAll(); /* notify any waiters */
+					}
+				}
+				else {
+					becameUntracked = true;
+				}
+			}
+		}
+		/*
+		 * The item became untracked during the customizer callback.
+		 */
+		if (becameUntracked && (object != null)) {
+			if (DEBUG) {
+				System.out
+						.println("AbstractTracked.trackAdding[removed]: " + item); //$NON-NLS-1$
+			}
+			/* Call customizer outside of synchronized region */
+			customizerRemoved(item, related, object);
+			/*
+			 * If the customizer throws an unchecked exception, it is safe to
+			 * let it propagate
+			 */
+		}
+	}
+
+	/**
+	 * Discontinue tracking the item.
+	 * 
+	 * @param item Item to be untracked.
+	 * @param related Action related object.
+	 */
+	void untrack(final Object item, final Object related) {
+		final Object object;
+		synchronized (this) {
+			if (initial.remove(item)) { /*
+										 * if this item is already in the list
+										 * of initial references to process
+										 */
+				if (DEBUG) {
+					System.out
+							.println("AbstractTracked.untrack[removed from initial]: " + item); //$NON-NLS-1$
+				}
+				return; /*
+						 * we have removed it from the list and it will not be
+						 * processed
+						 */
+			}
+
+			if (adding.remove(item)) { /*
+										 * if the item is in the process of
+										 * being added
+										 */
+				if (DEBUG) {
+					System.out
+							.println("AbstractTracked.untrack[being added]: " + item); //$NON-NLS-1$
+				}
+				return; /*
+						 * in case the item is untracked while in the process of
+						 * adding
+						 */
+			}
+			object = tracked.remove(item); /*
+											 * must remove from tracker before
+											 * calling customizer callback
+											 */
+			if (object == null) { /* are we actually tracking the item */
+				return;
+			}
+			modified(); /* increment modification count */
+		}
+		if (DEBUG) {
+			System.out.println("AbstractTracked.untrack[removed]: " + item); //$NON-NLS-1$
+		}
+		/* Call customizer outside of synchronized region */
+		customizerRemoved(item, related, object);
+		/*
+		 * If the customizer throws an unchecked exception, it is safe to let it
+		 * propagate
+		 */
+	}
+
+	/**
+	 * Returns the number of tracked items.
+	 * 
+	 * @return The number of tracked items.
+	 * 
+	 * @GuardedBy this
+	 */
+	int size() {
+		return tracked.size();
+	}
+
+	/**
+	 * Return the customized object for the specified item
+	 * 
+	 * @param item The item to lookup in the map
+	 * @return The customized object for the specified item.
+	 * 
+	 * @GuardedBy this
+	 */
+	Object getCustomizedObject(final Object item) {
+		return tracked.get(item);
+	}
+
+	/**
+	 * Return the list of tracked items.
+	 * 
+	 * @param list An array to contain the tracked items.
+	 * @return The specified list if it is large enough to hold the tracked
+	 *         items or a new array large enough to hold the tracked items.
+	 * @GuardedBy this
+	 */
+	Object[] getTracked(final Object[] list) {
+		return tracked.keySet().toArray(list);
+	}
+
+	/**
+	 * Increment the modification count. If this method is overridden, the
+	 * overriding method MUST call this method to increment the tracking count.
+	 * 
+	 * @GuardedBy this
+	 */
+	void modified() {
+		trackingCount++;
+	}
+
+	/**
+	 * Returns the tracking count for this <code>ServiceTracker</code> object.
+	 * 
+	 * The tracking count is initialized to 0 when this object is opened. Every
+	 * time an item is added, modified or removed from this object the tracking
+	 * count is incremented.
+	 * 
+	 * @GuardedBy this
+	 * @return The tracking count for this object.
+	 */
+	int getTrackingCount() {
+		return trackingCount;
+	}
+
+	/**
+	 * Call the specific customizer adding method. This method must not be
+	 * called while synchronized on this object.
+	 * 
+	 * @param item Item to be tracked.
+	 * @param related Action related object.
+	 * @return Customized object for the tracked item or <code>null</code> if
+	 *         the item is not to be tracked.
+	 */
+	abstract Object customizerAdding(final Object item, final Object related);
+
+	/**
+	 * Call the specific customizer modified method. This method must not be
+	 * called while synchronized on this object.
+	 * 
+	 * @param item Tracked item.
+	 * @param related Action related object.
+	 * @param object Customized object for the tracked item.
+	 */
+	abstract void customizerModified(final Object item, final Object related,
+			final Object object);
+
+	/**
+	 * Call the specific customizer removed method. This method must not be
+	 * called while synchronized on this object.
+	 * 
+	 * @param item Tracked item.
+	 * @param related Action related object.
+	 * @param object Customized object for the tracked item.
+	 */
+	abstract void customizerRemoved(final Object item, final Object related,
+			final Object object);
+}

Added: camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/BundleTracker.java
URL: http://svn.apache.org/viewvc/camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/BundleTracker.java?rev=818182&view=auto
==============================================================================
--- camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/BundleTracker.java (added)
+++ camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/BundleTracker.java Wed Sep 23 18:07:38 2009
@@ -0,0 +1,471 @@
+/*
+ * Copyright (c) OSGi Alliance (2007, 2008). 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.camel.osgi.tracker;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.SynchronousBundleListener;
+
+/**
+ * The <code>BundleTracker</code> class simplifies tracking bundles much like
+ * the <code>ServiceTracker</code> simplifies tracking services.
+ * <p>
+ * A <code>BundleTracker</code> is constructed with state criteria and a
+ * <code>BundleTrackerCustomizer</code> object. A <code>BundleTracker</code> can
+ * use the <code>BundleTrackerCustomizer</code> to select which bundles are
+ * tracked and to create a customized object to be tracked with the bundle. The
+ * <code>BundleTracker</code> can then be opened to begin tracking all bundles
+ * whose state matches the specified state criteria.
+ * <p>
+ * The <code>getBundles</code> method can be called to get the
+ * <code>Bundle</code> objects of the bundles being tracked. The
+ * <code>getObject</code> method can be called to get the customized object for
+ * a tracked bundle.
+ * <p>
+ * The <code>BundleTracker</code> class is thread-safe. It does not call a
+ * <code>BundleTrackerCustomizer</code> while holding any locks.
+ * <code>BundleTrackerCustomizer</code> implementations must also be
+ * thread-safe.
+ * 
+ * @ThreadSafe
+ * @version $Revision$
+ * @since 1.4
+ */
+public class BundleTracker implements BundleTrackerCustomizer {
+	/* set this to true to compile in debug messages */
+	static final boolean			DEBUG	= false;
+
+	/**
+	 * The Bundle Context used by this <code>BundleTracker</code>.
+	 */
+	protected final BundleContext	context;
+
+	/**
+	 * The <code>BundleTrackerCustomizer</code> object for this tracker.
+	 */
+	final BundleTrackerCustomizer	customizer;
+
+	/**
+	 * Tracked bundles: <code>Bundle</code> object -> customized Object and
+	 * <code>BundleListener</code> object
+	 */
+	private volatile Tracked		tracked;
+
+	/**
+	 * Accessor method for the current Tracked object. This method is only
+	 * intended to be used by the unsynchronized methods which do not modify the
+	 * tracked field.
+	 * 
+	 * @return The current Tracked object.
+	 */
+	private Tracked tracked() {
+		return tracked;
+	}
+
+	/**
+	 * State mask for bundles being tracked. This field contains the ORed values
+	 * of the bundle states being tracked.
+	 */
+	final int						mask;
+
+	/**
+	 * Create a <code>BundleTracker</code> for bundles whose state is present in
+	 * the specified state mask.
+	 * 
+	 * <p>
+	 * Bundles whose state is present on the specified state mask will be
+	 * tracked by this <code>BundleTracker</code>.
+	 * 
+	 * @param context The <code>BundleContext</code> against which the tracking
+	 *        is done.
+	 * @param stateMask The bit mask of the <code>OR</code>ing of the bundle
+	 *        states to be tracked.
+	 * @param customizer The customizer object to call when bundles are added,
+	 *        modified, or removed in this <code>BundleTracker</code>. If
+	 *        customizer is <code>null</code>, then this
+	 *        <code>BundleTracker</code> will be used as the
+	 *        <code>BundleTrackerCustomizer</code> and this
+	 *        <code>BundleTracker</code> will call the
+	 *        <code>BundleTrackerCustomizer</code> methods on itself.
+	 * @see Bundle#getState()
+	 */
+	public BundleTracker(BundleContext context, int stateMask,
+			BundleTrackerCustomizer customizer) {
+		this.context = context;
+		this.mask = stateMask;
+		this.customizer = (customizer == null) ? this : customizer;
+	}
+
+	/**
+	 * Open this <code>BundleTracker</code> and begin tracking bundles.
+	 * 
+	 * <p>
+	 * Bundle which match the state criteria specified when this
+	 * <code>BundleTracker</code> was created are now tracked by this
+	 * <code>BundleTracker</code>.
+	 * 
+	 * @throws java.lang.IllegalStateException If the <code>BundleContext</code>
+	 *         with which this <code>BundleTracker</code> was created is no
+	 *         longer valid.
+	 * @throws java.lang.SecurityException If the caller and this class do not
+	 *         have the appropriate
+	 *         <code>AdminPermission[context bundle,LISTENER]</code>, and the
+	 *         Java Runtime Environment supports permissions.
+	 */
+	public void open() {
+		final Tracked t;
+		synchronized (this) {
+			if (tracked != null) {
+				return;
+			}
+			if (DEBUG) {
+				System.out.println("BundleTracker.open"); //$NON-NLS-1$
+			}
+			t = new Tracked();
+			synchronized (t) {
+				context.addBundleListener(t);
+				Bundle[] bundles = context.getBundles();
+				if (bundles != null) {
+					int length = bundles.length;
+					for (int i = 0; i < length; i++) {
+						int state = bundles[i].getState();
+						if ((state & mask) == 0) {
+							/* null out bundles whose states are not interesting */
+							bundles[i] = null;
+						}
+					}
+					/* set tracked with the initial bundles */
+					t.setInitial(bundles); 
+				}
+			}
+			tracked = t;
+		}
+		/* Call tracked outside of synchronized region */
+		t.trackInitial(); /* process the initial references */
+	}
+
+	/**
+	 * Close this <code>BundleTracker</code>.
+	 * 
+	 * <p>
+	 * This method should be called when this <code>BundleTracker</code> should
+	 * end the tracking of bundles.
+	 * 
+	 * <p>
+	 * This implementation calls {@link #getBundles()} to get the list of
+	 * tracked bundles to remove.
+	 */
+	public void close() {
+		final Bundle[] bundles;
+		final Tracked outgoing;
+		synchronized (this) {
+			outgoing = tracked;
+			if (outgoing == null) {
+				return;
+			}
+			if (DEBUG) {
+				System.out.println("BundleTracker.close"); //$NON-NLS-1$
+			}
+			outgoing.close();
+			bundles = getBundles();
+			tracked = null;
+			try {
+				context.removeBundleListener(outgoing);
+			}
+			catch (IllegalStateException e) {
+				/* In case the context was stopped. */
+			}
+		}
+		if (bundles != null) {
+			for (int i = 0; i < bundles.length; i++) {
+				outgoing.untrack(bundles[i], null);
+			}
+		}
+	}
+
+	/**
+	 * Default implementation of the
+	 * <code>BundleTrackerCustomizer.addingBundle</code> method.
+	 * 
+	 * <p>
+	 * This method is only called when this <code>BundleTracker</code> has been
+	 * constructed with a <code>null BundleTrackerCustomizer</code> argument.
+	 * 
+	 * <p>
+	 * This implementation simply returns the specified <code>Bundle</code>.
+	 * 
+	 * <p>
+	 * This method can be overridden in a subclass to customize the object to be
+	 * tracked for the bundle being added.
+	 * 
+	 * @param bundle The <code>Bundle</code> being added to this
+	 *        <code>BundleTracker</code> object.
+	 * @param event The bundle event which caused this customizer method to be
+	 *        called or <code>null</code> if there is no bundle event associated
+	 *        with the call to this method.
+	 * @return The specified bundle.
+	 * @see BundleTrackerCustomizer#addingBundle(Bundle, BundleEvent)
+	 */
+	public Object addingBundle(Bundle bundle, BundleEvent event) {
+		return bundle;
+	}
+
+	/**
+	 * Default implementation of the
+	 * <code>BundleTrackerCustomizer.modifiedBundle</code> method.
+	 * 
+	 * <p>
+	 * This method is only called when this <code>BundleTracker</code> has been
+	 * constructed with a <code>null BundleTrackerCustomizer</code> argument.
+	 * 
+	 * <p>
+	 * This implementation does nothing.
+	 * 
+	 * @param bundle The <code>Bundle</code> whose state has been modified.
+	 * @param event The bundle event which caused this customizer method to be
+	 *        called or <code>null</code> if there is no bundle event associated
+	 *        with the call to this method.
+	 * @param object The customized object for the specified Bundle.
+	 * @see BundleTrackerCustomizer#modifiedBundle(Bundle, BundleEvent, Object)
+	 */
+	public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) {
+		/* do nothing */
+	}
+
+	/**
+	 * Default implementation of the
+	 * <code>BundleTrackerCustomizer.removedBundle</code> method.
+	 * 
+	 * <p>
+	 * This method is only called when this <code>BundleTracker</code> has been
+	 * constructed with a <code>null BundleTrackerCustomizer</code> argument.
+	 * 
+	 * <p>
+	 * This implementation does nothing.
+	 * 
+	 * @param bundle The <code>Bundle</code> being removed.
+	 * @param event The bundle event which caused this customizer method to be
+	 *        called or <code>null</code> if there is no bundle event associated
+	 *        with the call to this method.
+	 * @param object The customized object for the specified bundle.
+	 * @see BundleTrackerCustomizer#removedBundle(Bundle, BundleEvent, Object)
+	 */
+	public void removedBundle(Bundle bundle, BundleEvent event, Object object) {
+		/* do nothing */
+	}
+
+	/**
+	 * Return an array of <code>Bundle</code>s for all bundles being tracked by
+	 * this <code>BundleTracker</code>.
+	 * 
+	 * @return An array of <code>Bundle</code>s or <code>null</code> if no
+	 *         bundles are being tracked.
+	 */
+	public Bundle[] getBundles() {
+		final Tracked t = tracked();
+		if (t == null) { /* if BundleTracker is not open */
+			return null;
+		}
+		synchronized (t) {
+			int length = t.size();
+			if (length == 0) {
+				return null;
+			}
+			return (Bundle[]) t.getTracked(new Bundle[length]);
+		}
+	}
+
+	/**
+	 * Returns the customized object for the specified <code>Bundle</code> if
+	 * the specified bundle is being tracked by this <code>BundleTracker</code>.
+	 * 
+	 * @param bundle The <code>Bundle</code> being tracked.
+	 * @return The customized object for the specified <code>Bundle</code> or
+	 *         <code>null</code> if the specified <code>Bundle</code> is not
+	 *         being tracked.
+	 */
+	public Object getObject(Bundle bundle) {
+		final Tracked t = tracked();
+		if (t == null) { /* if BundleTracker is not open */
+			return null;
+		}
+		synchronized (t) {
+			return t.getCustomizedObject(bundle);
+		}
+	}
+
+	/**
+	 * Remove a bundle from this <code>BundleTracker</code>.
+	 * 
+	 * The specified bundle will be removed from this <code>BundleTracker</code>
+	 * . If the specified bundle was being tracked then the
+	 * <code>BundleTrackerCustomizer.removedBundle</code> method will be called
+	 * for that bundle.
+	 * 
+	 * @param bundle The <code>Bundle</code> to be removed.
+	 */
+	public void remove(Bundle bundle) {
+		final Tracked t = tracked();
+		if (t == null) { /* if BundleTracker is not open */
+			return;
+		}
+		t.untrack(bundle, null);
+	}
+
+	/**
+	 * Return the number of bundles being tracked by this
+	 * <code>BundleTracker</code>.
+	 * 
+	 * @return The number of bundles being tracked.
+	 */
+	public int size() {
+		final Tracked t = tracked();
+		if (t == null) { /* if BundleTracker is not open */
+			return 0;
+		}
+		synchronized (t) {
+			return t.size();
+		}
+	}
+
+	/**
+	 * Returns the tracking count for this <code>BundleTracker</code>.
+	 * 
+	 * The tracking count is initialized to 0 when this
+	 * <code>BundleTracker</code> is opened. Every time a bundle is added,
+	 * modified or removed from this <code>BundleTracker</code> the tracking
+	 * count is incremented.
+	 * 
+	 * <p>
+	 * The tracking count can be used to determine if this
+	 * <code>BundleTracker</code> has added, modified or removed a bundle by
+	 * comparing a tracking count value previously collected with the current
+	 * tracking count value. If the value has not changed, then no bundle has
+	 * been added, modified or removed from this <code>BundleTracker</code>
+	 * since the previous tracking count was collected.
+	 * 
+	 * @return The tracking count for this <code>BundleTracker</code> or -1 if
+	 *         this <code>BundleTracker</code> is not open.
+	 */
+	public int getTrackingCount() {
+		final Tracked t = tracked();
+		if (t == null) { /* if BundleTracker is not open */
+			return -1;
+		}
+		synchronized (t) {
+			return t.getTrackingCount();
+		}
+	}
+
+	/**
+	 * Inner class which subclasses AbstractTracked. This class is the
+	 * <code>SynchronousBundleListener</code> object for the tracker.
+	 * 
+	 * @ThreadSafe
+	 * @since 1.4
+	 */
+	class Tracked extends AbstractTracked implements SynchronousBundleListener {
+		/**
+		 * Tracked constructor.
+		 */
+		Tracked() {
+			super();
+		}
+
+		/**
+		 * <code>BundleListener</code> method for the <code>BundleTracker</code>
+		 * class. This method must NOT be synchronized to avoid deadlock
+		 * potential.
+		 * 
+		 * @param event <code>BundleEvent</code> object from the framework.
+		 */
+		public void bundleChanged(final BundleEvent event) {
+			/*
+			 * Check if we had a delayed call (which could happen when we
+			 * close).
+			 */
+			if (closed) {
+				return;
+			}
+			final Bundle bundle = event.getBundle();
+			final int state = bundle.getState();
+			if (DEBUG) {
+				System.out
+						.println("BundleTracker.Tracked.bundleChanged[" + state + "]: " + bundle); //$NON-NLS-1$ //$NON-NLS-2$
+			}
+
+			if ((state & mask) != 0) {
+				track(bundle, event);
+				/*
+				 * If the customizer throws an unchecked exception, it is safe
+				 * to let it propagate
+				 */
+			}
+			else {
+				untrack(bundle, event);
+				/*
+				 * If the customizer throws an unchecked exception, it is safe
+				 * to let it propagate
+				 */
+			}
+		}
+
+		/**
+		 * Call the specific customizer adding method. This method must not be
+		 * called while synchronized on this object.
+		 * 
+		 * @param item Item to be tracked.
+		 * @param related Action related object.
+		 * @return Customized object for the tracked item or <code>null</code>
+		 *         if the item is not to be tracked.
+		 */
+		Object customizerAdding(final Object item,
+				final Object related) {
+			return customizer
+					.addingBundle((Bundle) item, (BundleEvent) related);
+		}
+
+		/**
+		 * Call the specific customizer modified method. This method must not be
+		 * called while synchronized on this object.
+		 * 
+		 * @param item Tracked item.
+		 * @param related Action related object.
+		 * @param object Customized object for the tracked item.
+		 */
+		void customizerModified(final Object item,
+				final Object related, final Object object) {
+			customizer.modifiedBundle((Bundle) item, (BundleEvent) related,
+					object);
+		}
+
+		/**
+		 * Call the specific customizer removed method. This method must not be
+		 * called while synchronized on this object.
+		 * 
+		 * @param item Tracked item.
+		 * @param related Action related object.
+		 * @param object Customized object for the tracked item.
+		 */
+		void customizerRemoved(final Object item,
+				final Object related, final Object object) {
+			customizer.removedBundle((Bundle) item, (BundleEvent) related,
+					object);
+		}
+	}
+}

Added: camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/BundleTrackerCustomizer.java
URL: http://svn.apache.org/viewvc/camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/BundleTrackerCustomizer.java?rev=818182&view=auto
==============================================================================
--- camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/BundleTrackerCustomizer.java (added)
+++ camel/trunk/components/camel-osgi/src/main/java/org/apache/camel/osgi/tracker/BundleTrackerCustomizer.java Wed Sep 23 18:07:38 2009
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) OSGi Alliance (2007, 2008). 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.camel.osgi.tracker;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+
+/**
+ * The <code>BundleTrackerCustomizer</code> interface allows a
+ * <code>BundleTracker</code> to customize the <code>Bundle</code>s that are
+ * tracked. A <code>BundleTrackerCustomizer</code> is called when a bundle is
+ * being added to a <code>BundleTracker</code>. The
+ * <code>BundleTrackerCustomizer</code> can then return an object for the
+ * tracked bundle. A <code>BundleTrackerCustomizer</code> is also called when a
+ * tracked bundle is modified or has been removed from a
+ * <code>BundleTracker</code>.
+ * 
+ * <p>
+ * The methods in this interface may be called as the result of a
+ * <code>BundleEvent</code> being received by a <code>BundleTracker</code>.
+ * Since <code>BundleEvent</code>s are received synchronously by the
+ * <code>BundleTracker</code>, it is highly recommended that implementations of
+ * these methods do not alter bundle states while being synchronized on any
+ * object.
+ * 
+ * <p>
+ * The <code>BundleTracker</code> class is thread-safe. It does not call a
+ * <code>BundleTrackerCustomizer</code> while holding any locks.
+ * <code>BundleTrackerCustomizer</code> implementations must also be
+ * thread-safe.
+ * 
+ * @ThreadSafe
+ * @version $Revision$
+ * @since 1.4
+ */
+public interface BundleTrackerCustomizer {
+	/**
+	 * A bundle is being added to the <code>BundleTracker</code>.
+	 * 
+	 * <p>
+	 * This method is called before a bundle which matched the search parameters
+	 * of the <code>BundleTracker</code> is added to the
+	 * <code>BundleTracker</code>. This method should return the object to be
+	 * tracked for the specified <code>Bundle</code>. The returned object is
+	 * stored in the <code>BundleTracker</code> and is available from the
+	 * {@link BundleTracker#getObject(Bundle) getObject} method.
+	 * 
+	 * @param bundle The <code>Bundle</code> being added to the
+	 *        <code>BundleTracker</code>.
+	 * @param event The bundle event which caused this customizer method to be
+	 *        called or <code>null</code> if there is no bundle event associated
+	 *        with the call to this method.
+	 * @return The object to be tracked for the specified <code>Bundle</code>
+	 *         object or <code>null</code> if the specified <code>Bundle</code>
+	 *         object should not be tracked.
+	 */
+	public Object addingBundle(Bundle bundle, BundleEvent event);
+
+	/**
+	 * A bundle tracked by the <code>BundleTracker</code> has been modified.
+	 * 
+	 * <p>
+	 * This method is called when a bundle being tracked by the
+	 * <code>BundleTracker</code> has had its state modified.
+	 * 
+	 * @param bundle The <code>Bundle</code> whose state has been modified.
+	 * @param event The bundle event which caused this customizer method to be
+	 *        called or <code>null</code> if there is no bundle event associated
+	 *        with the call to this method.
+	 * @param object The tracked object for the specified bundle.
+	 */
+	public void modifiedBundle(Bundle bundle, BundleEvent event,
+			Object object);
+
+	/**
+	 * A bundle tracked by the <code>BundleTracker</code> has been removed.
+	 * 
+	 * <p>
+	 * This method is called after a bundle is no longer being tracked by the
+	 * <code>BundleTracker</code>.
+	 * 
+	 * @param bundle The <code>Bundle</code> that has been removed.
+	 * @param event The bundle event which caused this customizer method to be
+	 *        called or <code>null</code> if there is no bundle event associated
+	 *        with the call to this method.
+	 * @param object The tracked object for the specified bundle.
+	 */
+	public void removedBundle(Bundle bundle, BundleEvent event,
+			Object object);
+}