Moving the EventAdmin from sandbox to trunk (FELIX-19)
git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@397861 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/Activator.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/Activator.java
new file mode 100644
index 0000000..d0e8cdb
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/Activator.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright 2005 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.eventadmin.impl;
+
+import org.apache.felix.eventadmin.impl.adapter.BundleEventAdapter;
+import org.apache.felix.eventadmin.impl.adapter.FrameworkEventAdapter;
+import org.apache.felix.eventadmin.impl.adapter.LogEventAdapter;
+import org.apache.felix.eventadmin.impl.adapter.ServiceEventAdapter;
+import org.apache.felix.eventadmin.impl.dispatch.CacheThreadPool;
+import org.apache.felix.eventadmin.impl.dispatch.DelayScheduler;
+import org.apache.felix.eventadmin.impl.dispatch.Scheduler;
+import org.apache.felix.eventadmin.impl.dispatch.TaskHandler;
+import org.apache.felix.eventadmin.impl.dispatch.ThreadPool;
+import org.apache.felix.eventadmin.impl.handler.BlacklistingHandlerTasks;
+import org.apache.felix.eventadmin.impl.handler.CacheFilters;
+import org.apache.felix.eventadmin.impl.handler.CacheTopicHandlerFilters;
+import org.apache.felix.eventadmin.impl.handler.CleanBlackList;
+import org.apache.felix.eventadmin.impl.handler.Filters;
+import org.apache.felix.eventadmin.impl.handler.HandlerTasks;
+import org.apache.felix.eventadmin.impl.handler.TopicHandlerFilters;
+import org.apache.felix.eventadmin.impl.security.CacheTopicPermissions;
+import org.apache.felix.eventadmin.impl.security.SecureEventAdminFactory;
+import org.apache.felix.eventadmin.impl.security.TopicPermissions;
+import org.apache.felix.eventadmin.impl.tasks.AsyncDeliverTasks;
+import org.apache.felix.eventadmin.impl.tasks.DeliverTasks;
+import org.apache.felix.eventadmin.impl.tasks.DispatchTask;
+import org.apache.felix.eventadmin.impl.tasks.SyncDeliverTasks;
+import org.apache.felix.eventadmin.impl.util.LeastRecentlyUsedCacheMap;
+import org.apache.felix.eventadmin.impl.util.LogWrapper;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.TopicPermission;
+
+/**
+ * The activator of the EventAdmin bundle. This class registers an implementation of
+ * the OSGi R4 <tt>EventAdmin</tt> service (see the Compendium 113) with the
+ * framework. It features timeout-based blacklisting of event-handlers for both,
+ * asynchronous and synchronous event-dispatching (as a spec conform optional
+ * extension).
+ *
+ * The service knows about the following properties which are read at bundle startup:
+ *
+ * <p>
+ * <p>
+ * <tt>org.apache.felix.eventadmin.CacheSize</tt> - The size of various internal
+ * caches.
+ * </p>
+ * The default value is 30. Increase in case of a large number (more then 100) of
+ * <tt>EventHandler</tt> services. A value less then 10 triggers the default value.
+ * </p>
+ * <p>
+ * <p>
+ * <tt>org.apache.felix.eventadmin.ThreadPoolSize</tt> - The size of the thread
+ * pool.
+ * </p>
+ * The default value is 10. Increase in case of a large amount of synchronous events
+ * where the <tt>EventHandler</tt> services in turn send new synchronous events in
+ * the event dispatching thread or a lot of timeouts are to be expected. A value of
+ * less then 2 triggers the default value. A value of 2 effectively disables thread
+ * pooling.
+ * </p>
+ * <p>
+ * <p>
+ * <tt>org.apache.felix.eventadmin.Timeout</tt> - The black-listing timeout in
+ * milliseconds
+ * </p>
+ * The default value is 5000. Increase or decrease at own discretion. A value of less
+ * then 100 turns timeouts off. Any other value is the time in milliseconds granted
+ * to each <tt>EventHandler</tt> before it gets blacklisted.
+ * </p>
+ * <p>
+ * <p>
+ * <tt>org.apache.felix.eventadmin.RequireTopic</tt> - Are <tt>EventHandler</tt>
+ * required to be registered with a topic?
+ * </p>
+ * The default is <tt>true</tt>. The specification says that <tt>EventHandler</tt>
+ * must register with a list of topics they are interested in. Setting this value to
+ * <tt>false</tt> will enable that handlers without a topic are receiving all events
+ * (i.e., they are treated the same as with a topic=*).
+ * </p>
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+// TODO: This is a tricky one: what do we do in case we are stopped but still have
+// already posted or send events that are not yet delivered? At the moment we will
+// throw an IllegalStateException on new events after we are stopped but
+// will deliver the pending events. Now the question is do we block in the stop
+// method until we don't have pending events anymore or (as we currently do) return
+// immediately and deliver the events ignoring the fact that we are stopped? What is
+// the best practice? Alternatively, we could just free all blocked threads and
+// discard any pending events (Note that this would need some refactoring). From my
+// point of view the current approach has the advantage that the semantics are
+// preserved: First, the immediate return allows other bundles to be notified that we
+// are stopped (and hence they deserve an IllegalStateException if they call us
+// regardless) second, successful posted or send events will be delivered, and third
+// we don't block a shutdown due to only using daemon threads internally. However,
+// it might take some time until we settle down which is somewhat cumbersome given
+// that we already are marked as stopped?
+// TODO: Security is in place but untested due to not being implemented by the
+// framework. However, it needs to be revisited once security is implemented.
+// Two places are affected by this namely, security/* and handler/*
+public class Activator implements BundleActivator
+{
+ // The thread pool used - this is a member because we need to close it on stop
+ private volatile ThreadPool m_pool;
+
+ // The asynchronous event queue - this is a member because we need to close it on
+ // stop
+ private volatile TaskHandler m_asyncQueue;
+
+ // The synchronous event queue - this is a member because we need to close it on
+ // stop
+ private volatile TaskHandler m_syncQueue;
+
+ // The actual implementation of the service - this is a member because we need to
+ // close it on stop. Note, security is not part of this implementation but is
+ // added via a decorator in the start method (this is the wrapped object without
+ // the wrapper).
+ private volatile EventAdminImpl m_admin;
+
+ /**
+ * Called upon starting of the bundle. Constructs and registers the EventAdmin
+ * service with the framework. Note that the properties of the service are
+ * requested from the context in this method hence, the bundle has to be
+ * restarted in order to take changed properties into account.
+ *
+ * @param context The bundle context passed by the framework
+ *
+ * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+ */
+ public void start(final BundleContext context)
+ {
+ // init the LogWrapper. Subsequently, the static methods of the LogWrapper
+ // can be used to log messages similar to the LogService. The effect of a
+ // call to any of this methods is either a print to standard out (in case
+ // no LogService is present) or a call to the respective method of
+ // available LogServices (the reason is that this way the bundle is
+ // independent of the org.osgi.service.log package)
+ LogWrapper.setContext(context);
+
+ // The size of various internal caches. At the moment there are 4
+ // internal caches affected. Each will cache the determined amount of
+ // small but frequently used objects (i.e., in case of the default value
+ // we end-up with a total of 120 small objects being cached). A value of less
+ // then 10 triggers the default value.
+ final int cacheSize = getIntProperty("org.apache.felix.eventadmin.CacheSize",
+ context, 30, 10);
+
+ // The size of the internal thread pool. Note that we must execute
+ // each synchronous event dispatch that happens in the synchronous event
+ // dispatching thread in a new thread, hence a small thread pool is o.k.
+ // A value of less then 2 triggers the default value. A value of 2
+ // effectively disables thread pooling. Furthermore, this will be used by
+ // a lazy thread pool (i.e., new threads are created when needed). Ones the
+ // the size is reached and no cached thread is available new threads will
+ // be created.
+ final int threadPoolSize = getIntProperty(
+ "org.apache.felix.eventadmin.ThreadPoolSize", context, 10, 2);
+
+ // The timeout in milliseconds - A value of less then 100 turns timeouts off.
+ // Any other value is the time in milliseconds granted to each EventHandler
+ // before it gets blacklisted.
+ final int timeout = getIntProperty("org.apache.felix.eventadmin.Timeout",
+ context, 5000, Integer.MIN_VALUE);
+
+ // Are EventHandler required to be registered with a topic? - The default is
+ // true. The specification says that EventHandler must register with a list
+ // of topics they are interested in. Setting this value to false will enable
+ // that handlers without a topic are receiving all events
+ // (i.e., they are treated the same as with a topic=*).
+ final boolean requireTopic = getBooleanProperty(
+ "org.apache.felix.eventadmin.RequireTopic", context, true);
+
+ LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG,
+ "org.apache.felix.eventadmin.CacheSize=" + cacheSize);
+
+ LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG,
+ "org.apache.felix.eventadmin.ThreadPoolSize=" + threadPoolSize);
+
+ LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG,
+ "org.apache.felix.eventadmin.Timeout=" + timeout);
+
+ LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG,
+ "org.apache.felix.eventadmin.RequireTopic=" + requireTopic);
+
+ final TopicPermissions publishPermissions = new CacheTopicPermissions(
+ new LeastRecentlyUsedCacheMap(cacheSize), TopicPermission.PUBLISH);
+
+ final TopicPermissions subscribePermissions = new CacheTopicPermissions(
+ new LeastRecentlyUsedCacheMap(cacheSize), TopicPermission.SUBSCRIBE);
+
+ final TopicHandlerFilters topicHandlerFilters =
+ new CacheTopicHandlerFilters(new LeastRecentlyUsedCacheMap(cacheSize),
+ requireTopic);
+
+ final Filters filters = new CacheFilters(
+ new LeastRecentlyUsedCacheMap(cacheSize), context);
+
+ // The handlerTasks object is responsible to determine concerned EventHandler
+ // for a given event. Additionally, it keeps a list of blacklisted handlers.
+ // Note that blacklisting is deactivated by selecting a different scheduler
+ // below (and not in this HandlerTasks object!)
+ final HandlerTasks handlerTasks = new BlacklistingHandlerTasks(context,
+ new CleanBlackList(), topicHandlerFilters, filters,
+ subscribePermissions);
+
+ // Either we need a scheduler that will trigger EventHandler blacklisting
+ // (timeout >= 100) or a null object (timeout < 100)
+ final Scheduler scheduler = createScheduler(timeout);
+
+ // Note that this uses a lazy thread pool that will create new threads on
+ // demand - in case none of its cached threads is free - until threadPoolSize
+ // is reached. Subsequently, a threadPoolSize of 2 effectively disables
+ // caching of threads.
+ m_pool = new CacheThreadPool(threadPoolSize);
+
+ m_asyncQueue = new TaskHandler();
+
+ m_syncQueue = new TaskHandler();
+
+ m_admin = new EventAdminImpl(handlerTasks,
+ createAsyncExecuters(m_asyncQueue, m_syncQueue, scheduler, m_pool),
+ createSyncExecuters(m_syncQueue, scheduler, m_pool));
+
+ // register the admin wrapped in a service factory (SecureEventAdminFactory)
+ // that hands-out the m_admin object wrapped in a decorator that checks
+ // appropriated permissions of each calling bundle
+ context.registerService(EventAdmin.class.getName(),
+ new SecureEventAdminFactory(m_admin, publishPermissions), null);
+
+ // Finally, adapt the outside events to our kind of events as per spec
+ adaptEvents(context, m_admin);
+ }
+
+ /**
+ * Called upon stopping the bundle.
+ *
+ * @param context The bundle context passed by the framework
+ *
+ * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+ */
+ public void stop(final BundleContext context)
+ {
+ m_admin.stop();
+
+ m_pool.close();
+
+ m_asyncQueue.close();
+
+ m_syncQueue.close();
+
+ m_admin = null;
+
+ m_pool = null;
+
+ m_asyncQueue = null;
+
+ m_syncQueue = null;
+ }
+
+ /*
+ * Create an AsyncDeliverTasks object that is used to dispatch asynchronous
+ * events. Additionally, the asynchronous dispatch queue is initialized and
+ * activated (i.e., a thread is started via the given ThreadPool).
+ */
+ private DeliverTasks createAsyncExecuters(final TaskHandler handler,
+ final TaskHandler handoverHandler, final Scheduler scheduler,
+ final ThreadPool pool)
+ {
+ // init the queue
+ final AsyncDeliverTasks result = new AsyncDeliverTasks(handler,
+ handoverHandler, pool);
+
+ // set-up the queue for asynchronous event delivery and activate it
+ // (i.e., a thread is started via the pool)
+ result.execute(new DispatchTask(handler, scheduler, result));
+
+ return result;
+ }
+
+ /*
+ * Create a SyncDeliverTasks object that is used to dispatch synchronous events.
+ * Additionally, the synchronous dispatch queue is initialized and activated
+ * (i.e., a thread is started via the given ThreadPool).
+ */
+ private DeliverTasks createSyncExecuters(final TaskHandler handler,
+ final Scheduler scheduler, final ThreadPool pool)
+ {
+ // init the queue
+ final SyncDeliverTasks result = new SyncDeliverTasks(handler, pool);
+
+ // set-up the queue for synchronous event delivery and activate it
+ // (i.e. a thread is started via the pool)
+ result.execute(new DispatchTask(handler, scheduler, result));
+
+ return result;
+ }
+
+ /*
+ * Returns either a new DelayScheduler with a delay of timeout or the
+ * Scheduler.NULL_SCHEDULER in case timeout is < 100 in which case timeout and
+ * subsequently black-listing is disabled.
+ */
+ private Scheduler createScheduler(final int timeout)
+ {
+ if(100 > timeout)
+ {
+ return Scheduler.NULL_SCHEDULER;
+ }
+
+ return new DelayScheduler(timeout);
+ }
+
+ /*
+ * Init the adapters in org.apache.felix.eventadmin.impl.adapter
+ */
+ private void adaptEvents(final BundleContext context, final EventAdmin admin)
+ {
+ new FrameworkEventAdapter(context, admin);
+
+ new BundleEventAdapter(context, admin);
+
+ new ServiceEventAdapter(context, admin);
+
+ new LogEventAdapter(context, admin);
+ }
+
+ /*
+ * Returns either the parsed int from the value of the property if it is set and
+ * not less then the min value or the default. Additionally, a warning is
+ * generated in case the value is erroneous (i.e., can not be parsed as an int or
+ * is less then the min value).
+ */
+ private int getIntProperty(final String key, final BundleContext context,
+ final int defaultValue, final int min)
+ {
+ final String value = context.getProperty(key);
+
+ if(null != value)
+ {
+ try {
+ final int result = Integer.parseInt(value);
+
+ if(result >= min)
+ {
+ return result;
+ }
+
+ LogWrapper.getLogger().log(LogWrapper.LOG_WARNING,
+ "Value for property: " + key + " is to low - Using default");
+ } catch (NumberFormatException e) {
+ LogWrapper.getLogger().log(LogWrapper.LOG_WARNING,
+ "Unable to parse property: " + key + " - Using default", e);
+ }
+ }
+
+ return defaultValue;
+ }
+
+
+ /*
+ * Returns true if the value of the property is set and is either 1, true, or yes
+ * Returns false if the value of the property is set and is either 0, false, or no
+ * Returns the defaultValue otherwise
+ */
+ private boolean getBooleanProperty(final String key, final BundleContext context,
+ final boolean defaultValue)
+ {
+ String value = context.getProperty(key);
+
+ if(null != value)
+ {
+ value = value.trim().toLowerCase();
+
+ if(0 < value.length() && ("0".equals(value) || "false".equals(value)
+ || "no".equals(value)))
+ {
+ return false;
+ }
+
+ if(0 < value.length() && ("1".equals(value) || "true".equals(value)
+ || "yes".equals(value)))
+ {
+ return true;
+ }
+ }
+
+ return defaultValue;
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/EventAdminImpl.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/EventAdminImpl.java
new file mode 100644
index 0000000..0e4a23d
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/EventAdminImpl.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2005 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.eventadmin.impl;
+
+import org.apache.felix.eventadmin.impl.handler.HandlerTasks;
+import org.apache.felix.eventadmin.impl.tasks.DeliverTasks;
+import org.apache.felix.eventadmin.impl.tasks.HandlerTask;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+
+/**
+ * This is the actual implementation of the OSGi R4 Event Admin Service (see the
+ * Compendium 113 for details). The implementation uses a <tt>HandlerTasks</tt>
+ * in order to determine applicable <tt>EventHandler</tt> for a specific event and
+ * subsequently dispatches the event to the handlers via <tt>DeliverTasks</tt>.
+ * To do this, it uses two different <tt>DeliverTasks</tt> one for asynchronous and
+ * one for synchronous event delivery depending on whether its <tt>post()</tt> or
+ * its <tt>send()</tt> method is called. Note that the actual work is done in the
+ * implementations of the <tt>DeliverTasks</tt>. Additionally, a stop method is
+ * provided that prevents subsequent events to be delivered.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class EventAdminImpl implements EventAdmin
+{
+ // The factory used to determine applicable EventHandlers - this will be replaced
+ // by a null object in stop() that subsequently throws an IllegalStateException
+ private volatile HandlerTasks m_managers;
+
+ // The asynchronous event dispatcher
+ private final DeliverTasks m_postManager;
+
+ // The synchronous event dispatcher
+ private final DeliverTasks m_sendManager;
+
+ /**
+ * The constructor of the <tt>EventAdmin</tt> implementation. The
+ * <tt>HandlerTasks</tt> factory is used to determine applicable
+ * <tt>EventHandler</tt> for a given event. Additionally, the two
+ * <tt>DeliverTasks</tt> are used to dispatch the event.
+ *
+ * @param managers The factory used to determine applicable <tt>EventHandler</tt>
+ * @param postManager The asynchronous event dispatcher
+ * @param sendManager The synchronous event dispatcher
+ */
+ public EventAdminImpl(final HandlerTasks managers,
+ final DeliverTasks postManager, final DeliverTasks sendManager)
+ {
+ checkNull(managers, "Managers");
+ checkNull(postManager, "PostManager");
+ checkNull(sendManager, "SendManager");
+
+ m_managers = managers;
+
+ m_postManager = postManager;
+
+ m_sendManager = sendManager;
+ }
+
+ /**
+ * Post an asynchronous event.
+ *
+ * @param event The event to be posted by this service
+ *
+ * @throws IllegalStateException - In case we are stopped
+ *
+ * @see org.osgi.service.event.EventAdmin#postEvent(org.osgi.service.event.Event)
+ */
+ public void postEvent(final Event event)
+ {
+ handleEvent(m_managers.createHandlerTasks(event), m_postManager);
+ }
+
+ /**
+ * Send a synchronous event.
+ *
+ * @param event The event to be send by this service
+ *
+ * @throws IllegalStateException - In case we are stopped
+ *
+ * @see org.osgi.service.event.EventAdmin#sendEvent(org.osgi.service.event.Event)
+ */
+ public void sendEvent(final Event event)
+ {
+ handleEvent(m_managers.createHandlerTasks(event), m_sendManager);
+ }
+
+ /**
+ * This method can be used to stop the delivery of events. The m_managers is
+ * replaced with a null object that throws an IllegalStateException on a call
+ * to <tt>createHandlerTasks()</tt>.
+ */
+ public void stop()
+ {
+ // replace the HandlerTasks with a null object that will throw an
+ // IllegalStateException on a call to createHandlerTasks
+ m_managers = new HandlerTasks()
+ {
+ /**
+ * This is a null object and this method will throw an
+ * IllegalStateException due to the bundle being stopped.
+ *
+ * @param event An event that is not used.
+ *
+ * @return This method does not return normally
+ *
+ * @throws IllegalStateException - This is a null object and this method
+ * will always throw an IllegalStateException
+ */
+ public HandlerTask[] createHandlerTasks(final Event event)
+ {
+ throw new IllegalStateException("The EventAdmin is stopped");
+ }
+ };
+ }
+
+ /*
+ * This is a utility method that uses the given DeliverTasks to create a
+ * dispatch tasks that subsequently is used to dispatch the given HandlerTasks.
+ */
+ private void handleEvent(final HandlerTask[] managers,
+ final DeliverTasks manager)
+ {
+ if (0 < managers.length)
+ {
+ // This might throw an IllegalStateException in case that we are stopped
+ // and the null object for m_managers was not fast enough established
+ // This is needed in the adapter/* classes due to them sending
+ // events whenever they receive an event from their source.
+ // Service importers that call us regardless of the fact that we are
+ // stopped deserve an exception anyways
+ manager.createTask().execute(managers);
+ }
+ }
+
+ /*
+ * This is a utility method that will throw a <tt>NullPointerException</tt>
+ * in case that the given object is null. The message will be of the form
+ * "${name} + may not be null".
+ */
+ private void checkNull(final Object object, final String name)
+ {
+ if(null == object)
+ {
+ throw new NullPointerException(name + " may not be null");
+ }
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/adapter/BundleEventAdapter.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/adapter/BundleEventAdapter.java
new file mode 100644
index 0000000..c40da41
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/adapter/BundleEventAdapter.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.adapter;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.EventConstants;
+
+/**
+ * This class registers itself as a listener for bundle events and posts them via
+ * the EventAdmin as specified in 113.6.4 OSGi R4 compendium.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class BundleEventAdapter implements BundleListener
+{
+ private final EventAdmin m_admin;
+
+ /**
+ * The constructor of the adapter. This will register the adapter with the given
+ * context as a <tt>BundleListener</tt> and subsequently, will post received
+ * events via the given EventAdmin.
+ *
+ * @param context The bundle context with which to register as a listener.
+ * @param admin The <tt>EventAdmin</tt> to use for posting events.
+ */
+ public BundleEventAdapter(final BundleContext context, final EventAdmin admin)
+ {
+ if(null == admin)
+ {
+ throw new NullPointerException("EventAdmin must not be null");
+ }
+
+ m_admin = admin;
+
+ context.addBundleListener(this);
+ }
+
+ /**
+ * Once a bundle event is received this method assembles and posts an event via
+ * the <tt>EventAdmin</tt> as specified in 113.6.4 OSGi R4 compendium.
+ *
+ * @param event The event to adapt.
+ */
+ public void bundleChanged(final BundleEvent event)
+ {
+ final Dictionary properties = new Hashtable();
+
+ properties.put(EventConstants.EVENT, event);
+
+ properties.put("bundle.id", new Long(event.getBundle()
+ .getBundleId()));
+
+ final String symbolicName = event.getBundle().getSymbolicName();
+
+ if (null != symbolicName)
+ {
+ properties.put(EventConstants.BUNDLE_SYMBOLICNAME,
+ symbolicName);
+ }
+
+ properties.put("bundle", event.getBundle());
+
+ final StringBuffer topic = new StringBuffer(BundleEvent.class
+ .getName().replace('.', '/')).append('/');
+
+ switch (event.getType())
+ {
+ case BundleEvent.INSTALLED:
+ topic.append("INSTALLED");
+ break;
+ case BundleEvent.STARTED:
+ topic.append("STARTED");
+ break;
+ case BundleEvent.STOPPED:
+ topic.append("STOPPED");
+ break;
+ case BundleEvent.UPDATED:
+ topic.append("UPDATED");
+ break;
+ case BundleEvent.UNINSTALLED:
+ topic.append("UNINSTALLED");
+ break;
+ case BundleEvent.RESOLVED:
+ topic.append("RESOLVED");
+ break;
+ case BundleEvent.UNRESOLVED:
+ topic.append("UNRESOLVED");
+ break;
+ default:
+ return; // IGNORE EVENT
+ }
+
+ try {
+ m_admin.postEvent(new Event(topic.toString(), properties));
+ } catch (IllegalStateException e) {
+ // This is o.k. - indicates that we are stopped.
+ }
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/adapter/FrameworkEventAdapter.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/adapter/FrameworkEventAdapter.java
new file mode 100644
index 0000000..3e0e926
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/adapter/FrameworkEventAdapter.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.adapter;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.EventConstants;
+
+/**
+ * This class registers itself as a listener for framework events and posts them via
+ * the EventAdmin as specified in 113.6.3 OSGi R4 compendium.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class FrameworkEventAdapter implements FrameworkListener
+{
+ private final EventAdmin m_admin;
+
+ /**
+ * The constructor of the adapter. This will register the adapter with the
+ * given context as a <tt>FrameworkListener</tt> and subsequently, will
+ * post received events via the given EventAdmin.
+ *
+ * @param context The bundle context with which to register as a listener.
+ * @param admin The <tt>EventAdmin</tt> to use for posting events.
+ */
+ public FrameworkEventAdapter(final BundleContext context, final EventAdmin admin)
+ {
+ if(null == admin)
+ {
+ throw new NullPointerException("EventAdmin must not be null");
+ }
+
+ m_admin = admin;
+
+ context.addFrameworkListener(this);
+ }
+
+ /**
+ * Once a framework event is received this method assembles and posts an event
+ * via the <tt>EventAdmin</tt> as specified in 113.6.3 OSGi R4 compendium.
+ *
+ * @param event The event to adapt.
+ */
+ public void frameworkEvent(final FrameworkEvent event)
+ {
+ final Dictionary properties = new Hashtable();
+
+ properties.put(EventConstants.EVENT, event);
+
+ final Bundle bundle = event.getBundle();
+
+ if (null != bundle)
+ {
+ properties.put("bundle.id", new Long(bundle.getBundleId()));
+
+ final String symbolicName = bundle.getSymbolicName();
+
+ if (null != symbolicName)
+ {
+ properties.put(EventConstants.BUNDLE_SYMBOLICNAME,
+ symbolicName);
+ }
+
+ properties.put("bundle", bundle);
+ }
+
+ final Throwable thrown = event.getThrowable();
+
+ if (null != thrown)
+ {
+ properties.put(EventConstants.EXCEPTION_CLASS,
+ thrown.getClass().getName());
+
+ final String message = thrown.getMessage();
+
+ if (null != message)
+ {
+ properties.put(EventConstants.EXCEPTION_MESSAGE,
+ message);
+ }
+
+ properties.put(EventConstants.EXCEPTION, thrown);
+ }
+
+ final StringBuffer topic = new StringBuffer(
+ FrameworkEvent.class.getName().replace('.', '/'))
+ .append('/');
+
+ switch (event.getType())
+ {
+ case FrameworkEvent.STARTED:
+ topic.append("STARTED");
+ break;
+ case FrameworkEvent.ERROR:
+ topic.append("ERROR");
+ break;
+ case FrameworkEvent.PACKAGES_REFRESHED:
+ topic.append("PACKAGES_REFRESHED");
+ break;
+ case FrameworkEvent.STARTLEVEL_CHANGED:
+ topic.append("STARTLEVEL_CHANGED");
+ break;
+ case FrameworkEvent.WARNING:
+ topic.append("WARNING");
+ break;
+ case FrameworkEvent.INFO:
+ topic.append("INFO");
+ break;
+ default:
+ return; // IGNORE EVENT
+ }
+
+ try {
+ m_admin.postEvent(new Event(topic.toString(), properties));
+ } catch(IllegalStateException e) {
+ // This is o.k. - indicates that we are stopped.
+ }
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/adapter/LogEventAdapter.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/adapter/LogEventAdapter.java
new file mode 100644
index 0000000..4d98e8e
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/adapter/LogEventAdapter.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.adapter;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.eventadmin.impl.util.LogWrapper;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.EventConstants;
+
+/**
+ * This class registers itself as a listener for <tt>LogReaderService</tt> services
+ * with the framework and subsequently, a <tt>LogListener</tt> callback with any
+ * currently available <tt>LogReaderService</tt>. Any received log event is then
+ * posted via the EventAdmin as specified in 113.6.6 OSGi R4 compendium.
+ * Note that this class does not create a hard dependency on the org.osgi.service.log
+ * packages. The adaption only takes place if it is present or once it becomes
+ * available hence, combined with a DynamicImport-Package no hard dependency is
+ * needed.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class LogEventAdapter implements ServiceListener
+{
+ // The internal lock for this object used instead synchronized(this)
+ private final Object m_lock = new Object();
+
+ private BundleContext m_context;
+
+ // A singleton instance of the used log listener that is the adapter
+ private Object m_logListener;
+
+ final EventAdmin m_admin;
+
+ /**
+ * The constructor of the adapter. This will register the adapter with the
+ * given context as a listener for <tt>LogReaderService</tt> services and
+ * subsequently, a <tt>LogListener</tt> callback with any currently available
+ * <tt>LogReaderService</tt>. Any received log event is then posted via the given
+ * EventAdmin.
+ *
+ * @param context The bundle context with which to register as a listener.
+ * @param admin The <tt>EventAdmin</tt> to use for posting events.
+ */
+ public LogEventAdapter(final BundleContext context, final EventAdmin admin)
+ {
+ if(null == admin)
+ {
+ throw new NullPointerException("EventAdmin must not be null");
+ }
+
+ m_context = context;
+
+ m_admin = admin;
+
+ try
+ {
+ m_context.addServiceListener(this, "(" + Constants.OBJECTCLASS
+ + "=org.osgi.service.log.LogReaderService)");
+
+ final ServiceReference[] refs;
+
+ refs = m_context.getServiceReferences(
+ "org.osgi.service.log.LogReaderService", null);
+
+ if (null != refs)
+ {
+ for (int i = 0; i < refs.length; i++)
+ {
+ final org.osgi.service.log.LogReaderService logReader =
+ (org.osgi.service.log.LogReaderService) m_context
+ .getService(refs[i]);
+
+ if (null != logReader)
+ {
+ logReader.addLogListener((org.osgi.service.log.LogListener)
+ getLogListener());
+ }
+ }
+ }
+ } catch (InvalidSyntaxException e)
+ {
+ // This never happens
+ }
+ }
+
+ /**
+ * Once a <tt>LogReaderService</tt> register event is received this method
+ * registers a <tt>LogListener</tt> with the received service that assembles
+ * and posts any log event via the <tt>EventAdmin</tt> as specified in
+ * 113.6.6 OSGi R4 compendium.
+ *
+ * @param event The event to adapt.
+ */
+ public void serviceChanged(final ServiceEvent event)
+ {
+ if (ServiceEvent.REGISTERED == event.getType())
+ {
+ final org.osgi.service.log.LogReaderService logReader =
+ (org.osgi.service.log.LogReaderService) m_context
+ .getService(event.getServiceReference());
+
+ if (null != logReader)
+ {
+ logReader.addLogListener((org.osgi.service.log.LogListener)
+ getLogListener());
+ }
+ }
+ }
+
+ /*
+ * Constructs a LogListener that assembles and posts any log event via the
+ * EventAdmin as specified in 113.6.6 OSGi R4 compendium. Note that great
+ * care is taken to not create a hard dependency on the org.osgi.service.log
+ * package.
+ */
+ private Object getLogListener()
+ {
+ synchronized (m_lock)
+ {
+ if (null != m_logListener)
+ {
+ return m_logListener;
+ }
+
+ m_logListener = new org.osgi.service.log.LogListener()
+ {
+ public void logged(final org.osgi.service.log.LogEntry entry)
+ {
+ // This is where the assembly as specified in 133.6.6 OSGi R4
+ // compendium is taking place (i.e., the log entry is adapted to
+ // an event and posted via the EventAdmin)
+
+ final Dictionary properties = new Hashtable();
+
+ final Bundle bundle = entry.getBundle();
+
+ if (null != bundle)
+ {
+ properties.put("bundle.id", new Long(bundle
+ .getBundleId()));
+
+ final String symbolicName = bundle.getSymbolicName();
+
+ if (null != symbolicName)
+ {
+ properties.put(EventConstants.BUNDLE_SYMBOLICNAME,
+ symbolicName);
+ }
+
+ properties.put("bundle", bundle);
+ }
+
+ properties.put("log.level", new Integer(entry.getLevel()));
+
+ properties.put(EventConstants.MESSAGE, entry.getMessage());
+
+ properties.put(EventConstants.TIMESTAMP, new Long(
+ entry.getTime()));
+
+ properties.put("log.entry", entry);
+
+ final Throwable exception = entry.getException();
+
+ if (null != exception)
+ {
+ properties.put(EventConstants.EXCEPTION_CLASS,
+ exception.getClass().getName());
+
+ final String message = exception.getMessage();
+
+ if (null != message)
+ {
+ properties.put(EventConstants.EXCEPTION_MESSAGE,
+ message);
+ }
+
+ properties.put(EventConstants.EXCEPTION, exception);
+ }
+
+ final ServiceReference service = entry
+ .getServiceReference();
+
+ if (null != service)
+ {
+ properties.put(EventConstants.SERVICE, service);
+
+ final Object id = service
+ .getProperty(EventConstants.SERVICE_ID);
+
+ if (null != id)
+ {
+ try
+ {
+ properties.put(EventConstants.SERVICE_ID,
+ new Long(id.toString()));
+ } catch (NumberFormatException ne)
+ {
+ // LOG and IGNORE
+ LogWrapper.getLogger().log(
+ entry.getServiceReference(),
+ LogWrapper.LOG_WARNING, "Exception parsing " +
+ EventConstants.SERVICE_ID + "=" + id, ne);
+ }
+ }
+
+ final Object pid = service.getProperty(
+ EventConstants.SERVICE_PID);
+
+ if (null != pid)
+ {
+ properties.put(EventConstants.SERVICE_PID,
+ pid.toString());
+ }
+
+ final Object objectClass = service.getProperty(
+ Constants.OBJECTCLASS);
+
+ if (null != objectClass)
+ {
+ if (objectClass instanceof String[])
+ {
+ properties.put(
+ EventConstants.SERVICE_OBJECTCLASS,
+ objectClass);
+ }
+ else
+ {
+ properties.put(
+ EventConstants.SERVICE_OBJECTCLASS,
+ new String[] { objectClass.toString() });
+ }
+ }
+ }
+
+ final StringBuffer topic = new StringBuffer(
+ org.osgi.service.log.LogEntry.class.getName().replace(
+ '.', '/')).append('/');
+
+ switch (entry.getLevel())
+ {
+ case org.osgi.service.log.LogService.LOG_ERROR:
+ topic.append("LOG_ERROR");
+ break;
+ case org.osgi.service.log.LogService.LOG_WARNING:
+ topic.append("LOG_WARNING");
+ break;
+ case org.osgi.service.log.LogService.LOG_INFO:
+ topic.append("LOG_INFO");
+ break;
+ case org.osgi.service.log.LogService.LOG_DEBUG:
+ topic.append("LOG_DEBUG");
+ break;
+ default:
+ topic.append("LOG_OTHER");
+ break;
+ }
+
+ try {
+ m_admin.postEvent(new Event(topic.toString(), properties));
+ } catch(IllegalStateException e) {
+ // This is o.k. - indicates that we are stopped.
+ }
+ }
+ };
+
+ return m_logListener;
+ }
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/adapter/ServiceEventAdapter.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/adapter/ServiceEventAdapter.java
new file mode 100644
index 0000000..bd3336f
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/adapter/ServiceEventAdapter.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.adapter;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.eventadmin.impl.util.LogWrapper;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.EventConstants;
+
+/**
+ * This class registers itself as a listener for service events and posts them via
+ * the EventAdmin as specified in 113.6.5 OSGi R4 compendium.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class ServiceEventAdapter implements ServiceListener
+{
+ private final EventAdmin m_admin;
+
+ /**
+ * The constructor of the adapter. This will register the adapter with the
+ * given context as a <tt>ServiceListener</tt> and subsequently, will
+ * post received events via the given EventAdmin.
+ *
+ * @param context The bundle context with which to register as a listener.
+ * @param admin The <tt>EventAdmin</tt> to use for posting events.
+ */
+ public ServiceEventAdapter(final BundleContext context, final EventAdmin admin)
+ {
+ m_admin = admin;
+
+ context.addServiceListener(this);
+ }
+
+ /**
+ * Once a Service event is received this method assembles and posts an event
+ * via the <tt>EventAdmin</tt> as specified in 113.6.5 OSGi R4 compendium.
+ *
+ * @param event The event to adapt.
+ */
+ public void serviceChanged(final ServiceEvent event)
+ {
+ final Dictionary properties = new Hashtable();
+
+ properties.put(EventConstants.EVENT, event);
+
+ properties.put(EventConstants.SERVICE, event
+ .getServiceReference());
+
+ final Object id = event.getServiceReference().getProperty(
+ EventConstants.SERVICE_ID);
+
+ if (null != id)
+ {
+ try
+ {
+ properties.put(EventConstants.SERVICE_ID, new Long(id
+ .toString()));
+ } catch (NumberFormatException ne)
+ {
+ // LOG and IGNORE
+ LogWrapper.getLogger().log(event.getServiceReference(),
+ LogWrapper.LOG_WARNING, "Exception parsing " +
+ EventConstants.SERVICE_ID + "=" + id, ne);
+ }
+ }
+
+ final Object pid = event.getServiceReference().getProperty(
+ EventConstants.SERVICE_PID);
+
+ if (null != pid)
+ {
+ properties.put(EventConstants.SERVICE_PID, pid.toString());
+ }
+
+ final Object objectClass = event.getServiceReference()
+ .getProperty(Constants.OBJECTCLASS);
+
+ if (null != objectClass)
+ {
+ if (objectClass instanceof String[])
+ {
+ properties.put(EventConstants.SERVICE_OBJECTCLASS,
+ objectClass);
+ }
+ else
+ {
+ properties.put(EventConstants.SERVICE_OBJECTCLASS,
+ new String[] { objectClass.toString() });
+ }
+ }
+
+ final StringBuffer topic = new StringBuffer(ServiceEvent.class
+ .getName().replace('.', '/')).append('/');
+
+ switch (event.getType())
+ {
+ case ServiceEvent.REGISTERED:
+ topic.append("REGISTERED");
+ break;
+ case ServiceEvent.MODIFIED:
+ topic.append("MODIFIED");
+ break;
+ case ServiceEvent.UNREGISTERING:
+ topic.append("UNREGISTERING");
+ break;
+ default:
+ return; // IGNORE
+ }
+
+ try {
+ m_admin.postEvent(new Event(topic.toString(), properties));
+ } catch(IllegalStateException e) {
+ // This is o.k. - indicates that we are stopped.
+ }
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/CacheThreadPool.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/CacheThreadPool.java
new file mode 100644
index 0000000..fd6e779
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/CacheThreadPool.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.dispatch;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.felix.eventadmin.impl.tasks.DeliverTask;
+import org.apache.felix.eventadmin.impl.tasks.DispatchTask;
+
+/**
+ * An implementation of a thread pool that uses a fixed number of cached threads
+ * but will spin-off new threads as needed. The underlying assumption is that
+ * threads that have been created more recently will be available sooner then older
+ * threads hence, once the pool size is reached older threads will be decoupled from
+ * the pool and the newly created are added to it.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+// TODO: The least recently used method deployed is rather a hack in this case
+// it really should be refactored into a plugable strategy. However, I believe
+// it to be the best strategy in this case.
+public class CacheThreadPool implements ThreadPool
+{
+ // The internal lock for this object used instead synchronized(this)
+ // Note that it is used by the pooled threads created by this pool too. This is
+ // the reason why it is not private. Don't use it from the outside.
+ final Object m_lock = new Object();
+
+ // The pooled threads
+ private final PooledThread[] m_pool;
+
+ // The least recently used index
+ private final List m_index;
+
+ // Is this pool closed i.e., do we not pool thread anymore?
+ private boolean m_closed = false;
+
+ /**
+ * The constructor of the pool. The given size will be used as the max number of
+ * pooled threads.
+ *
+ * @param size The max number of threads pooled at a given time.
+ */
+ public CacheThreadPool(final int size)
+ {
+ synchronized (m_lock)
+ {
+ m_pool = new PooledThread[size];
+
+ // We assume that a list is expanded once it reaches half of its capacity
+ // and it doesn't harm if the assumption is wrong.
+ m_index = new ArrayList(size + 1 + (size / 2));
+ }
+ }
+
+ /**
+ * Executes the task in a thread out of the pool or a new thread if no pooled
+ * thread is available. In case that the max size is reached the least recently
+ * used (i.e., the longest executing) thread in the pool is decoupled and a new
+ * one added to the pool that is used to execute the task.
+ *
+ * @param task The task to execute
+ * @param callback The callback associated with the task
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.ThreadPool#execute(DispatchTask, DeliverTask)
+ */
+ public void execute(final DispatchTask task, final DeliverTask callback)
+ {
+ // Note that we associate a callback with a task via the thread used to
+ // execute the task. In general, a free slot in the pool (i.e., m_pool[i] is
+ // null) can be used to set-up a new thread. Also note that we need to
+ // update the LRU index if we change the pool.
+ synchronized(m_lock)
+ {
+ if(m_closed)
+ {
+ // We are closed hence, spin-of a new thread for the new task.
+ final PooledThread result = new PooledThread();
+
+ // Set-up the thread and associate the task with the callback.
+ result.reset(task, callback);
+
+ // release the thread immediately since we don't pool anymore.
+ result.release();
+
+ return;
+ }
+
+ // Search in the pool for a free thread.
+ for (int i = 0; i < m_pool.length; i++)
+ {
+ // o.k. we found a free slot now set-up a new thread for it.
+ if (null == m_pool[i])
+ {
+ m_pool[i] = new PooledThread();
+
+ m_pool[i].reset(task, callback);
+
+ m_index.add(new Integer(i));
+
+ return;
+ }
+ else if (m_pool[i].available())
+ {
+ // we found a free thread now set it up.
+ m_pool[i].reset(task, callback);
+
+ final Integer idx = new Integer(i);
+
+ m_index.remove(idx);
+
+ m_index.add(idx);
+
+ return;
+ }
+ }
+
+ // The pool is full and no threads are available hence, spin-off a new
+ // thread and add it to the pool while decoupling the least recently used
+ // one. This assumes that older threads are likely to take longer to
+ // become available again then younger ones.
+ final int pos = ((Integer) m_index.remove(0)).intValue();
+
+ m_index.add(new Integer(pos));
+
+ m_pool[pos].release();
+
+ m_pool[pos] = new PooledThread();
+
+ m_pool[pos].reset(task, callback);
+ }
+ }
+
+ /**
+ * Look-up the callback associated with the task that the given thread is
+ * currently executing or return the default value that may be <tt>null</tt>.
+ *
+ * @param thread The thread that is currently executing the task for which to
+ * return the callback. In case the thread is not created by an instance of
+ * this class the default value will be returned.
+ * @param defaultCallback The value to return in case that the thread was not
+ * created by an instance of this class. May be <tt>null</tt>
+ * @return The callback associated with the given thread or the default value.
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.ThreadPool#getCallback(Thread, DeliverTask)
+ */
+ public DeliverTask getCallback(final Thread thread, final DeliverTask defaultCallback)
+ {
+ synchronized (m_lock)
+ {
+ if (thread instanceof PooledThread)
+ {
+ return ((PooledThread) thread).getCallback();
+ }
+
+ return defaultCallback;
+ }
+ }
+
+ /**
+ * Look-up the task that the given thread is currently executing or return the
+ * default value that may be <tt>null</tt> in case that the thread has not been
+ * created by an instance of this class.
+ *
+ * @param thread The thread whose currently executed task should be returned.
+ * @param defaultTask The default value to be returned in case that the thread
+ * was not created by this instance or doesn't currently has a task. May be
+ * <tt>null</tt>
+ * @return The task the given thread is currently executing or the defaultTask
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.ThreadPool#getTask(Thread, DispatchTask)
+ */
+ public DispatchTask getTask(Thread thread, DispatchTask defaultTask)
+ {
+ synchronized (m_lock)
+ {
+ if (thread instanceof PooledThread)
+ {
+ return ((PooledThread) thread).getTask();
+ }
+
+ return defaultTask;
+ }
+ }
+
+ /**
+ * Close the pool i.e, stop pooling threads. Note that subsequently, task will
+ * still be executed but no pooling is taking place anymore.
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.ThreadPool#close()
+ */
+ public void close()
+ {
+ synchronized (m_lock)
+ {
+ // We are closed hence, decouple all threads from the pool
+ for (int i = 0; i < m_pool.length; i++)
+ {
+ if (null != m_pool[i])
+ {
+ m_pool[i].release();
+
+ m_pool[i] = null;
+ }
+ }
+
+ m_closed = true;
+ }
+ }
+
+ /*
+ * The threads created by this pool. A PooledThread blocks until it gets a new
+ * task from the pool or is released. Additionally, it is used to associate
+ * the task it currently runs with its callback.
+ */
+ private class PooledThread extends Thread
+ {
+ // The current task or null if none
+ private DispatchTask m_runnable = null;
+
+ // The callback associated with the current task
+ private DeliverTask m_callback = null;
+
+ // Is this thread decoupled from the pool (i.e, may cease to exists once its
+ // current task is finished)?
+ private boolean m_released = false;
+
+ /*
+ * This will set-up the thread as a daemon and start it too. No need to call
+ * its start method explicitly
+ */
+ PooledThread()
+ {
+ setDaemon(true);
+
+ start();
+ }
+
+
+ /**
+ * Call next() in a loop until next() returns null indicating that we are
+ * done (i.e., decoupled from the pool) and may cease to exist.
+ */
+ public void run()
+ {
+ for (Runnable next = next(); null != next; next = next())
+ {
+ next.run();
+
+ synchronized (m_lock)
+ {
+ m_runnable = null;
+ }
+ }
+ }
+
+ /*
+ * Block until a new task is available or we are decoupled from the pool.
+ * This will return the next task or null if we are decoupled from the pool
+ */
+ private DispatchTask next()
+ {
+ synchronized (m_lock)
+ {
+ while (null == m_runnable)
+ {
+ if (m_released)
+ {
+ return null;
+ }
+
+ try
+ {
+ m_lock.wait();
+ } catch (InterruptedException e)
+ {
+ // Not needed
+ }
+ }
+
+ return m_runnable;
+ }
+ }
+
+ /*
+ * Set-up the thread for the next task
+ */
+ void reset(final DispatchTask next, final DeliverTask callback)
+ {
+ synchronized (m_lock)
+ {
+ m_runnable = next;
+ m_callback = callback;
+ m_lock.notifyAll();
+ }
+ }
+
+ /*
+ * Return the callback associated with the current task
+ */
+ DeliverTask getCallback()
+ {
+ synchronized (m_lock)
+ {
+ return m_callback;
+ }
+ }
+
+ /*
+ * Return whether this thread is available (i.e., has no task and has not
+ * been released) or not.
+ */
+ boolean available()
+ {
+ synchronized (m_lock)
+ {
+ return (null == m_runnable) && (!m_released);
+ }
+ }
+
+ /*
+ * Return the current task or null if none
+ */
+ DispatchTask getTask()
+ {
+ synchronized (m_lock)
+ {
+ return m_runnable;
+ }
+ }
+
+ /*
+ * Decouple this thread from the pool
+ */
+ void release()
+ {
+ synchronized (m_lock)
+ {
+ m_released = true;
+
+ m_lock.notifyAll();
+ }
+ }
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/DelayScheduler.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/DelayScheduler.java
new file mode 100644
index 0000000..866c207
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/DelayScheduler.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.dispatch;
+
+import java.util.Date;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * A simple delay scheduler that schedules tasks based on a fixed delay. Possible
+ * nice values are subtracted from this delay where appropriate. Note that this
+ * class uses a <tt>java.util.Timer</tt> internally that is set to be a daemon hence,
+ * allows to shutdown the vm regardless but can not be stopped. The spec says that
+ * a <tt>java.util.Timer</tt> without a reference to itself should go away eventually
+ * but no guaranties are given. It follows that once the bundle is stopped all
+ * references to instances of this class should be released and this in turn will
+ * allow that the timer thread goes away eventually, but this may take an arbitrary
+ * amount of time.
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.Scheduler
+ * @see java.util.Timer
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class DelayScheduler implements Scheduler
+{
+ // The timer used for scheduling. Note that it will not be stopped by but
+ // by the vm once all references to this instance are gone (at least eventually).
+ private final Timer m_timer = new Timer(true);
+
+ private final int m_delay;
+
+ /**
+ * The constructor of the scheduler. The scheduler will use the given delay to
+ * schedule tasks accordingly.
+ *
+ * @param delay The delay in milliseconds before a task is executed
+ */
+ public DelayScheduler(final int delay)
+ {
+ m_delay = delay;
+ }
+
+ /**
+ * Schedule the task to execute after the given delay.
+ *
+ * @param task The task to schedule for execution.
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.Scheduler#schedule(java.lang.Runnable)
+ */
+ public void schedule(final Runnable task)
+ {
+ scheduleTaskWithDelay(task, m_delay);
+ }
+
+ /**
+ * Schedule the task to execute after the given delay minus the nice.
+ *
+ * @param task The task to schedule for execution after delay minus nice
+ * @param nice The time to subtract from the delay.
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.Scheduler#schedule(java.lang.Runnable, int)
+ */
+ public void schedule(final Runnable task, int nice)
+ {
+ scheduleTaskWithDelay(task, m_delay - nice);
+ }
+
+ /*
+ * This method creates a new TimerTask as a wrapper around the given task
+ * and calls the m_timer.schedule method with it and the current time plus the
+ * delay.
+ */
+ private void scheduleTaskWithDelay(final Runnable task, final int delay)
+ {
+ m_timer.schedule(new TimerTask()
+ {
+ public void run()
+ {
+ task.run();
+ }
+ }, new Date(System.currentTimeMillis() + delay));
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/Scheduler.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/Scheduler.java
new file mode 100644
index 0000000..8bf918b
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/Scheduler.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.dispatch;
+
+/**
+ * A simple scheduler that accepts a task and schedules its for execution at
+ * its own discretion (i.e., the behavior of the actual implementor). The only
+ * possible hint is a nice value that should be subtracted from any fixed scheduling
+ * interval. Additionally, a null object is provided that can be used to disable
+ * scheduled execution.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public interface Scheduler
+{
+ /**
+ * This is a null object that can be used in case no scheduling is needed. In
+ * other words tasks given to this scheduler are never executed.
+ */
+ public final Scheduler NULL_SCHEDULER = new Scheduler(){
+ /**
+ * This is a null object hence, this method does nothing.
+ *
+ * @param task A task that will never be run.
+ */
+ public void schedule(final Runnable task)
+ {
+ // This is a null object hence we don't do nothing.
+ }
+
+ /**
+ * This is a null object hence, this method does nothing.
+ *
+ * @param task A task that will never be run.
+ * @param nice A nice value that will never be used.
+ */
+ public void schedule(final Runnable task, final int nice)
+ {
+ // This is a null object hence we don't do nothing.
+ }
+ };
+
+ /**
+ * Schedule the given task for execution at a later time based on the behavior
+ * of the actual implementor of this interface. Note that this may mean that
+ * the task is never executed.
+ *
+ * @param task The task to schedule for execution.
+ */
+ public void schedule(final Runnable task);
+
+ /**
+ * Schedule the given task for execution at a later time based on the behavior
+ * of the actual implementor of this interface. Note that this may mean that
+ * the task is never executed. The nice value should be subtracted from any fixed
+ * scheduling interval.
+ *
+ * @param task The task to schedule for execution.
+ * @param nice A value to subtract from any fixed scheduling interval.
+ */
+ public void schedule(final Runnable task, final int nice);
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/TaskHandler.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/TaskHandler.java
new file mode 100644
index 0000000..b3f3a04
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/TaskHandler.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.dispatch;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.felix.eventadmin.impl.tasks.HandlerTask;
+
+/**
+ * This class implements the <tt>TaskQueue</tt> and the <tt>TaskProducer</tt>
+ * interface. It makes the tasks added via the queue interface available via the
+ * producer interface until the queue is closed and the producer returns
+ * <tt>null</tt>.
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.TaskQueue
+ * @see org.apache.felix.eventadmin.impl.dispatch.TaskProducer
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class TaskHandler implements TaskQueue, TaskProducer
+{
+ // The queue that is used as a lock as well
+ private final List m_queue = new ArrayList();
+
+ // Are we closed?
+ private boolean m_closed = false;
+
+ /**
+ * Append the tasks to this queue in one atomic operation while preserving their
+ * order.
+ *
+ * @param tasks The tasks to append to this queue
+ *
+ * @throws IllegalStateException in case that this queue is already closed
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.TaskQueue#append(HandlerTask[])
+ */
+ public void append(final HandlerTask[] tasks)
+ {
+ synchronized (m_queue)
+ {
+ if(m_closed)
+ {
+ throw new IllegalArgumentException("Queue is closed");
+ }
+
+ for (int i = 0; i < tasks.length; i++)
+ {
+ m_queue.add(tasks[i]);
+ }
+
+ if(!m_queue.isEmpty())
+ {
+ m_queue.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Push the tasks to this queue in one atomic operation while preserving their
+ * order.
+ *
+ * @param tasks The tasks to push to the front of this queue.
+ *
+ * @throws IllegalStateException in case that this queue is already closed
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.TaskQueue#push(HandlerTask[])
+ */
+ public void push(final HandlerTask[] tasks)
+ {
+ synchronized (m_queue)
+ {
+ if(m_closed)
+ {
+ throw new IllegalArgumentException("Queue is closed");
+ }
+
+ for (int i = tasks.length -1; i >= 0; i--)
+ {
+ m_queue.add(0, tasks[i]);
+ }
+
+ if(!m_queue.isEmpty())
+ {
+ m_queue.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Close the queue.
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.TaskQueue#close()
+ */
+ public void close()
+ {
+ synchronized(m_queue)
+ {
+ m_closed = true;
+
+ m_queue.notifyAll();
+ }
+ }
+
+ /**
+ * Block until a new task is ready and is returned or no more tasks will be
+ * returned.
+ *
+ * @return The next task or <tt>null</tt> if no more tasks will be produced
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.TaskProducer#next()
+ */
+ public HandlerTask next()
+ {
+ synchronized (m_queue)
+ {
+ while(!m_closed && m_queue.isEmpty())
+ {
+ try
+ {
+ m_queue.wait();
+ } catch (InterruptedException e)
+ {
+ // Not needed
+ }
+ }
+
+ if(!m_queue.isEmpty())
+ {
+ return (HandlerTask) m_queue.remove(0);
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/TaskProducer.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/TaskProducer.java
new file mode 100644
index 0000000..f1c8acb
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/TaskProducer.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.dispatch;
+
+import org.apache.felix.eventadmin.impl.tasks.HandlerTask;
+
+/**
+ * Instances of this interface will deliver new tasks as soon as they are available
+ * while blocking in the <tt>next()</tt> call until then. Unless there won't be any
+ * more tasks in which case <tt>null</tt> is returned.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public interface TaskProducer
+{
+ /**
+ * Block until a new task is ready and is returned or no more tasks will be
+ * returned.
+ *
+ * @return The next task or <tt>null</tt> if no more tasks will be produced
+ */
+ public HandlerTask next();
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/TaskQueue.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/TaskQueue.java
new file mode 100644
index 0000000..c75a692
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/TaskQueue.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.dispatch;
+
+import org.apache.felix.eventadmin.impl.tasks.HandlerTask;
+
+/**
+ * This is the interface for a simple queue that allows to append or push arrays
+ * of tasks to it. The elements of such an array are added atomically (i.e, they
+ * are in the same order one after the other in the queue) either at the end or the
+ * front of the queue. Additionally, the queue can be closed.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public interface TaskQueue
+{
+ /**
+ * Append the tasks to this queue in one atomic operation while preserving their
+ * order.
+ *
+ * @param tasks The tasks to append to this queue
+ *
+ * @throws IllegalStateException in case that this queue is already closed
+ */
+ public void append(HandlerTask[] tasks);
+
+ /**
+ * Push the tasks to this queue in one atomic operation while preserving their
+ * order.
+ *
+ * @param tasks The tasks to push to the front of this queue.
+ *
+ * @throws IllegalStateException in case that this queue is already closed
+ */
+ public void push(HandlerTask[] tasks);
+
+ /**
+ * Close the queue.
+ */
+ public void close();
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/ThreadPool.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/ThreadPool.java
new file mode 100644
index 0000000..b496fbf
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/dispatch/ThreadPool.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.dispatch;
+
+import org.apache.felix.eventadmin.impl.tasks.DeliverTask;
+import org.apache.felix.eventadmin.impl.tasks.DispatchTask;
+
+/**
+ * A ThreadPool interface that allows to execute tasks using pooled threads in order
+ * to ease the thread creation overhead and additionally, to associate a callback
+ * with the thread that executes the task. Subsequently, the callback for a given
+ * thread can be asked from instances of this class. Finally, the currently executed
+ * task of a thread created by this pool can be retrieved as well. The look-up
+ * methods accept plain thread objects and will return given default values in case
+ * that the specific threads have not been created by this pool. Note that a closed
+ * pool should still execute new tasks but stop pooling threads.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public interface ThreadPool
+{
+ /**
+ * Execute the task in a free thread or create a new one. The given callback
+ * will be associated with the executing thread as long as it is executed.
+ *
+ * @param task The task to execute
+ * @param callback The callback that will be associated with the executing thread
+ * or <tt>null</tt> if none.
+ */
+ public void execute(final DispatchTask task, final DeliverTask callback);
+
+ /**
+ * Look-up the callback associated with the task that the given thread is
+ * currently executing or return the default value that may be <tt>null</tt>.
+ *
+ * @param thread The thread that is currently executing the task for which to
+ * return the callback. In case the thread is not created by an instance of
+ * this class the default value will be returned.
+ * @param defaultCallback The value to return in case that the thread was not
+ * created by an instance of this class. May be <tt>null</tt>
+ * @return The callback associated with the given thread or the default value.
+ */
+ public DeliverTask getCallback(final Thread thread,
+ final DeliverTask defaultCallback);
+
+ /**
+ * Look-up the task that the given thread is currently executing or return the
+ * default value that may be <tt>null</tt> in case that the thread has not been
+ * created by an instance of this class.
+ *
+ * @param thread The thread whose currently executed task should be returned.
+ * @param defaultTask The default value to be returned in case that the thread
+ * was not created by this instance or doesn't currently has a task. May be
+ * <tt>null</tt>
+ * @return The task the given thread is currently executing or the defaultTask
+ */
+ public DispatchTask getTask(final Thread thread, final DispatchTask defaultTask);
+
+ /**
+ * Close the pool i.e, stop pooling threads. Note that subsequently, task will
+ * still be executed but no pooling is taking place anymore.
+ */
+ public void close();
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/BlackList.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/BlackList.java
new file mode 100644
index 0000000..3a11bf7
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/BlackList.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.handler;
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * This interface represents a simple set that allows to add service references
+ * and lookup whether a given reference is in the list. Note that implementations
+ * of this interface may do additional service reference life-cycle related
+ * clean-up actions like removing references that point to unregistered services.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public interface BlackList
+{
+ /**
+ * Add a service to this blacklist.
+ *
+ * @param ref The reference of the service that is blacklisted
+ */
+ public void add(final ServiceReference ref);
+
+ /**
+ * Lookup whether a given service is blacklisted.
+ *
+ * @param ref The reference of the service
+ *
+ * @return <tt>true</tt> in case that the service reference has been blacklisted,
+ * <tt>false</tt> otherwise.
+ */
+ public boolean contains(final ServiceReference ref);
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/BlacklistingHandlerTasks.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/BlacklistingHandlerTasks.java
new file mode 100644
index 0000000..9f50b3a
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/BlacklistingHandlerTasks.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.handler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.felix.eventadmin.impl.security.TopicPermissions;
+import org.apache.felix.eventadmin.impl.tasks.HandlerTask;
+import org.apache.felix.eventadmin.impl.tasks.HandlerTaskImpl;
+import org.apache.felix.eventadmin.impl.util.LogWrapper;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+
+/**
+ * This class is an implementation of the HandlerTasks interface that does provide
+ * blacklisting of event handlers. Furthermore, handlers are determined from the
+ * framework on any call to <tt>createHandlerTasks()</tt> hence, there is no
+ * book-keeping of <tt>EventHandler</tt> services while they come and go but a
+ * query for each sent event. In order to do this, an ldap-filter is created that
+ * will match applicable <tt>EventHandler</tt> references. In order to ease some of
+ * the overhead pains of this approach some light caching is going on.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class BlacklistingHandlerTasks implements HandlerTasks
+{
+ // The blacklist that holds blacklisted event handler service references
+ private final BlackList m_blackList;
+
+ // The context of the bundle used to get the actual event handler services
+ private final BundleContext m_context;
+
+ // Used to create the filters that can determine applicable event handlers for
+ // a given event
+ private final TopicHandlerFilters m_topicHandlerFilters;
+
+ // Used to create the filters that are used to determine whether an applicable
+ // event handler is interested in a particular event
+ private final Filters m_filters;
+
+ // Used to create and possibly cache topic permissions
+ private final TopicPermissions m_topicPermissions;
+
+ /**
+ * The constructor of the factory.
+ *
+ * @param context The context of the bundle
+ * @param blackList The set to use for keeping track of blacklisted references
+ * @param topicHandlerFilters The factory for topic handler filters
+ * @param filters The factory for <tt>Filter</tt> objects
+ * @param topicPermissions The factory for permission objects of type PUBLISH
+ */
+ public BlacklistingHandlerTasks(final BundleContext context,
+ final BlackList blackList,
+ final TopicHandlerFilters topicHandlerFilters, final Filters filters,
+ final TopicPermissions topicPermissions)
+ {
+ checkNull(context, "Context");
+ checkNull(blackList, "BlackList");
+ checkNull(topicHandlerFilters, "TopicHandlerFilters");
+ checkNull(filters, "Filters");
+ checkNull(topicPermissions, "TopicPermissions");
+
+ m_context = context;
+
+ m_blackList = blackList;
+
+ m_topicHandlerFilters = topicHandlerFilters;
+
+ m_filters = filters;
+
+ m_topicPermissions = topicPermissions;
+ }
+
+ /**
+ * Create the handler tasks for the event. All matching event handlers are
+ * determined and delivery tasks for them returned.
+ *
+ * @param event The event for which' handlers delivery tasks must be created
+ *
+ * @return A delivery task for each handler that matches the given event
+ *
+ * @see org.apache.felix.eventadmin.impl.handler.HandlerTasks#createHandlerTasks(org.osgi.service.event.Event)
+ */
+ public HandlerTask[] createHandlerTasks(final Event event)
+ {
+ final List result = new ArrayList();
+
+ ServiceReference[] handlerRefs = new ServiceReference[0];
+
+ try
+ {
+ handlerRefs = m_context.getServiceReferences(EventHandler.class
+ .getName(), m_topicHandlerFilters.createFilterForTopic(event
+ .getTopic()));
+ } catch (InvalidSyntaxException e)
+ {
+ LogWrapper.getLogger().log(LogWrapper.LOG_WARNING,
+ "Invalid EVENT_TOPIC [" + event.getTopic() + "]", e);
+ }
+
+ if(null == handlerRefs)
+ {
+ handlerRefs = new ServiceReference[0];
+ }
+
+ for (int i = 0; i < handlerRefs.length; i++)
+ {
+ if (!m_blackList.contains(handlerRefs[i])
+ && handlerRefs[i].getBundle().hasPermission(
+ m_topicPermissions.createTopicPermission(event.getTopic())))
+ {
+ try
+ {
+ if (event.matches(m_filters.createFilter(
+ (String) handlerRefs[i]
+ .getProperty(EventConstants.EVENT_FILTER),
+ Filters.TRUE_FILTER)))
+ {
+ result.add(new HandlerTaskImpl(handlerRefs[i],
+ event, this));
+ }
+ } catch (InvalidSyntaxException e)
+ {
+ LogWrapper.getLogger().log(
+ handlerRefs[i],
+ LogWrapper.LOG_WARNING,
+ "Invalid EVENT_FILTER - Blacklisting ServiceReference ["
+ + handlerRefs[i] + " | Bundle("
+ + handlerRefs[i].getBundle() + ")]", e);
+
+ m_blackList.add(handlerRefs[i]);
+ }
+ }
+ }
+
+ return (HandlerTaskImpl[]) result
+ .toArray(new HandlerTaskImpl[result.size()]);
+ }
+
+ /**
+ * Blacklist the given service reference. This is a private method and only
+ * public due to its usage in a friend class.
+ *
+ * @param handlerRef The service reference to blacklist
+ */
+ public void blackList(final ServiceReference handlerRef)
+ {
+ m_blackList.add(handlerRef);
+
+ LogWrapper.getLogger().log(
+ LogWrapper.LOG_WARNING,
+ "Blacklisting ServiceReference [" + handlerRef + " | Bundle("
+ + handlerRef.getBundle() + ")] due to timeout!");
+ }
+
+ /**
+ * Get the real EventHandler service for the handlerRef from the context in case
+ * the ref is not blacklisted and the service is not unregistered. The
+ * NullEventHandler object is returned otherwise. This is a private method and
+ * only public due to its usage in a friend class.
+ *
+ * @param handlerRef The service reference for which to get its service
+ * @return The service of the reference or a null object if the service is
+ * unregistered
+ */
+ public EventHandler getEventHandler(final ServiceReference handlerRef)
+ {
+ final Object result = (m_blackList.contains(handlerRef)) ? null
+ : m_context.getService(handlerRef);
+
+ return (EventHandler) ((null != result) ? result : m_nullEventHandler);
+ }
+
+ /*
+ * This is a null object that is supposed to do nothing. This is used once an
+ * EventHandler is requested for a service reference that is either stale
+ * (i.e., unregistered) or blacklisted
+ */
+ private final EventHandler m_nullEventHandler = new EventHandler()
+ {
+ /**
+ * This is a null object that is supposed to do nothing at this point.
+ *
+ * @param event an event that is not used
+ */
+ public void handleEvent(final Event event)
+ {
+ // This is a null object that is supposed to do nothing at this
+ // point. This is used once a EventHandler is requested for a
+ // servicereference that is either stale (i.e., unregistered) or
+ // blacklisted.
+ }
+ };
+
+ /*
+ * This is a utility method that will throw a <tt>NullPointerException</tt>
+ * in case that the given object is null. The message will be of the form name +
+ * may not be null.
+ */
+ private void checkNull(final Object object, final String name)
+ {
+ if(null == object)
+ {
+ throw new NullPointerException(name + " may not be null");
+ }
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/CacheFilters.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/CacheFilters.java
new file mode 100644
index 0000000..883feb2
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/CacheFilters.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.handler;
+
+import org.apache.felix.eventadmin.impl.util.CacheMap;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+
+/**
+ * This is an implementation of the <tt>Filters</tt> factory that uses a cache in
+ * order to speed-up filter creation.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class CacheFilters implements Filters
+{
+ // The cache to use
+ private final CacheMap m_cache;
+
+ // The context of the bundle used to create the Filter objects
+ private final BundleContext m_context;
+
+ /**
+ * The constructor of this factory. The cache is used to speed-up filter
+ * creation.
+ *
+ * @param cache The cache to use
+ * @param context The context of the bundle used to create the <tt>Filter</tt>
+ * objects
+ */
+ public CacheFilters(final CacheMap cache, final BundleContext context)
+ {
+ if(null == cache)
+ {
+ throw new NullPointerException("Cache may not be null");
+ }
+
+ if(null == context)
+ {
+ throw new NullPointerException("Context may not be null");
+ }
+
+ m_cache = cache;
+
+ m_context = context;
+ }
+
+ /**
+ * Create a filter for the given filter string or return the nullFilter in case
+ * the string is <tt>null</tt>.
+ *
+ * @param filter The filter as a string
+ * @param nullFilter The default value to return if filter is <tt>null</tt>
+ * @return The <tt>Filter</tt> of the filter string or the nullFilter if the
+ * filter string was <tt>null</tt>
+ * @throws InvalidSyntaxException if <tt>BundleContext.createFilter()</tt>
+ * throws an <tt>InvalidSyntaxException</tt>
+ *
+ * @see org.apache.felix.eventadmin.impl.handler.Filters#createFilter(java.lang.String, org.osgi.framework.Filter)
+ */
+ public Filter createFilter(String filter, Filter nullFilter)
+ throws InvalidSyntaxException
+ {
+ Filter result = (Filter) ((null != filter) ? m_cache.get(filter)
+ : nullFilter);
+
+ if (null == result)
+ {
+ result = m_context.createFilter(filter);
+
+ m_cache.add(filter, result);
+ }
+
+ return result;
+ }
+
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/CacheTopicHandlerFilters.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/CacheTopicHandlerFilters.java
new file mode 100644
index 0000000..de30ac2
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/CacheTopicHandlerFilters.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.handler;
+
+import org.apache.felix.eventadmin.impl.util.CacheMap;
+import org.osgi.service.event.EventConstants;
+
+/**
+ * The factory for <tt>EventHandler</tt> filters based on a certain topic. This
+ * implementation uses a cache to speed-up filter creation.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class CacheTopicHandlerFilters implements TopicHandlerFilters
+{
+ // The cache
+ private final CacheMap m_cache;
+
+ private final char[] m_keyChars = EventConstants.EVENT_TOPIC.toCharArray();
+
+ private final char[] m_filterStart;
+
+ /**
+ * The constructor of the filter factory.
+ *
+ * @param cache The cache to use in order to speed-up filter creation.
+ *
+ * @param requireTopic Include handlers that do not provide a topic
+ */
+ public CacheTopicHandlerFilters(final CacheMap cache, final boolean requireTopic)
+ {
+ if(null == cache)
+ {
+ throw new NullPointerException("Cache may not be null");
+ }
+
+ m_cache = cache;
+
+ m_filterStart = ("(|" +
+ ((requireTopic) ? "" : "(!(" + new String(m_keyChars) + "=*))") +
+ "(" + new String(m_keyChars) + "=\\*)(" + new String(m_keyChars) +
+ "=").toCharArray();
+ }
+
+ /**
+ * Create a filter that will match all <tt>EventHandler</tt> services that match
+ * the given topic.
+ *
+ * @param topic The topic to match
+ *
+ * @return A filter that will match all <tt>EventHandler</tt> services for
+ * the given topic.
+ *
+ * @see org.apache.felix.eventadmin.impl.handler.TopicHandlerFilters#createFilterForTopic(java.lang.String)
+ */
+ public String createFilterForTopic(String topic)
+ {
+ // build the ldap-query - as a simple example:
+ // topic=org/apache/felix/TEST
+ // result = (|(topic=\*)(topic=org/\*)(topic=org/apache/\*)
+ // (topic=org/apache/felix/\*)(topic=org/apache/felix/TEST))
+ String result = (String) m_cache.get(topic);
+
+ if(null == result)
+ {
+ char[] topicChars = topic.toCharArray();
+
+ final StringBuffer filter = new StringBuffer(topicChars.length
+ * topicChars.length);
+
+ filter.append(m_filterStart);
+
+ for (int i = 0; i < topicChars.length; i++)
+ {
+ if ('/' == topicChars[i])
+ {
+ filter.append('/').append('\\').append('*').append(')');
+
+ filter.append('(').append(m_keyChars).append('=').append(
+ topicChars, 0, i + 1);
+ }
+ else
+ {
+ filter.append(topicChars[i]);
+ }
+ }
+
+ filter.append(')').append(')');
+
+ result = filter.toString();
+
+ m_cache.add(topic, result);
+ }
+
+ return result;
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/CleanBlackList.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/CleanBlackList.java
new file mode 100644
index 0000000..1c103ad
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/CleanBlackList.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.handler;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * This class implements a <tt>BlackList</tt> that removes references to unregistered
+ * services automatically.
+ *
+ * @see org.apache.felix.eventadmin.impl.handler.BlackList
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class CleanBlackList implements BlackList
+{
+ // This set removes stale (i.e., unregistered) references on any call to contains
+ private final Set m_blackList = Collections.synchronizedSet(new HashSet()
+ {
+ public boolean contains(final Object object)
+ {
+ for (Iterator iter = super.iterator(); iter.hasNext();)
+ {
+ final ServiceReference ref = (ServiceReference) iter.next();
+
+ if (null == ref.getBundle())
+ {
+ iter.remove();
+ }
+ }
+
+ return super.contains(object);
+ }
+ });
+
+ /**
+ * Add a service to this blacklist.
+ *
+ * @param ref The reference of the service that is blacklisted
+ *
+ * @see org.apache.felix.eventadmin.impl.handler.BlackList#add(org.osgi.framework.ServiceReference)
+ */
+ public void add(final ServiceReference ref)
+ {
+ m_blackList.add(ref);
+ }
+
+ /**
+ * Lookup whether a given service is blacklisted.
+ *
+ * @param ref The reference of the service
+ *
+ * @return <tt>true</tt> in case that the service reference has been blacklisted,
+ * <tt>false</tt> otherwise.
+ *
+ * @see org.apache.felix.eventadmin.impl.handler.BlackList#contains(org.osgi.framework.ServiceReference)
+ */
+ public boolean contains(final ServiceReference ref)
+ {
+ return m_blackList.contains(ref);
+ }
+
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/Filters.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/Filters.java
new file mode 100644
index 0000000..fea183f
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/Filters.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.handler;
+
+import java.util.Dictionary;
+
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * The factory for <tt>Filter</tt> objects. Additionally, two null filter objects
+ * are provided that either always return <tt>true</tt> or <tt>false</tt>,
+ * respectively.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public interface Filters
+{
+ /**
+ * A null filter object that matches any given service reference.
+ */
+ public static final Filter TRUE_FILTER = new Filter()
+ {
+
+ /**
+ * This is a null object that always returns <tt>true</tt>.
+ *
+ * @param reference An unused service reference
+ * @return <tt>true</tt>
+ */
+ public boolean match(final ServiceReference reference)
+ {
+ return true;
+ }
+
+ /**
+ * This is a null object that always returns <tt>true</tt>.
+ *
+ * @param dictionary An unused dictionary
+ * @return <tt>true</tt>
+ */
+ public boolean match(final Dictionary dictionary)
+ {
+ return true;
+ }
+
+ /**
+ * This is a null object that always returns <tt>true</tt>.
+ *
+ * @param dictionary An unused dictionary.
+ * @return <tt>true</tt>
+ */
+ public boolean matchCase(final Dictionary dictionary)
+ {
+ return true;
+ }
+ };
+
+ /**
+ * A null filter object that does not match any given service reference.
+ */
+ public static final Filter FALSE_FILTER = new Filter()
+ {
+
+ /**
+ * This is a null object that always returns <tt>false</tt>.
+ *
+ * @param reference An unused reference.
+ * @return <tt>false</tt>
+ */
+ public boolean match(final ServiceReference reference)
+ {
+ return false;
+ }
+
+ /**
+ * This is a null object that always returns <tt>false</tt>.
+ *
+ * @param dictionary An unused dictionary
+ * @return <tt>false</tt>
+ */
+ public boolean match(final Dictionary dictionary)
+ {
+ return false;
+ }
+
+ /**
+ * This is a null object that always returns <tt>false</tt>.
+ *
+ * @param dictionary An unused dictionary.
+ * @return <tt>false</tt>
+ */
+ public boolean matchCase(final Dictionary dictionary)
+ {
+ return false;
+ }
+ };
+
+ /**
+ * Create a filter for the given filter string or return the nullFilter in case
+ * the string is <tt>null</tt>.
+ *
+ * @param filter The filter as a string
+ * @param nullFilter The default value to return if filter is <tt>null</tt>
+ * @return The <tt>Filter</tt> of the filter string or the nullFilter if the
+ * filter string was null
+ * @throws InvalidSyntaxException if <tt>BundleContext.createFilter()</tt>
+ * throws an <tt>InvalidSyntaxException</tt>
+ */
+ public Filter createFilter(final String filter, final Filter nullFilter)
+ throws InvalidSyntaxException;
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/HandlerTasks.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/HandlerTasks.java
new file mode 100644
index 0000000..f9e2fd2
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/HandlerTasks.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.handler;
+
+import org.apache.felix.eventadmin.impl.tasks.HandlerTask;
+import org.osgi.service.event.Event;
+
+/**
+ * The factory for event handler tasks. Implementations of this interface can be
+ * used to create tasks that handle the delivery of events to event handlers.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public interface HandlerTasks
+{
+ /**
+ * Create the handler tasks for the event. All matching event handlers must
+ * be determined and delivery tasks for them returned.
+ *
+ * @param event The event for which' handlers delivery tasks must be created
+ *
+ * @return A delivery task for each handler that matches the given event
+ */
+ public HandlerTask[] createHandlerTasks(final Event event);
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/TopicHandlerFilters.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/TopicHandlerFilters.java
new file mode 100644
index 0000000..0177185
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/handler/TopicHandlerFilters.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.handler;
+
+/**
+ * The factory for <tt>EventHandler</tt> filters based on a certain topic.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public interface TopicHandlerFilters
+{
+ /**
+ * Create a filter that will match all <tt>EventHandler</tt> services that match
+ * the given topic.
+ *
+ * @param topic The topic to match
+ *
+ * @return A filter that will match all <tt>EventHandler</tt> services for
+ * the given topic.
+ */
+ public String createFilterForTopic(final String topic);
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/security/CacheTopicPermissions.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/security/CacheTopicPermissions.java
new file mode 100644
index 0000000..8e6620d
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/security/CacheTopicPermissions.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.security;
+
+import org.apache.felix.eventadmin.impl.util.CacheMap;
+
+/**
+ * An implementation of the <tt>TopicPermissions</tt> factory that uses a given
+ * cache in order to speed-up topic permission creation. Note that a
+ * <tt>java.lang.Object</tt> is returned in case creating a new TopicPermission
+ * fails. This assumes that Bundle.hasPermission is used in order to evaluate the
+ * created Permission which in turn will return true if security is not supported
+ * by the framework. Otherwise, it will return false due to receiving something that
+ * is not a subclass of <tt>java.lang.SecurityPermission</tt> hence, this combination
+ * ensures that access is granted in case a topic permission could not be created due
+ * to missing security support by the framework.
+ *
+ * @see org.apache.felix.eventadmin.impl.security.TopicPermissions
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class CacheTopicPermissions implements TopicPermissions
+{
+ // The cache used
+ private final CacheMap m_cache;
+
+ // The type of the permissions created
+ private final String m_type;
+
+ /**
+ * The constructor of this permission factory. The given cache will be used to
+ * speed-up permission creation and the created permissions will be of the given
+ * type (i.e., PUBLISH or SUBSCRIBE).
+ *
+ * @param cache The cache to be used
+ * @param type The type that created permissions will be of (i.e, PUBLISH or
+ * SUBSCRIBE)
+ *
+ * @see org.apache.felix.eventadmin.impl.security.TopicPermissions
+ * @see org.osgi.service.event.TopicPermission#PUBLISH
+ * @see org.osgi.service.event.TopicPermission#SUBSCRIBE
+ */
+ public CacheTopicPermissions(final CacheMap cache, final String type)
+ {
+ checkNull(cache, "CacheMap");
+ checkNull(type, "Type");
+
+ if(!org.osgi.service.event.TopicPermission.PUBLISH.equals(type) &&
+ !org.osgi.service.event.TopicPermission.SUBSCRIBE.equals(type))
+ {
+ throw new IllegalArgumentException(
+ "Type must be either PUBLISH or SUBSCRIBE");
+ }
+
+ m_cache = cache;
+
+ m_type = type;
+ }
+
+ /**
+ * Returns the type of the permissions created by this factory.
+ *
+ * @return The type of the permissions created by this factory
+ *
+ * @see org.apache.felix.eventadmin.impl.security.TopicPermissions#getType()
+ * @see org.osgi.service.event.TopicPermission#PUBLISH
+ * @see org.osgi.service.event.TopicPermission#SUBSCRIBE
+ */
+ public String getType()
+ {
+ return m_type;
+ }
+
+ /**
+ * Creates a <tt>TopicPermission</tt> for the given topic and the type of this
+ * factory (i.e., PUBLISH or SUBSCRIBE). Note that a
+ * <tt>java.lang.Object</tt> is returned in case creating a new TopicPermission
+ * fails. This assumes that Bundle.hasPermission is used in order to evaluate the
+ * created Permission which in turn will return true if security is not supported
+ * by the framework. Otherwise, it will return false due to receiving something
+ * that is not a subclass of <tt>java.lang.SecurityPermission</tt> hence, this
+ * combination ensures that access is granted in case a topic permission could
+ * not be created due to missing security support by the framework.
+ *
+ * @param topic The target topic
+ *
+ * @return The created permission or a <tt>java.lang.Object</tt> in case the
+ * permission could not be created.
+ *
+ * @see org.apache.felix.eventadmin.impl.security.TopicPermissions#createTopicPermission(String)
+ * @see org.osgi.service.event.TopicPermission
+ */
+ public Object createTopicPermission(final String topic)
+ {
+ Object result = m_cache.get(topic);
+
+ if(null == result)
+ {
+ try
+ {
+ result = new org.osgi.service.event.TopicPermission(topic, m_type);
+ } catch (Throwable t)
+ {
+ // This might happen in case security is not supported
+ // Bundle.hasPermission will return true in this case
+ // hence topicPermission = new Object() is o.k.
+
+ result = new Object();
+ }
+
+ m_cache.add(topic, result);
+ }
+
+ return result;
+ }
+
+ /*
+ * This is a utility method that will throw a <tt>NullPointerException</tt>
+ * in case that the given object is null. The message will be of the form name +
+ * may not be null.
+ */
+ private void checkNull(final Object object, final String name)
+ {
+ if(null == object)
+ {
+ throw new NullPointerException(name + " may not be null");
+ }
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/security/EventAdminSecurityDecorator.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/security/EventAdminSecurityDecorator.java
new file mode 100644
index 0000000..543f1c8
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/security/EventAdminSecurityDecorator.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.security;
+
+import org.osgi.framework.Bundle;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+
+/**
+ * This class is a decorator for an <tt>EventAdmin</tt> service. It secures the
+ * service by checking any call from a given bundle (i.e., the caller) to the admins
+ * post or send methods for the appropriate permissions based on a given permission
+ * factory. This methods then in turn throw a <tt>SecurityException</tt> in case
+ * the given bundle doesn't pass the check or delegate the call to decorated service
+ * instance, respectively.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class EventAdminSecurityDecorator implements EventAdmin
+{
+ // The bundle used to determine appropriate permissions
+ private final Bundle m_bundle;
+
+ // The decorated service instance
+ private final EventAdmin m_admin;
+
+ // The permission factory
+ private final TopicPermissions m_topicPermissions;
+
+ /**
+ * The constructor of this decorator. The given bundle and permission factory
+ * will be used to determine appropriate permissions for any call to
+ * <tt>postEvent()</tt> or <tt>sendEvent()</tt>, respectively. This method then
+ * in turn throw a <tt>SecurityException</tt> in case the given bundle doesn't
+ * pass the check.
+ *
+ * @param bundle The calling bundle used to determine appropriate permissions
+ * @param admin The decorated service instance
+ * @param topicPermissions The permission factory
+ */
+ public EventAdminSecurityDecorator(final Bundle bundle, final EventAdmin admin,
+ final TopicPermissions topicPermissions)
+ {
+ checkNull(bundle, "Bundle");
+ checkNull(admin, "Admin");
+ checkNull(topicPermissions, "TopicPermissions");
+
+ m_bundle = bundle;
+
+ m_admin = admin;
+
+ m_topicPermissions = topicPermissions;
+ }
+
+ /**
+ * This method checks whether the given (i.e., calling) bundle has
+ * appropriate permissions to post an event to the targeted topic. A
+ * <tt>SecurityException</tt> is thrown in case it has not. Otherwise, the
+ * event is posted using this decorator's service instance.
+ *
+ * @param event The event that should be posted
+ *
+ * @see org.osgi.service.event.EventAdmin#postEvent(org.osgi.service.event.Event)
+ */
+ public void postEvent(final Event event)
+ {
+ checkPermission(event.getTopic());
+
+ m_admin.postEvent(event);
+ }
+
+ /**
+ * This method checks whether the given (i.e., calling) bundle has
+ * appropriate permissions to send an event to the targeted topic. A
+ * <tt>SecurityException</tt> is thrown in case it has not. Otherwise,
+ * the event is posted using this decorator's service instance.
+ *
+ * @param event The event that should be send
+ *
+ * @see org.osgi.service.event.EventAdmin#sendEvent(org.osgi.service.event.Event)
+ */
+ public void sendEvent(final Event event)
+ {
+ checkPermission(event.getTopic());
+
+ m_admin.sendEvent(event);
+ }
+
+ /**
+ * Overrides <tt>hashCode()</tt> and returns the hash code of the decorated
+ * service instance.
+ *
+ * @return The hash code of the decorated service instance
+ *
+ * @see java.lang.Object#hashCode()
+ * @see org.osgi.service.event.EventAdmin
+ */
+ public int hashCode()
+ {
+ return m_admin.hashCode();
+ }
+
+ /**
+ * Overrides <tt>equals()</tt> and delegates the call to the decorated service
+ * instance. In case that o is an instance of this class it passes o's service
+ * instance instead of o.
+ *
+ * @param o The object to compare with this decorator's service instance
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ * @see org.osgi.service.event.EventAdmin
+ */
+ public boolean equals(final Object o)
+ {
+ if(o instanceof EventAdminSecurityDecorator)
+ {
+ return m_admin.equals(((EventAdminSecurityDecorator) o).m_admin);
+ }
+
+ return m_admin.equals(o);
+ }
+
+ /*
+ * This is a utility method that will throw a <tt>SecurityExcepiton</tt> in case
+ * that the given bundle (i.e, the caller) has not appropriate permissions to
+ * publish to this topic. This method uses Bundle.hasPermission() and the given
+ * permission factory to determine this.
+ */
+ private void checkPermission(final String topic)
+ {
+ if(!m_bundle.hasPermission(m_topicPermissions.createTopicPermission(topic)))
+ {
+ throw new SecurityException("Bundle[" + m_bundle +
+ "] has no PUBLISH permission for topic [" + topic + "]");
+ }
+ }
+
+ /*
+ * This is a utility method that will throw a <tt>NullPointerException</tt>
+ * in case that the given object is null. The message will be of the form name +
+ * may not be null.
+ */
+ private void checkNull(final Object object, final String name)
+ {
+ if(null == object)
+ {
+ throw new NullPointerException(name + " may not be null");
+ }
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/security/SecureEventAdminFactory.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/security/SecureEventAdminFactory.java
new file mode 100644
index 0000000..c8c50e2
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/security/SecureEventAdminFactory.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.security;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.EventAdmin;
+
+/**
+ * This class is a factory that secures a given <tt>EventAdmin</tt> service by
+ * wrapping it with a new instance of an <tt>EventAdminSecurityDecorator</tt> on
+ * any call to its <tt>getService()</tt> method. The decorator will determine the
+ * appropriate permissions by using the given permission factory and the bundle
+ * parameter passed to the <tt>getService()</tt> method.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class SecureEventAdminFactory implements ServiceFactory
+{
+ // The EventAdmin to secure
+ private EventAdmin m_admin;
+
+ // The permission factory
+ private final TopicPermissions m_topicPermissions;
+
+ /**
+ * The constructor of the factory. The factory will use the given event admin and
+ * permission factory to create a new <tt>EventAdminSecurityDecorator</tt>
+ * on any call to <tt>getService()</tt>.
+ *
+ * @param admin The <tt>EventAdmin</tt> service to secure.
+ * @param topicPermissions The permission factory to use for permission lookup.
+ */
+ public SecureEventAdminFactory(final EventAdmin admin, final TopicPermissions
+ topicPermissions)
+ {
+ checkNull(admin, "Admin");
+ checkNull(topicPermissions, "TopicPermissions");
+
+ m_admin = admin;
+
+ m_topicPermissions = topicPermissions;
+ }
+
+ /**
+ * Returns a new <tt>EventAdminSecurityDecorator</tt> initialized with the
+ * given <tt>EventAdmin</tt>. That in turn will check any call to post or
+ * send for the appropriate permissions based on the bundle parameter.
+ *
+ * @param bundle The bundle used to determine the permissions of the caller
+ * @param registration The ServiceRegistration that is not used
+ *
+ * @return The given service instance wrapped by an <tt>EventAdminSecuriryDecorator</tt>
+ *
+ * @see org.osgi.framework.ServiceFactory#getService(org.osgi.framework.Bundle,
+ * org.osgi.framework.ServiceRegistration)
+ */
+ public Object getService(final Bundle bundle,
+ final ServiceRegistration registration)
+ {
+ // We don't need to cache this objects since the framework already does this.
+ return new EventAdminSecurityDecorator(bundle, m_admin, m_topicPermissions);
+ }
+
+ /**
+ * This method doesn't do anything at the moment.
+ *
+ * @param bundle The bundle object that is not used
+ * @param registration The ServiceRegistration that is not used
+ * @param service The service object that is not used
+ *
+ * @see org.osgi.framework.ServiceFactory#ungetService(org.osgi.framework.Bundle,
+ * org.osgi.framework.ServiceRegistration, java.lang.Object)
+ */
+ public void ungetService(final Bundle bundle,
+ final ServiceRegistration registration, final Object service)
+ {
+ // We don't need to do anything here since we hand-out a new instance with
+ // any call to getService hence, it is o.k. to just wait for the next gc.
+ }
+
+ /*
+ * This is a utility method that will throw a <tt>NullPointerException</tt>
+ * in case that the given object is null. The message will be of the form name +
+ * may not be null.
+ */
+ private void checkNull(final Object object, final String name)
+ {
+ if(null == object)
+ {
+ throw new NullPointerException(name + " may not be null");
+ }
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/security/TopicPermissions.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/security/TopicPermissions.java
new file mode 100644
index 0000000..381a7e4
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/security/TopicPermissions.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.security;
+
+/**
+ * A <tt>TopicPermission</tt> factory. The factory is bound to a specific type (i.e.,
+ * either PUBLISH or SUBSCRIBE) and subsequently allows to create new permission
+ * objects by providing the topic. Note that the created permission objects most
+ * likely will be cached and that in case that a permission can not be created due
+ * to missing security support by the framework (i.e, security is not supported at
+ * all) an instance of <tt>java.lang.Object</tt> will be returned.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public interface TopicPermissions
+{
+ /**
+ * Get the type (i.e., PUBLISH or SUBSCRIBE) of the permission objects that this
+ * factory will create.
+ *
+ * @return The type of the permission objects that this factory will create.
+ *
+ * @see org.osgi.service.event.TopicPermission#PUBLISH
+ * @see org.osgi.service.event.TopicPermission#SUBSCRIBE
+ */
+ public String getType();
+
+ /**
+ * This method returns a <tt>TopicPermission</tt> object for the given topic and
+ * the type (i.e., PUBLISH or SUBSCRIBE) of this factory. Note that this methods
+ * returns an instance of <tt>java.lang.Object</tt> in case that a permission
+ * could not be created due to missing security support by the framework.
+ *
+ * @param topic The targeted topic.
+ *
+ * @return A <tt>TopicPermission</tt> for the given topic and the type of this
+ * factory or a <tt>java.lang.Object</tt> in case that the permission could
+ * not be created due to missing security support by the framework.
+ *
+ * @see org.osgi.service.event.TopicPermission
+ * @see org.osgi.service.event.TopicPermission#PUBLISH
+ * @see org.osgi.service.event.TopicPermission#SUBSCRIBE
+ */
+ public Object createTopicPermission(final String topic);
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/AsyncDeliverTasks.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/AsyncDeliverTasks.java
new file mode 100644
index 0000000..8e8660d
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/AsyncDeliverTasks.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.tasks;
+
+import org.apache.felix.eventadmin.impl.dispatch.TaskQueue;
+import org.apache.felix.eventadmin.impl.dispatch.ThreadPool;
+
+/**
+ * This class does the actual work of the asynchronous event dispatch.
+ *
+ * <p>It serves two purposes: first, it will append tasks to its queue hence,
+ * asynchronous event delivery is executed - second, it will set up a given dispatch
+ * task with its <tt>ThreadPool</tt> in a way that it is associated with a
+ * <tt>DeliverTask</tt> that will block in case the thread hits the
+ * <tt>SyncDeliverTasks</tt>.
+ * </p>
+ * In other words, if the asynchronous event dispatching thread is used to send a
+ * synchronous event then it will spin-off a new asynchronous dispatching thread
+ * while the former waits for the synchronous event to be delivered and then return
+ * to its <tt>ThreadPool</tt>.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class AsyncDeliverTasks implements DeliverTasks, HandoverTask, DeliverTask
+{
+ // The asynchronous event delivery queue
+ private final TaskQueue m_queue;
+
+ // The synchronous event delivery queue needed in case that the asynchronous
+ // event dispatching thread is used to send a synchronous event. This is a
+ // private member and only default because it is used in an inner class (for
+ // performance reasons)
+ final TaskQueue m_handoverQueue;
+
+ // The thread pool to use to spin-off new threads
+ private final ThreadPool m_pool;
+
+ /**
+ * The constructor of the class that will use the asynchronous queue to append
+ * event dispatch handlers. Furthermore, a second queue is used to append
+ * the events in case that the asynchronous event dispatching thread is used to
+ * send a synchronous event - in this case the given <tt>ThreadPool</tt> is used
+ * to spin-off a new asynchronous event dispatching thread while the former waits
+ * for the synchronous event to be delivered.
+ *
+ * @param queue The asynchronous event queue
+ * @param handoverQueue The synchronous event queue, to be used in case that the
+ * asynchronous event dispatching thread is used to send a synchronous event
+ * @param pool The thread pool used to spin-off new asynchronous event
+ * dispatching threads in case of timeout or that the asynchronous event
+ * dispatching thread is used to send a synchronous event
+ */
+ public AsyncDeliverTasks(final TaskQueue queue, final TaskQueue handoverQueue,
+ final ThreadPool pool)
+ {
+ m_queue = queue;
+
+ m_handoverQueue = handoverQueue;
+
+ m_pool = pool;
+ }
+
+ /**
+ * Return a <tt>DeliverTask</tt> that can be used to execute asynchronous event
+ * dispatch.
+ *
+ * @return A task that can be used to execute asynchronous event dispatch
+ *
+ * @see org.apache.felix.eventadmin.impl.tasks.DeliverTasks#createTask()
+ */
+ public DeliverTask createTask()
+ {
+ return this;
+ }
+
+ /**
+ * Execute asynchronous event dispatch.
+ *
+ * @param tasks The event dispatch tasks to execute
+ *
+ * @see org.apache.felix.eventadmin.impl.tasks.DeliverTask#execute(org.apache.felix.eventadmin.impl.tasks.HandlerTask[])
+ */
+ public void execute(final HandlerTask[] tasks)
+ {
+ m_queue.append(tasks);
+ }
+
+ /**
+ * Execute the handover in case of timeout or that the asynchronous event
+ * dispatching thread is used to send a synchronous event.
+ *
+ * @param task The task to set-up in a new thread
+ *
+ * @see org.apache.felix.eventadmin.impl.tasks.HandoverTask#execute(org.apache.felix.eventadmin.impl.tasks.DispatchTask)
+ */
+ public void execute(final DispatchTask task)
+ {
+ // This will spin-off a new thread using the thread pool and set it up with
+ // the given task. Additionally, the thread is associated with a callback
+ // that will handover (i.e., yet again call this method) and append the
+ // tasks given to to the m_handoverQueue (i.e., the synchronous queue). This
+ // will happen in case that the current asynchronous thread is used to
+ // send a synchronous event.
+ m_pool.execute(task, new DeliverTask()
+ {
+ public void execute(final HandlerTask[] managers)
+ {
+ final BlockTask waitManager = new BlockTask();
+
+ final HandlerTask[] newmanagers = new HandlerTask[managers.length + 1];
+
+ System.arraycopy(managers, 0, newmanagers, 0,
+ managers.length);
+
+ newmanagers[managers.length] = waitManager;
+
+ m_handoverQueue.append(newmanagers);
+
+ task.handover();
+
+ waitManager.block();
+ }
+ });
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/BlockTask.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/BlockTask.java
new file mode 100644
index 0000000..b1896a4
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/BlockTask.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.tasks;
+
+/**
+ * This task will can be used to block a thread that subsequently will be unblocked
+ * once the task is executed.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class BlockTask implements HandlerTask
+{
+ // The internal lock for this object used instead synchronized(this)
+ private final Object m_lock = new Object();
+
+ // Has this task not been executed?
+ private boolean m_blocking = true;
+
+ /**
+ * Unblock possibly blocking threads.
+ *
+ * @see org.apache.felix.eventadmin.impl.tasks.HandlerTask#execute()
+ */
+ public void execute()
+ {
+ synchronized (m_lock)
+ {
+ m_blocking = false;
+ m_lock.notifyAll();
+ }
+ }
+
+ /**
+ * This methods does nothing since we only need this task to block and unblock
+ * threads.
+ *
+ * @see org.apache.felix.eventadmin.impl.tasks.HandlerTask#blackListHandler()
+ */
+ public void blackListHandler()
+ {
+ // This method does nothing since we only need this task to block and
+ // unblock threads
+ }
+
+ /**
+ * Block the calling thread until this task is executed.
+ */
+ public void block()
+ {
+ synchronized (m_lock)
+ {
+ while (m_blocking)
+ {
+ try
+ {
+ m_lock.wait();
+ } catch (InterruptedException e)
+ {
+
+ }
+ }
+ }
+ }
+
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/DeliverTask.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/DeliverTask.java
new file mode 100644
index 0000000..94ed48e
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/DeliverTask.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.tasks;
+
+/**
+ * Dispatch given event dispatch tasks.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public interface DeliverTask
+{
+ /**
+ * Dispatch the given event dispatch tasks.
+ *
+ * @param handlerTasks The event dispatch tasks to execute
+ */
+ public void execute(final HandlerTask[] handlerTasks);
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/DeliverTasks.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/DeliverTasks.java
new file mode 100644
index 0000000..7e8f1be
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/DeliverTasks.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.tasks;
+
+/**
+ * A factory that creates <tt>DeliverTask</tt> objects.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public interface DeliverTasks
+{
+ /**
+ * Create a deliver task.
+ *
+ * @return A <tt>DeliverTask</tt> that can be used to dispatch event handler
+ * tasks
+ */
+ public DeliverTask createTask();
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/DispatchTask.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/DispatchTask.java
new file mode 100644
index 0000000..6473abb
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/DispatchTask.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.tasks;
+
+import org.apache.felix.eventadmin.impl.dispatch.Scheduler;
+import org.apache.felix.eventadmin.impl.dispatch.TaskProducer;
+
+/**
+ * This class is the core of the event dispatching (for both, synchronous and
+ * asynchronous). It implements handover and timeout capabilities.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class DispatchTask implements Runnable
+{
+ // A null scheduler object that does not schedule given tasks
+ private static final Scheduler NULL_SCHEDULER = new Scheduler()
+ {
+ /**
+ * This is a null object and will do nothing with the given task
+ *
+ * @param task A task that is not used
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.Scheduler#schedule(java.lang.Runnable)
+ */
+ public void schedule(final Runnable task)
+ {
+ // This is a null object and will do nothing with the given task
+ }
+
+ /**
+ * This is a null object and will do nothing with the given task
+ *
+ * @param task A task that is not used
+ * @parma nice A value that is not used
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.Scheduler#schedule(java.lang.Runnable, int)
+ */
+ public void schedule(final Runnable task, final int nice)
+ {
+ // This is a null object and will do nothing with the given task
+ }
+ };
+
+ // A null producer object that will return null on any call to next()
+ private static final TaskProducer NULL_PRODUCER = new TaskProducer()
+ {
+ /**
+ * This is a null object and will return <tt>null</tt>
+ *
+ * @return <tt>null</tt>
+ *
+ * @see org.apache.felix.eventadmin.impl.dispatch.TaskProducer#next()
+ */
+ public HandlerTask next()
+ {
+ return null;
+ }
+ };
+
+ // A null handover task that will do nothing on execute
+ private static final HandoverTask NULL_HANDOVER = new HandoverTask()
+ {
+ /**
+ * This is a null object that will do nothing.
+ *
+ * @parma task A task that is not used
+ *
+ * @see org.apache.felix.eventadmin.impl.tasks.HandoverTask#execute(org.apache.felix.eventadmin.impl.tasks.DispatchTask)
+ */
+ public void execute(final DispatchTask task)
+ {
+ // This is a null object that will do nothing.
+ }
+ };
+
+ // The internal lock for this object used instead synchronized(this)
+ final Object m_lock = new Object();
+
+ // The task producer (i.e., the event queue) that will be a null object if not
+ // needed anymore
+ private volatile TaskProducer m_producer;
+
+ // The scheduler to use that will be a null object if not needed anymore
+ private Scheduler m_scheduler;
+
+ // The handover callback that is called on timeouts and handovers and that will
+ // be a null object if not needed anymore
+ private HandoverTask m_handover;
+
+ // Used to blacklist on timeout
+ private BlackListTask m_blackListTask = null;
+
+ // Are we currently blocked (i.e., do not tick the timeout clock down)?
+ private boolean m_isHolding = false;
+
+ /**
+ * The constructor of the object.
+ *
+ * @param producer The producer (i.e., the event queue) that provides the next
+ * tasks
+ * @param scheduler The scheduler to use for timeout actions
+ * @param handover The callback to use on timeouts and handovers
+ */
+ public DispatchTask(final TaskProducer producer, final Scheduler scheduler,
+ final HandoverTask handover)
+ {
+ m_producer = producer;
+
+ m_scheduler = scheduler;
+
+ m_handover = handover;
+ }
+
+ /*
+ * Construct a new object from a old one.
+ */
+ private DispatchTask(final DispatchTask old)
+ {
+ this(old.m_producer, old.m_scheduler, old.m_handover);
+ }
+
+ /**
+ * This will loop until the producer returns <tt>null</tt>. Until then the
+ * returned tasks are executed.
+ *
+ * @see java.lang.Runnable#run()
+ */
+ public void run()
+ {
+ for (HandlerTask manager = m_producer.next(); null != manager; manager = m_producer
+ .next())
+ {
+ synchronized (m_lock)
+ {
+ // Set-up the timeout
+ m_blackListTask = new BlackListTask(manager);
+
+ m_scheduler.schedule(m_blackListTask);
+ }
+
+ // HandlerTask does catch exceptions hence, we don't need to do it.
+ manager.execute();
+
+ synchronized (m_lock)
+ {
+ // release the timeout
+ m_blackListTask.cancel();
+ }
+ }
+ }
+
+ /**
+ * This method will trigger a callback to the handover callback and stop this
+ * task.
+ */
+ public void handover()
+ {
+ synchronized (m_lock)
+ {
+ // release the timeout
+ m_blackListTask.cancel();
+
+ // spin-off a new thread
+ m_handover.execute(new DispatchTask(this));
+
+ stop();
+ }
+ }
+
+ /**
+ * This method stops the tasks without a handover
+ */
+ public void stop()
+ {
+ synchronized (m_lock)
+ {
+ // release the timeout
+ m_blackListTask.cancel();
+
+ m_handover = NULL_HANDOVER;
+
+ m_producer = NULL_PRODUCER;
+
+ m_scheduler = NULL_SCHEDULER;
+ }
+ }
+
+ /**
+ * This will pause the task (including its timeout clock) until a call to
+ * <tt>resume()</tt>
+ */
+ public void hold()
+ {
+ synchronized (m_lock)
+ {
+ // release the timeout
+ m_blackListTask.cancel();
+
+ // record the time that we already used
+ int pastTime = (int) (System.currentTimeMillis() - m_blackListTask
+ .getTime());
+
+ // spin-off a new thread
+ m_handover.execute(new DispatchTask(this));
+
+ // block until a call to resume()
+ m_isHolding = true;
+
+ while (m_isHolding)
+ {
+ try
+ {
+ m_lock.wait();
+ } catch (InterruptedException e)
+ {
+ }
+ }
+
+ // restore the timeout
+ m_blackListTask = new BlackListTask(m_blackListTask,
+ System.currentTimeMillis() - pastTime);
+
+ m_scheduler.schedule(m_blackListTask, pastTime);
+ }
+ }
+
+ /**
+ * This will let the previously hold task resume.
+ */
+ public void resume()
+ {
+ synchronized (m_lock)
+ {
+ m_isHolding = false;
+
+ m_lock.notifyAll();
+ }
+ }
+
+ /*
+ * This is the implementation of the timeout.
+ */
+ private class BlackListTask implements Runnable
+ {
+ // Are we canceled?
+ private boolean m_canceled = false;
+
+ // The time we have been started
+ private final long m_time;
+
+ // The task we will blacklist if we are triggered
+ private final HandlerTask m_manager;
+
+ BlackListTask(final HandlerTask manager)
+ {
+ this(manager, System.currentTimeMillis());
+ }
+
+ BlackListTask(final HandlerTask manager, final long time)
+ {
+ m_manager = manager;
+
+ m_time = time;
+ }
+
+ BlackListTask(final BlackListTask old, final long time)
+ {
+ this(old.m_manager, time);
+ }
+
+ /**
+ * @return The time we have been created.
+ */
+ public long getTime()
+ {
+ return m_time;
+ }
+
+ /**
+ * We have been triggered hence, blacklist the handler except if we are
+ * already canceled
+ *
+ * @see java.lang.Runnable#run()
+ */
+ public void run()
+ {
+ synchronized (m_lock)
+ {
+ if (!m_canceled)
+ {
+ m_manager.blackListHandler();
+
+ handover();
+ }
+ }
+ }
+
+ /**
+ * Cancel the timeout
+ */
+ public void cancel()
+ {
+ synchronized (m_lock)
+ {
+ m_canceled = true;
+ }
+ }
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java
new file mode 100644
index 0000000..edf00b6
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.tasks;
+
+/**
+ * A task that will deliver its event to its <tt>EventHandler</tt> when executed
+ * or blacklist the handler, respectively.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public interface HandlerTask
+{
+ /**
+ * Deliver the event to the handler.
+ */
+ public void execute();
+
+ /**
+ * Blacklist the handler.
+ */
+ public void blackListHandler();
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTaskImpl.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTaskImpl.java
new file mode 100644
index 0000000..a263762
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTaskImpl.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.tasks;
+
+import org.apache.felix.eventadmin.impl.handler.BlacklistingHandlerTasks;
+import org.apache.felix.eventadmin.impl.util.LogWrapper;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
+
+/**
+ * An implementation of the <tt>HandlerTask</tt> interface.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class HandlerTaskImpl implements HandlerTask
+{
+ // The service reference of the handler
+ private final ServiceReference m_eventHandlerRef;
+
+ // The event to deliver to the handler
+ private final Event m_event;
+
+ // Used to blacklist the service or get the service object for the reference
+ private final BlacklistingHandlerTasks m_handlerTasks;
+
+ /**
+ * Construct a delivery task for the given service and event.
+ *
+ * @param eventHandlerRef The servicereference of the handler
+ * @param event The event to deliver
+ * @param handlerTasks Used to blacklist the service or get the service object
+ * for the reference
+ */
+ public HandlerTaskImpl(final ServiceReference eventHandlerRef,
+ final Event event, final BlacklistingHandlerTasks handlerTasks)
+ {
+ m_eventHandlerRef = eventHandlerRef;
+
+ m_event = event;
+
+ m_handlerTasks = handlerTasks;
+ }
+
+ /**
+ * @see org.apache.felix.eventadmin.impl.tasks.HandlerTask#execute()
+ */
+ public void execute()
+ {
+ // Get the service object
+ final EventHandler handler = m_handlerTasks
+ .getEventHandler(m_eventHandlerRef);
+
+ try
+ {
+ handler.handleEvent(m_event);
+ } catch (Exception e)
+ {
+ // The spec says that we must catch exceptions and log them:
+ LogWrapper.getLogger().log(
+ m_eventHandlerRef,
+ LogWrapper.LOG_WARNING,
+ "Exception during event dispatch [" + m_event + " | "
+ + m_eventHandlerRef + " | Bundle("
+ + m_eventHandlerRef.getBundle() + ")]", e);
+ }
+ }
+
+ /**
+ * @see org.apache.felix.eventadmin.impl.tasks.HandlerTask#blackListHandler()
+ */
+ public void blackListHandler()
+ {
+ m_handlerTasks.blackList(m_eventHandlerRef);
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandoverTask.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandoverTask.java
new file mode 100644
index 0000000..5ae14ce
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandoverTask.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.tasks;
+
+/**
+ * A task that is used to handover a dispatch thread context to another thread.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public interface HandoverTask
+{
+ /**
+ * Handover the context to another thread.
+ *
+ * @param task The context to be executed in another thread.
+ */
+ public void execute(final DispatchTask task);
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/ResumeTask.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/ResumeTask.java
new file mode 100644
index 0000000..82ab8f41
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/ResumeTask.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.tasks;
+
+import org.apache.felix.eventadmin.impl.dispatch.ThreadPool;
+
+/**
+ * A task that wakes-up a disabled <tt>DispatchTask</tt>. Additionally, it will
+ * stop the currently running task.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class ResumeTask implements HandlerTask
+{
+ // The task to wake-up on execution
+ private final DispatchTask m_target;
+
+ // The pool used to get the task to stop on execution
+ private final ThreadPool m_pool;
+
+ /**
+ * @param target The task to wake-up on execution
+ * @param pool The pool used to get the task to stop on execution
+ */
+ public ResumeTask(final DispatchTask target, final ThreadPool pool)
+ {
+ m_target = target;
+
+ m_pool = pool;
+ }
+
+ /**
+ * Stop the current task and wake-up the target.
+ *
+ * @see org.apache.felix.eventadmin.impl.tasks.HandlerTask#execute()
+ */
+ public void execute()
+ {
+ m_pool.getTask(Thread.currentThread(), null).stop();
+
+ m_target.resume();
+ }
+
+ /**
+ * This does nothing since this task is only used to wake-up disabled tasks.
+ *
+ * @see org.apache.felix.eventadmin.impl.tasks.HandlerTask#blackListHandler()
+ */
+ public void blackListHandler()
+ {
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java
new file mode 100644
index 0000000..3854436
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.tasks;
+
+import org.apache.felix.eventadmin.impl.dispatch.TaskQueue;
+import org.apache.felix.eventadmin.impl.dispatch.ThreadPool;
+
+/**
+ * This class does the actual work of the synchronous event delivery.
+ * <p><tt>
+ * It serves two purposes, first it is used to select the appropriate action
+ * depending on whether the sending thread is the asynchronous, the synchronous, or
+ * an unrelated thread. Second, it will set up a given dispatch
+ * task with its <tt>ThreadPool</tt> in a way that it is associated with a
+ * <tt>DeliverTask</tt> that will push given handler tasks to the queue and
+ * then wait for the tasks to be completed.
+ * </tt></p>
+ * In other words if an unrelated thread is used to send a synchronous event it is
+ * blocked until the event is send (or a timeout occurs), if an asynchronous thread
+ * is used its handover callback is called in order to spin-off a new asynchronous
+ * delivery thread and the former is blocked until the events are delivered and then
+ * released (or returned to its thread pool), if a synchronous thread is used its
+ * task is disabled, the events are pushed to the queue and the threads continuous
+ * with the delivery of the new events (as per spec). Once the new events are done
+ * the thread wakes-up the disabled task and resumes to execute it.
+ * <p><tt>
+ * Note that in case of a timeout while a task is disabled the thread is released and
+ * we spin-off a new thread that resumes the disabled task hence, this is the only
+ * place were we break the semantics of the synchronous delivery. While the only one
+ * to notice this is the timed-out handler - it is the fault of this handler too
+ * (i.e., it blocked the dispatch for to long) but since it will not receive events
+ * anymore it will not notice this semantic difference except that it might not see
+ * events it already sent before.
+ * </tt></pre>
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class SyncDeliverTasks implements DeliverTasks, HandoverTask, DeliverTask
+{
+ // The synchronous event queue
+ final TaskQueue m_queue;
+
+ // The thread pool used to spin-off new threads and associate callbacks with
+ // tasks
+ final ThreadPool m_pool;
+
+ /**
+ * @param queue The synchronous event queue
+ * @param pool The thread pool used to spin-off new threads and associate
+ * callbacks with tasks
+ */
+ public SyncDeliverTasks(final TaskQueue queue, final ThreadPool pool)
+ {
+ m_queue = queue;
+
+ m_pool = pool;
+ }
+
+ /**
+ * This will select the appropriate action depending on whether the sending
+ * thread is the asynchronous, the synchronous, or an unrelated thread.
+ *
+ * @return The appropriate action
+ *
+ * @see org.apache.felix.eventadmin.impl.tasks.DeliverTasks#createTask()
+ */
+ public DeliverTask createTask()
+ {
+ return m_pool.getCallback(Thread.currentThread(), this);
+ }
+
+ /**
+ * This blocks an unrelated thread used to send a synchronous event until the
+ * event is send (or a timeout occurs).
+ *
+ * @param tasks The event handler dispatch tasks to execute
+ *
+ * @see org.apache.felix.eventadmin.impl.tasks.DeliverTask#execute(org.apache.felix.eventadmin.impl.tasks.HandlerTask[])
+ */
+ public void execute(final HandlerTask[] tasks)
+ {
+ final BlockTask waitManager = new BlockTask();
+
+ final HandlerTask[] newtasks = new HandlerTask[tasks.length + 1];
+
+ System.arraycopy(tasks, 0, newtasks, 0, tasks.length);
+
+ newtasks[tasks.length] = waitManager;
+
+ m_queue.append(newtasks);
+
+ waitManager.block();
+ }
+
+ /**
+ * Set up a given dispatch task with its <tt>ThreadPool</tt> in a way that it is
+ * associated with a <tt>DeliverTask</tt> that will push given handler tasks to
+ * the queue and then wait for the tasks to be completed.
+ *
+ * @param task The task to set-up
+ *
+ * @see org.apache.felix.eventadmin.impl.tasks.HandoverTask#execute(org.apache.felix.eventadmin.impl.tasks.DispatchTask)
+ */
+ public void execute(final DispatchTask task)
+ {
+ m_pool.execute(task, new DeliverTask()
+ {
+ public void execute(final HandlerTask[] managers)
+ {
+ final ResumeTask resumeManager = new ResumeTask(
+ task, m_pool);
+
+ final HandlerTask[] newmanagers = new HandlerTask[managers.length + 1];
+
+ System.arraycopy(managers, 0, newmanagers, 0,
+ managers.length);
+
+ newmanagers[managers.length] = resumeManager;
+
+ m_queue.push(newmanagers);
+
+ task.hold();
+ }
+ });
+ }
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/util/CacheMap.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/util/CacheMap.java
new file mode 100644
index 0000000..93e007f
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/util/CacheMap.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.util;
+
+/**
+ * This is the interface of a simple cache map.
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public interface CacheMap
+{
+ /**
+ * Return the value for the key in case there is one in the cache.
+ *
+ * @param key The key to look-up
+ *
+ * @return The value for the given key in case there is one in the cache,
+ * <tt>null</tt> otherwise
+ */
+ public Object get(final Object key);
+
+ /**
+ * Add a value for the key to this cache.
+ *
+ * @param key The key for the value
+ * @param value The value to add to the cache
+ */
+ public void add(final Object key, final Object value);
+
+ /**
+ * Remove a key and its value from the cache.
+ *
+ * @param key The key to remove
+ *
+ * @return The value of the key in case there is one in the cache, <tt>null</tt>
+ * otherwise
+ */
+ public Object remove(final Object key);
+
+ /**
+ * Returns the number of key-value pairs in this cache.
+ *
+ * @return The number of key-value pairs in this cache
+ */
+ public int size();
+
+ /**
+ * Remove all entries of the cache.
+ */
+ public void clear();
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/util/LeastRecentlyUsedCacheMap.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/util/LeastRecentlyUsedCacheMap.java
new file mode 100644
index 0000000..6fd0901
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/util/LeastRecentlyUsedCacheMap.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class implements a least recently used cache map. It will hold
+ * a given size of key-value pairs and drop the least recently used entry once this
+ * size is reached. This class is thread safe.
+ *
+ * @see org.apache.felix.eventadmin.impl.util.CacheMap
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+ */
+public class LeastRecentlyUsedCacheMap implements CacheMap
+{
+ // The internal lock for this object used instead synchronized(this)
+ private final Object m_lock = new Object();
+
+ // The max number of entries in the cache. Once reached entries are replaced
+ private final int m_maxSize;
+
+ // The cache
+ private final Map m_cache;
+
+ // The history used to determine the least recently used entries. The end of the
+ // list is the most recently used key. In other words m_history.get(0) returns
+ // the least recently used key.
+ private final List m_history;
+
+ /**
+ * The constructor of the cache. The given max size will be used to determine the
+ * size of the cache that triggers replacing least recently used entries with
+ * new ones.
+ *
+ * @param maxSize The max number of entries in the cache
+ */
+ public LeastRecentlyUsedCacheMap(final int maxSize)
+ {
+ if(0 >= maxSize)
+ {
+ throw new IllegalArgumentException("Size must be positive");
+ }
+
+ m_maxSize = maxSize;
+
+ // We need one more entry then m_maxSize in the cache and a HashMap is
+ // expanded when it reaches 3/4 of its size hence, the funny numbers.
+ m_cache = new HashMap(m_maxSize + 1 + ((m_maxSize + 1) * 3) / 4);
+
+ // This is like above but assumes a list is expanded when it reaches 1/2 of
+ // its size. Not much harm if this is not the case.
+ m_history = new ArrayList(m_maxSize + 1 + ((m_maxSize + 1) / 2));
+ }
+
+ /**
+ * Returns the value for the key in case there is one. Additionally, the
+ * LRU counter for the key is updated.
+ *
+ * @param key The key for the value to return
+ *
+ * @return The value of the key in case there is one, <tt>null</tt> otherwise
+ *
+ * @see org.apache.felix.eventadmin.impl.util.CacheMap#get(java.lang.Object)
+ */
+ public Object get(final Object key)
+ {
+ synchronized(m_lock)
+ {
+ final Object result = m_cache.get(key);
+
+ if(null != result)
+ {
+ m_history.remove(key);
+
+ m_history.add(key);
+ }
+
+ return result;
+ }
+ }
+
+ /**
+ * Add the key-value pair to the cache. The key will be come the most recently
+ * used entry. In case max size is (or has been) reached this will remove the
+ * least recently used entry in the cache. In case that the cache already
+ * contains this specific key-value pair it LRU counter is updated only.
+ *
+ * @param key The key for the value
+ * @param value The value for the key
+ *
+ * @see org.apache.felix.eventadmin.impl.util.CacheMap#add(java.lang.Object, java.lang.Object)
+ */
+ public void add(final Object key, final Object value)
+ {
+ synchronized(m_lock)
+ {
+ final Object result = m_cache.put(key, value);
+
+ if(null != result)
+ {
+ m_history.remove(key);
+ }
+
+ m_history.add(key);
+
+ if(m_maxSize < m_cache.size())
+ {
+ m_cache.remove(m_history.remove(0));
+ }
+ }
+ }
+
+ /**
+ * Remove the entry denoted by key from the cache and return its value.
+ *
+ * @param key The key of the entry to be removed
+ *
+ * @return The value of the entry removed, <tt>null</tt> if none
+ *
+ * @see org.apache.felix.eventadmin.impl.util.CacheMap#remove(java.lang.Object)
+ */
+ public Object remove(final Object key)
+ {
+ synchronized(m_lock)
+ {
+ final Object result = m_cache.remove(key);
+
+ if(null != result)
+ {
+ m_history.remove(key);
+ }
+
+ return result;
+ }
+ }
+
+ /**
+ * Return the current size of the cache.
+ *
+ * @return The number of entries currently in the cache.
+ *
+ * @see org.apache.felix.eventadmin.impl.util.CacheMap#size()
+ */
+ public int size()
+ {
+ synchronized (m_lock)
+ {
+ return m_cache.size();
+ }
+ }
+
+ /**
+ * Remove all entries from the cache.
+ *
+ * @see org.apache.felix.eventadmin.impl.util.CacheMap#clear()
+ */
+ public void clear()
+ {
+ synchronized (m_lock)
+ {
+ m_cache.clear();
+
+ m_history.clear();
+ }
+ }
+
+}
diff --git a/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/util/LogWrapper.java b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/util/LogWrapper.java
new file mode 100644
index 0000000..e1cc5e7
--- /dev/null
+++ b/org.apache.felix.eventadmin/src/main/java/org/apache/felix/eventadmin/impl/util/LogWrapper.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2005 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.eventadmin.impl.util;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * This class mimics the standard OSGi <tt>LogService</tt> interface. An
+ * instance of this class will be used by the EventAdmin for all logging. The
+ * implementation of this class sends log messages to standard output, if no
+ * <tt>LogService</tt> is present; it uses a log service if one is
+ * installed in the framework. To do that without creating a hard dependency on the
+ * package it uses fully qualified class names and registers a listener with the
+ * framework hence, it does not need access to the <tt>LogService</tt> class but will
+ * use it if the listener is informed about an available service. By using a
+ * DynamicImport-Package dependency we don't need the package but
+ * use it if present. Additionally, all log methods prefix the log message with
+ * <tt>EventAdmin: </tt>.
+ *
+ * @see org.osgi.service.log.LogService
+ *
+ * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
+**/
+// TODO: At the moment we log a message to all currently available LogServices.
+// Maybe, we should only log to the one with the highest ranking instead?
+// What is the best practice in this case?
+public class LogWrapper
+{
+ /**
+ * ERROR LEVEL
+ *
+ * @see org.osgi.service.log.LogService#LOG_ERROR
+ */
+ public static final int LOG_ERROR = 1;
+
+ /**
+ * WARNING LEVEL
+ *
+ * @see org.osgi.service.log.LogService#LOG_WARNING
+ */
+ public static final int LOG_WARNING = 2;
+
+ /**
+ * INFO LEVEL
+ *
+ * @see org.osgi.service.log.LogService#LOG_INFO
+ */
+ public static final int LOG_INFO = 3;
+
+ /**
+ * DEBUG LEVEL
+ *
+ * @see org.osgi.service.log.LogService#LOG_DEBUG
+ */
+ public static final int LOG_DEBUG = 4;
+
+ // A set containing the currently available LogServices. Furthermore used as lock
+ private final Set m_loggerRefs = new HashSet();
+
+ // Only null while not set and m_loggerRefs is empty hence, only needs to be
+ // checked in case m_loggerRefs is empty otherwise it will not be null.
+ private BundleContext m_context;
+
+ /*
+ * A thread save variant of the double checked locking singleton.
+ */
+ private static class LogWrapperLoader
+ {
+ static final LogWrapper m_singleton = new LogWrapper();
+ }
+
+ /**
+ * Returns the singleton instance of this LogWrapper that can be used to send
+ * log messages to all currently available LogServices or to standard output,
+ * respectively.
+ *
+ * @return the singleton instance of this LogWrapper.
+ */
+ public static LogWrapper getLogger()
+ {
+ return LogWrapperLoader.m_singleton;
+ }
+
+ /**
+ * Set the <tt>BundleContext</tt> of the bundle. This method registers a service
+ * listener for LogServices with the framework that are subsequently used to
+ * log messages.
+ *
+ * @param context The context of the bundle.
+ */
+ public static void setContext(final BundleContext context)
+ {
+ LogWrapperLoader.m_singleton.setBundleContext(context);
+
+ try
+ {
+ context.addServiceListener(new ServiceListener()
+ {
+ // Add a newly available LogService reference to the singleton.
+ public void serviceChanged(final ServiceEvent event)
+ {
+ if (ServiceEvent.REGISTERED == event.getType())
+ {
+ LogWrapperLoader.m_singleton.addLoggerRef(
+ event.getServiceReference());
+ }
+ // unregistered services are handled in the next log operation.
+ }
+
+ }, "(" + Constants.OBJECTCLASS
+ + "=org.osgi.service.log.LogService)");
+
+ // Add all available LogService references to the singleton.
+ final ServiceReference[] refs = context.getServiceReferences(
+ "org.osgi.service.log.LogService", null);
+
+ if (null != refs)
+ {
+ for (int i = 0; i < refs.length; i++)
+ {
+ LogWrapperLoader.m_singleton.addLoggerRef(refs[i]);
+ }
+ }
+ } catch (InvalidSyntaxException e)
+ {
+ // this never happens
+ }
+ }
+
+ /*
+ * The private singleton constructor.
+ */
+ LogWrapper()
+ {
+ // Singleton
+ }
+
+ /*
+ * Add a reference to a newly available LogService
+ */
+ void addLoggerRef(final ServiceReference ref)
+ {
+ synchronized (m_loggerRefs)
+ {
+ m_loggerRefs.add(ref);
+ }
+ }
+
+ /*
+ * Set the context of the bundle in the singleton implementation.
+ */
+ private void setBundleContext(final BundleContext context)
+ {
+ synchronized(m_loggerRefs)
+ {
+ m_context = context;
+ }
+ }
+
+ /**
+ * Log a message with the given log level. Note that this will prefix the message
+ * with <tt>EventAdmin: </tt>.
+ *
+ * @param level The log level with which to log the msg.
+ * @param msg The message to log.
+ */
+ public void log(final int level, final String msg)
+ {
+ // The method will remove any unregistered service reference as well.
+ synchronized(m_loggerRefs)
+ {
+ final String logMsg = "EventAdmin: " + msg;
+
+ if (!m_loggerRefs.isEmpty())
+ {
+ // There is at least one LogService available hence, we can use the
+ // class as well.
+ for (Iterator iter = m_loggerRefs.iterator(); iter.hasNext();)
+ {
+ org.osgi.service.log.LogService logger =
+ (org.osgi.service.log.LogService) m_context.getService(
+ (ServiceReference) iter.next());
+
+ if (null != logger)
+ {
+ logger.log(level, logMsg);
+ }
+ else
+ {
+ // The context returned null for the reference - it follows
+ // that the service is unregistered and we can remove it
+ iter.remove();
+ }
+ }
+ }
+ else
+ {
+ _log(null, level, logMsg, null);
+ }
+ }
+ }
+
+ /**
+ * Log a message with the given log level and the associated exception. Note that
+ * this will prefix the message with <tt>EventAdmin: </tt>.
+ *
+ * @param level The log level with which to log the msg.
+ * @param msg The message to log.
+ * @param ex The exception associated with the message.
+ */
+ public void log(final int level, final String msg, final Throwable ex)
+ {
+ // The method will remove any unregistered service reference as well.
+ synchronized(m_loggerRefs)
+ {
+ final String logMsg = "EventAdmin: " + msg;
+
+ if (!m_loggerRefs.isEmpty())
+ {
+ // There is at least one LogService available hence, we can use the
+ // class as well.
+ for (Iterator iter = m_loggerRefs.iterator(); iter.hasNext();)
+ {
+ org.osgi.service.log.LogService logger =
+ (org.osgi.service.log.LogService) m_context.getService(
+ (ServiceReference) iter.next());
+
+ if (null != logger)
+ {
+ logger.log(level, logMsg, ex);
+ }
+ else
+ {
+ // The context returned null for the reference - it follows
+ // that the service is unregistered and we can remove it
+ iter.remove();
+ }
+ }
+ }
+ else
+ {
+ _log(null, level, logMsg, ex);
+ }
+ }
+ }
+
+ /**
+ * Log a message with the given log level together with the associated service
+ * reference. Note that this will prefix the message with <tt>EventAdmin: </tt>.
+ *
+ * @param sr The reference of the service associated with this message.
+ * @param level The log level with which to log the msg.
+ * @param msg The message to log.
+ */
+ public void log(final ServiceReference sr, final int level, final String msg)
+ {
+ // The method will remove any unregistered service reference as well.
+ synchronized(m_loggerRefs)
+ {
+ final String logMsg = "EventAdmin: " + msg;
+
+ if (!m_loggerRefs.isEmpty())
+ {
+ // There is at least one LogService available hence, we can use the
+ // class as well.
+ for (Iterator iter = m_loggerRefs.iterator(); iter.hasNext();)
+ {
+ org.osgi.service.log.LogService logger =
+ (org.osgi.service.log.LogService) m_context.getService(
+ (ServiceReference) iter.next());
+
+ if (null != logger)
+ {
+ logger.log(sr, level, logMsg);
+ }
+ else
+ {
+ // The context returned null for the reference - it follows
+ // that the service is unregistered and we can remove it
+ iter.remove();
+ }
+ }
+ }
+ else
+ {
+ _log(sr, level, logMsg, null);
+ }
+ }
+ }
+
+ /**
+ * Log a message with the given log level, the associated service reference and
+ * exception. Note that this will prefix the message with <tt>EventAdmin: </tt>.
+ *
+ * @param sr The reference of the service associated with this message.
+ * @param level The log level with which to log the msg.
+ * @param msg The message to log.
+ * @param ex The exception associated with the message.
+ */
+ public void log(final ServiceReference sr, final int level, final String msg,
+ final Throwable ex)
+ {
+ // The method will remove any unregistered service reference as well.
+ synchronized(m_loggerRefs)
+ {
+ final String logMsg = "EventAdmin: " + msg;
+
+ if (!m_loggerRefs.isEmpty())
+ {
+ // There is at least one LogService available hence, we can use the
+ // class as well.
+ for (Iterator iter = m_loggerRefs.iterator(); iter.hasNext();)
+ {
+ org.osgi.service.log.LogService logger =
+ (org.osgi.service.log.LogService) m_context.getService(
+ (ServiceReference) iter.next());
+
+ if (null != logger)
+ {
+ logger.log(sr, level, logMsg, ex);
+ }
+ else
+ {
+ // The context returned null for the reference - it follows
+ // that the service is unregistered and we can remove it
+ iter.remove();
+ }
+ }
+ }
+ else
+ {
+ _log(sr, level, logMsg, ex);
+ }
+ }
+ }
+
+ /*
+ * Log the message to standard output. This appends the level to the message.
+ * null values are handled appropriate.
+ */
+ private void _log(final ServiceReference sr, final int level, final String msg,
+ Throwable ex)
+ {
+ String s = (sr == null) ? null : "SvcRef " + sr;
+ s = (s == null) ? msg : s + " " + msg;
+ s = (ex == null) ? s : s + " (" + ex + ")";
+
+ switch (level)
+ {
+ case LOG_DEBUG:
+ System.out.println("DEBUG: " + s);
+ break;
+ case LOG_ERROR:
+ System.out.println("ERROR: " + s);
+ if (ex != null)
+ {
+ if ((ex instanceof BundleException)
+ && (((BundleException) ex).getNestedException() != null))
+ {
+ ex = ((BundleException) ex).getNestedException();
+ }
+
+ ex.printStackTrace();
+ }
+ break;
+ case LOG_INFO:
+ System.out.println("INFO: " + s);
+ break;
+ case LOG_WARNING:
+ System.out.println("WARNING: " + s);
+ break;
+ default:
+ System.out.println("UNKNOWN[" + level + "]: " + s);
+ }
+ }
+}