FELIX-2020 : Make event admin configurable through configuration admin

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@905932 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/eventadmin/impl/pom.xml b/eventadmin/impl/pom.xml
index b202312..d504124 100644
--- a/eventadmin/impl/pom.xml
+++ b/eventadmin/impl/pom.xml
@@ -67,7 +67,23 @@
                         <DynamicImport-Package>
                             org.osgi.service.log
                         </DynamicImport-Package>
-                        <Import-Package>!org.osgi.service.log,*</Import-Package>
+                        <Import-Package>
+                            !org.osgi.service.log,
+                            <!--
+                                Configuration Admin is optional and if it is
+                                present, version 1.2 (from R4.0) is enough  
+                            -->
+                            org.osgi.service.cm;version="[1.2,2)";resolution:=optional,
+                            
+                            <!--
+                                Metatype is optional and if it is
+                                present, version 1.1 (from R4.1) is enough  
+                            -->
+                            org.osgi.service.metatype;version="[1.1,2)";resolution:=optional,
+                            
+                            <!-- default -->
+                            *
+                        </Import-Package>
                         <Export-Package>org.osgi.service.event</Export-Package>
                         <Private-Package>org.apache.felix.eventadmin.impl.*</Private-Package>
                         <Import-Service>
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Activator.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Activator.java
index 855a27e..8277851 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Activator.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Activator.java
@@ -18,17 +18,9 @@
  */
 package org.apache.felix.eventadmin.impl;
 
-import org.apache.felix.eventadmin.impl.adapter.*;
-import org.apache.felix.eventadmin.impl.dispatch.DefaultThreadPool;
-import org.apache.felix.eventadmin.impl.dispatch.ThreadPool;
-import org.apache.felix.eventadmin.impl.handler.*;
-import org.apache.felix.eventadmin.impl.security.*;
-import org.apache.felix.eventadmin.impl.tasks.*;
-import org.apache.felix.eventadmin.impl.util.LeastRecentlyUsedCacheMap;
 import org.apache.felix.eventadmin.impl.util.LogWrapper;
-import org.osgi.framework.*;
-import org.osgi.service.event.EventAdmin;
-import org.osgi.service.event.TopicPermission;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
 
 /**
  * The activator of the EventAdmin bundle. This class registers an implementation of
@@ -37,46 +29,8 @@
  * asynchronous and synchronous event-dispatching (as a spec conform optional
  * extension).
  *
- * The service knows about the following properties which are read at bundle startup:
+ * @see Configuration For configuration features of the event admin.
  *
- * <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:dev@felix.apache.org">Felix Project Team</a>
  */
@@ -85,19 +39,7 @@
 // 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_sync_pool;
-
-    private volatile ThreadPool m_async_pool;
-
-    // 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;
-
-    // The registration of the security decorator factory (i.e., the service)
-    private volatile ServiceRegistration m_registration;
+    private volatile Configuration m_config;
 
     /**
      * Called upon starting of the bundle. Constructs and registers the EventAdmin
@@ -119,93 +61,9 @@
         // 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);
+        m_config = new Configuration(context);
 
-        // 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, 20, 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);
-
-        // 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_sync_pool = new DefaultThreadPool(threadPoolSize, true);
-        m_async_pool = new DefaultThreadPool(threadPoolSize > 5 ? threadPoolSize / 2 : 2, false);
-
-        final DeliverTask syncExecuter = createSyncExecuters( m_sync_pool, timeout);
-        m_admin = createEventAdmin(context,
-            handlerTasks,
-            createAsyncExecuters(m_async_pool, syncExecuter),
-            syncExecuter);
-
-        // 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
-        m_registration = 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);
+        m_config.start();
     }
 
     /**
@@ -221,141 +79,10 @@
      */
     public void stop(final BundleContext context)
     {
-        // We need to unregister manually
-        m_registration.unregister();
-
-        m_admin.stop();
-
-        m_admin = null;
-
-        m_registration = null;
-
-        m_async_pool.close();
-
-        m_sync_pool.close();
-
-        m_async_pool = null;
-
-        m_sync_pool = null;
-    }
-
-
-    /**
-     * Create a event admin implementation.
-     * @param context      The bundle context
-     * @param handlerTasks
-     * @param asyncExecuters
-     * @param syncExecuters
-     * @return
-     */
-    protected EventAdminImpl createEventAdmin(BundleContext context,
-                                              HandlerTasks handlerTasks,
-                                              DeliverTask asyncExecuters,
-                                              DeliverTask syncExecuters)
-    {
-        return new EventAdminImpl(handlerTasks, asyncExecuters, syncExecuters);
-    }
-
-    /*
-     * 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 DeliverTask createAsyncExecuters(final ThreadPool pool, final DeliverTask deliverTask)
-    {
-        // init the queue
-        final AsyncDeliverTasks result = new AsyncDeliverTasks(pool, deliverTask);
-
-        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 DeliverTask createSyncExecuters(final ThreadPool pool, final long timeout)
-    {
-        // init the queue
-        final SyncDeliverTasks result = new SyncDeliverTasks(pool, (timeout > 100 ? timeout : 0));
-
-        return result;
-    }
-
-    /*
-     * 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)
+        if ( m_config != null )
         {
-            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);
-            }
+            m_config.destroy();
         }
-
-        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;
+        m_config = null;
     }
 }
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Configuration.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Configuration.java
new file mode 100644
index 0000000..0f2224b
--- /dev/null
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Configuration.java
@@ -0,0 +1,569 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.eventadmin.impl;
+
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.eventadmin.impl.adapter.AbstractAdapter;
+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.DefaultThreadPool;
+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.DeliverTask;
+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.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.TopicPermission;
+import org.osgi.service.metatype.MetaTypeProvider;
+
+
+/**
+ * The <code>Configuration</code> class encapsules the
+ * configuration for the event admin.
+ *
+ * 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>
+ *
+ * These properties are read at startup and serve as a default configuration.
+ * If a configuration admin is configured, the event admin can be configured
+ * through the config admin.
+ */
+public class Configuration
+{
+    /** The PID for the event admin. */
+    static final String PID = "org.apache.felix.eventadmin.impl.EventAdmin";
+
+    static final String PROP_CACHE_SIZE = "org.apache.felix.eventadmin.CacheSize";
+    static final String PROP_THREAD_POOL_SIZE = "org.apache.felix.eventadmin.ThreadPoolSize";
+    static final String PROP_TIMEOUT = "org.apache.felix.eventadmin.Timeout";
+    static final String PROP_REQUIRE_TOPIC = "org.apache.felix.eventadmin.RequireTopic";
+
+    /** The bundle context. */
+    private final BundleContext bundleContext;
+
+    private int cacheSize;
+
+    private int threadPoolSize;
+
+    private int timeout;
+
+    private boolean requireTopic;
+
+    // The thread pool used - this is a member because we need to close it on stop
+    private volatile ThreadPool m_sync_pool;
+
+    private volatile ThreadPool m_async_pool;
+
+    // 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;
+
+    // The registration of the security decorator factory (i.e., the service)
+    private volatile ServiceRegistration m_registration;
+
+    // all adapters
+    private AbstractAdapter[] adapters;
+
+    public Configuration( BundleContext bundleContext )
+    {
+        this.bundleContext = bundleContext;
+
+        // default configuration
+        configure( null );
+
+        // listen for Configuration Admin configuration
+        try
+        {
+            Object service = new ManagedService()
+            {
+                public void updated( Dictionary properties ) throws ConfigurationException
+                {
+                    configure( properties );
+                    stop();
+                    start();
+                }
+            };
+            // add meta type provider if interfaces are available
+            Object enhancedService = tryToCreateMetaTypeProvider(service);
+            final String[] interfaceNames;
+            if ( enhancedService == null )
+            {
+                interfaceNames = new String[] {ManagedService.class.getName()};
+            }
+            else
+            {
+                interfaceNames = new String[] {ManagedService.class.getName(), MetaTypeProvider.class.getName()};
+                service = enhancedService;
+            }
+            Dictionary props = new Hashtable();
+            props.put( Constants.SERVICE_PID, PID );
+            bundleContext.registerService( interfaceNames, service, props );
+        }
+        catch ( Throwable t )
+        {
+            // don't care
+        }
+    }
+
+    /**
+     * Configures this instance.
+     */
+    void configure( Dictionary config )
+    {
+        if ( config == null )
+        {
+            // 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.
+            cacheSize = getIntProperty(PROP_CACHE_SIZE,
+                this.bundleContext, 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.
+            threadPoolSize = getIntProperty(
+                PROP_THREAD_POOL_SIZE, this.bundleContext, 20, 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.
+            timeout = getIntProperty(PROP_TIMEOUT,
+                    this.bundleContext, 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=*).
+            requireTopic = getBooleanProperty(
+                PROP_REQUIRE_TOPIC, this.bundleContext, true);
+        }
+        else
+        {
+            cacheSize = getIntProperty(PROP_CACHE_SIZE, config, 30, 10);
+            threadPoolSize = getIntProperty(PROP_THREAD_POOL_SIZE, config, 20, 2);
+            timeout = getIntProperty(PROP_TIMEOUT, config, 5000, Integer.MIN_VALUE);
+            requireTopic = getBooleanProperty(PROP_REQUIRE_TOPIC, config, true);
+        }
+    }
+
+    public synchronized void start()
+    {
+        LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG,
+                PROP_CACHE_SIZE + "=" + cacheSize);
+        LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG,
+            PROP_THREAD_POOL_SIZE + "=" + threadPoolSize);
+        LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG,
+            PROP_TIMEOUT + "=" + timeout);
+        LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG,
+            PROP_REQUIRE_TOPIC + "=" + 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), this.bundleContext);
+
+        // 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(this.bundleContext,
+            new CleanBlackList(), topicHandlerFilters, filters,
+            subscribePermissions);
+
+        // 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_sync_pool = new DefaultThreadPool(threadPoolSize, true);
+        m_async_pool = new DefaultThreadPool(threadPoolSize > 5 ? threadPoolSize / 2 : 2, false);
+
+        final DeliverTask syncExecuter = createSyncExecuters( m_sync_pool, timeout);
+        m_admin = createEventAdmin(this.bundleContext,
+                handlerTasks,
+                createAsyncExecuters(m_async_pool, syncExecuter),
+                syncExecuter);
+
+        // 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
+        m_registration = this.bundleContext.registerService(EventAdmin.class.getName(),
+            new SecureEventAdminFactory(m_admin, publishPermissions), null);
+
+        // Finally, adapt the outside events to our kind of events as per spec
+        adaptEvents(this.bundleContext, m_admin);
+    }
+
+    /**
+     * Called to stop the event admin and restart it.
+     */
+    public synchronized void stop()
+    {
+        // We need to unregister manually
+        if ( m_registration != null )
+        {
+            m_registration.unregister();
+            m_registration = null;
+        }
+        if ( m_admin != null )
+        {
+            m_admin.stop();
+            m_admin = null;
+        }
+        if ( m_async_pool != null )
+        {
+            m_async_pool.close();
+            m_async_pool = null;
+        }
+        if ( m_sync_pool != null )
+        {
+            m_sync_pool.close();
+            m_sync_pool = null;
+        }
+    }
+
+    /**
+     * Called upon stopping the bundle. This will block until all pending events are
+     * delivered. An IllegalStateException will be thrown on new events starting with
+     * the begin of this method. However, it might take some time until we settle
+     * down which is somewhat cumbersome given that the spec asks for return in
+     * a timely manner.
+     */
+    public synchronized void destroy()
+    {
+        if ( this.adapters != null )
+        {
+            for(int i=0;i<adapters.length;i++)
+            {
+                adapters[i].destroy(bundleContext);
+            }
+            adapters = null;
+        }
+    }
+
+    /**
+     * Create a event admin implementation.
+     * @param context      The bundle context
+     * @param handlerTasks
+     * @param asyncExecuters
+     * @param syncExecuters
+     * @return
+     */
+    protected EventAdminImpl createEventAdmin(BundleContext context,
+                                              HandlerTasks handlerTasks,
+                                              DeliverTask asyncExecuters,
+                                              DeliverTask syncExecuters)
+    {
+        return new EventAdminImpl(handlerTasks, asyncExecuters, syncExecuters);
+    }
+
+    /*
+     * 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 DeliverTask createAsyncExecuters(final ThreadPool pool, final DeliverTask deliverTask)
+    {
+        // init the queue
+        final AsyncDeliverTasks result = new AsyncDeliverTasks(pool, deliverTask);
+
+        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 DeliverTask createSyncExecuters(final ThreadPool pool, final long timeout)
+    {
+        // init the queue
+        final SyncDeliverTasks result = new SyncDeliverTasks(pool, (timeout > 100 ? timeout : 0));
+
+        return result;
+    }
+
+    /*
+     * Init the adapters in org.apache.felix.eventadmin.impl.adapter
+     */
+    private void adaptEvents(final BundleContext context, final EventAdmin admin)
+    {
+        if ( adapters == null )
+        {
+            adapters = new AbstractAdapter[4];
+            adapters[0] = new FrameworkEventAdapter(context, admin);
+            adapters[1] = new BundleEventAdapter(context, admin);
+            adapters[2] = new ServiceEventAdapter(context, admin);
+            adapters[3] = new LogEventAdapter(context, admin);
+        }
+        else
+        {
+            for(int i=0; i<adapters.length; i++)
+            {
+                adapters[i].update(admin);
+            }
+        }
+    }
+
+    public int getCacheSize()
+    {
+        return cacheSize;
+    }
+
+    public int getThreadPoolSize()
+    {
+        return threadPoolSize;
+    }
+
+    public int getTimeout()
+    {
+        return timeout;
+    }
+
+    public boolean getRequireTopic()
+    {
+        return requireTopic;
+    }
+
+    private Object tryToCreateMetaTypeProvider(final Object managedService)
+    {
+        try
+        {
+            return new MetaTypeProviderImpl(this, (ManagedService)managedService);
+        } catch (Throwable t)
+        {
+            // we simply ignore this
+        }
+        return null;
+    }
+
+    /**
+     * 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 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 Dictionary dict,
+        final int defaultValue, final int min)
+    {
+        final Object value = dict.get(key);
+
+        if(null != value)
+        {
+            final int result;
+            if ( value instanceof Integer )
+            {
+                result = ((Integer)value).intValue();
+            }
+            else
+            {
+                try
+                {
+                    result = Integer.parseInt(value.toString());
+                }
+                catch (NumberFormatException e)
+                {
+                    LogWrapper.getLogger().log(LogWrapper.LOG_WARNING,
+                        "Unable to parse property: " + key + " - Using default", e);
+                    return defaultValue;
+                }
+            }
+            if(result >= min)
+            {
+                return result;
+            }
+
+            LogWrapper.getLogger().log(LogWrapper.LOG_WARNING,
+                    "Value for property: " + key + " is to low - Using default");
+        }
+
+        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;
+    }
+
+    /**
+     * 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 Dictionary dict,
+        final boolean defaultValue)
+    {
+        Object obj = dict.get(key);
+
+        if(null != obj)
+        {
+            if ( obj instanceof Boolean )
+            {
+                return ((Boolean)obj).booleanValue();
+            }
+            String value = obj.toString().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/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/MetaTypeProviderImpl.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/MetaTypeProviderImpl.java
new file mode 100644
index 0000000..cb5e88f
--- /dev/null
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/MetaTypeProviderImpl.java
@@ -0,0 +1,242 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.eventadmin.impl;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Dictionary;
+
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.metatype.*;
+
+/**
+ * The optional meta type provider for the event admin config.
+ */
+public class MetaTypeProviderImpl
+    implements MetaTypeProvider, ManagedService
+{
+    private final int cacheSize;
+    private final int threadPoolSize;
+    private final int timeout;
+    private final boolean requireTopic;
+
+    private final ManagedService delegatee;
+
+    public MetaTypeProviderImpl(final Configuration config,
+                                final ManagedService delegatee)
+    {
+        this.cacheSize = config.getCacheSize();
+        this.threadPoolSize = config.getThreadPoolSize();
+        this.timeout = config.getTimeout();
+        this.requireTopic = config.getRequireTopic();
+        this.delegatee = delegatee;
+    }
+
+    private ObjectClassDefinition ocd;
+
+    public void updated(Dictionary properties) throws ConfigurationException
+    {
+        this.delegatee.updated(properties);
+    }
+
+    /**
+     * @see org.osgi.service.metatype.MetaTypeProvider#getLocales()
+     */
+    public String[] getLocales()
+    {
+        return null;
+    }
+
+    /**
+     * @see org.osgi.service.metatype.MetaTypeProvider#getObjectClassDefinition(java.lang.String, java.lang.String)
+     */
+    public ObjectClassDefinition getObjectClassDefinition( String id, String locale )
+    {
+        if ( !Configuration.PID.equals( id ) )
+        {
+            return null;
+        }
+
+        if ( ocd == null )
+        {
+            final ArrayList adList = new ArrayList();
+
+            adList.add( new AttributeDefinitionImpl( Configuration.PROP_CACHE_SIZE, "Cache Size",
+                    "The size of various internal caches. The default value is 30. Increase in case " +
+                    "of a large number (more then 100) of services. A value less then 10 triggers the " +
+                    "default value.", this.cacheSize) );
+
+            adList.add( new AttributeDefinitionImpl( Configuration.PROP_THREAD_POOL_SIZE, "Thread Pool Size",
+                "The size of the thread pool. The default value is 10. Increase in case of a large amount " +
+                "of synchronous events where the event handler 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.",
+                this.threadPoolSize ) );
+
+            adList.add( new AttributeDefinitionImpl( Configuration.PROP_TIMEOUT, "Timeout",
+                    "The black-listing timeout in milliseconds. 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 event handler before it gets blacklisted",
+                    this.timeout ) );
+
+            adList.add( new AttributeDefinitionImpl( Configuration.PROP_REQUIRE_TOPIC, "Require Topic",
+                    "Are event handlers required to be registered with a topic? " +
+                    "This is enabled by default. The specification says that event handlers " +
+                    "must register with a list of topics they are interested in. Disabling this setting " +
+                    "will enable that handlers without a topic are receiving all events " +
+                    "(i.e., they are treated the same as with a topic=*).",
+                    this.requireTopic ) );
+
+            ocd = new ObjectClassDefinition()
+            {
+
+                private final AttributeDefinition[] attrs = ( AttributeDefinition[] ) adList
+                    .toArray( new AttributeDefinition[adList.size()] );
+
+
+                public String getName()
+                {
+                    return "Apache Felix Event Admin Implementation";
+                }
+
+
+                public InputStream getIcon( int arg0 )
+                {
+                    return null;
+                }
+
+
+                public String getID()
+                {
+                    return Configuration.PID;
+                }
+
+
+                public String getDescription()
+                {
+                    return "Configuration for the Apache Felix Event Admin Implementation." +
+                           " This configuration overwrites configuration defined in framework properties of the same names.";
+                }
+
+
+                public AttributeDefinition[] getAttributeDefinitions( int filter )
+                {
+                    return ( filter == OPTIONAL ) ? null : attrs;
+                }
+            };
+        }
+
+        return ocd;
+    }
+
+    class AttributeDefinitionImpl implements AttributeDefinition
+    {
+
+        private final String id;
+        private final String name;
+        private final String description;
+        private final int type;
+        private final String[] defaultValues;
+        private final int cardinality;
+        private final String[] optionLabels;
+        private final String[] optionValues;
+
+
+        AttributeDefinitionImpl( final String id, final String name, final String description, final boolean defaultValue )
+        {
+            this( id, name, description, BOOLEAN, new String[]
+                { String.valueOf(defaultValue) }, 0, null, null );
+        }
+
+        AttributeDefinitionImpl( final String id, final String name, final String description, final int defaultValue )
+        {
+            this( id, name, description, INTEGER, new String[]
+                { String.valueOf(defaultValue) }, 0, null, null );
+        }
+
+        AttributeDefinitionImpl( final String id, final String name, final String description, final int type,
+            final String[] defaultValues, final int cardinality, final String[] optionLabels,
+            final String[] optionValues )
+        {
+            this.id = id;
+            this.name = name;
+            this.description = description;
+            this.type = type;
+            this.defaultValues = defaultValues;
+            this.cardinality = cardinality;
+            this.optionLabels = optionLabels;
+            this.optionValues = optionValues;
+        }
+
+
+        public int getCardinality()
+        {
+            return cardinality;
+        }
+
+
+        public String[] getDefaultValue()
+        {
+            return defaultValues;
+        }
+
+
+        public String getDescription()
+        {
+            return description;
+        }
+
+
+        public String getID()
+        {
+            return id;
+        }
+
+
+        public String getName()
+        {
+            return name;
+        }
+
+
+        public String[] getOptionLabels()
+        {
+            return optionLabels;
+        }
+
+
+        public String[] getOptionValues()
+        {
+            return optionValues;
+        }
+
+
+        public int getType()
+        {
+            return type;
+        }
+
+
+        public String validate( String arg0 )
+        {
+            return null;
+        }
+    }
+}
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/AbstractAdapter.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/AbstractAdapter.java
new file mode 100644
index 0000000..1d519eb
--- /dev/null
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/AbstractAdapter.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.eventadmin.impl.adapter;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.service.event.EventAdmin;
+
+/**
+ * Abstract base class for all adapters.
+ * This class allows to exchange the event admin at runtime
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public abstract class AbstractAdapter
+{
+    private volatile EventAdmin m_admin;
+
+    /**
+     * The constructor of the adapter.
+     *
+     * @param admin The <tt>EventAdmin</tt> to use for posting events.
+     */
+    public AbstractAdapter(final EventAdmin admin)
+    {
+        update(admin);
+    }
+
+    public void update(final EventAdmin admin)
+    {
+        if (null == admin)
+        {
+            throw new NullPointerException("EventAdmin must not be null");
+        }
+
+        m_admin = admin;
+    }
+
+    protected EventAdmin getEventAdmin()
+    {
+        return m_admin;
+    }
+
+    public abstract void destroy(final BundleContext bundleContext);
+}
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/BundleEventAdapter.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/BundleEventAdapter.java
index 6d80fb0..c636e06 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/BundleEventAdapter.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/BundleEventAdapter.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -31,37 +31,33 @@
 /**
  * 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:dev@felix.apache.org">Felix Project Team</a>
  */
-public class BundleEventAdapter implements BundleListener
+public class BundleEventAdapter extends AbstractAdapter 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 
+     * 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;
-        
+        super(admin);
         context.addBundleListener(this);
     }
-    
+
+    public void destroy(BundleContext context) {
+        context.removeBundleListener(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. 
-     * 
+     * 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)
@@ -114,8 +110,8 @@
         }
 
         try {
-            m_admin.postEvent(new Event(topic.toString(), properties));
-        } catch (IllegalStateException e) { 
+            getEventAdmin().postEvent(new Event(topic.toString(), properties));
+        } catch (IllegalStateException e) {
             // This is o.k. - indicates that we are stopped.
         }
     }
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/FrameworkEventAdapter.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/FrameworkEventAdapter.java
index c7ad2fc..90d8da5 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/FrameworkEventAdapter.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/FrameworkEventAdapter.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -32,37 +32,34 @@
 /**
  * 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:dev@felix.apache.org">Felix Project Team</a>
  */
-public class FrameworkEventAdapter implements FrameworkListener
+public class FrameworkEventAdapter extends AbstractAdapter 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;
-        
+        super(admin);
+
         context.addFrameworkListener(this);
     }
-    
+
+    public void destroy(BundleContext context) {
+        context.removeFrameworkListener(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. 
-     * 
+     * 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)
@@ -92,7 +89,7 @@
 
         if (null != thrown)
         {
-            properties.put(EventConstants.EXCEPTION_CLASS, 
+            properties.put(EventConstants.EXCEPTION_CLASS,
                 thrown.getClass().getName());
 
             final String message = thrown.getMessage();
@@ -135,7 +132,7 @@
         }
 
         try {
-            m_admin.postEvent(new Event(topic.toString(), properties));
+            getEventAdmin().postEvent(new Event(topic.toString(), properties));
         } catch(IllegalStateException e) {
             // This is o.k. - indicates that we are stopped.
         }
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/LogEventAdapter.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/LogEventAdapter.java
index 09918f8..1c48586 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/LogEventAdapter.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/LogEventAdapter.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -36,47 +36,39 @@
 /**
  * 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 
+ * 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. 
- * 
+ * 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:dev@felix.apache.org">Felix Project Team</a>
  */
-public class LogEventAdapter implements ServiceListener
+public class LogEventAdapter extends AbstractAdapter 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 
+     * 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 
+     * <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");
-        }
-        
+        super(admin);
         m_context = context;
-        
-        m_admin = admin;
 
         try
         {
@@ -92,13 +84,13 @@
             {
                 for (int i = 0; i < refs.length; i++)
                 {
-                    final org.osgi.service.log.LogReaderService logReader = 
+                    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) 
+                        logReader.addLogListener((org.osgi.service.log.LogListener)
                             getLogListener());
                     }
                 }
@@ -109,32 +101,36 @@
         }
     }
 
+    public void destroy(BundleContext context) {
+        context.removeServiceListener(this);
+    }
+
     /**
      * 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. 
-     * 
+     * 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 = 
+            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) 
+                logReader.addLogListener((org.osgi.service.log.LogListener)
                     getLogListener());
             }
         }
     }
 
     /*
-     * Constructs a LogListener that assembles and posts any log event via the 
+     * 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.
@@ -153,9 +149,9 @@
                 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 
+                    // 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();
@@ -224,7 +220,7 @@
                                 // LOG and IGNORE
                                 LogWrapper.getLogger().log(
                                     entry.getServiceReference(),
-                                    LogWrapper.LOG_WARNING, "Exception parsing " + 
+                                    LogWrapper.LOG_WARNING, "Exception parsing " +
                                     EventConstants.SERVICE_ID + "=" + id, ne);
                             }
                         }
@@ -234,7 +230,7 @@
 
                         if (null != pid)
                         {
-                            properties.put(EventConstants.SERVICE_PID, 
+                            properties.put(EventConstants.SERVICE_PID,
                                 pid.toString());
                         }
 
@@ -282,13 +278,13 @@
                     }
 
                     try {
-                        m_admin.postEvent(new Event(topic.toString(), properties));
+                        getEventAdmin().postEvent(new Event(topic.toString(), properties));
                     } catch(IllegalStateException e) {
                         // This is o.k. - indicates that we are stopped.
                     }
                 }
             };
-            
+
             return m_logListener;
         }
     }
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/ServiceEventAdapter.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/ServiceEventAdapter.java
index 4e365a8..7482e13 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/ServiceEventAdapter.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/ServiceEventAdapter.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -33,32 +33,34 @@
 /**
  * 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:dev@felix.apache.org">Felix Project Team</a>
  */
-public class ServiceEventAdapter implements ServiceListener
+public class ServiceEventAdapter extends AbstractAdapter 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;
-        
+        super(admin);
+
         context.addServiceListener(this);
     }
 
+    public void destroy(BundleContext context) {
+        context.removeServiceListener(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. 
-     * 
+     * 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)
@@ -83,7 +85,7 @@
             {
                 // LOG and IGNORE
                 LogWrapper.getLogger().log(event.getServiceReference(),
-                    LogWrapper.LOG_WARNING, "Exception parsing " + 
+                    LogWrapper.LOG_WARNING, "Exception parsing " +
                     EventConstants.SERVICE_ID + "=" + id, ne);
             }
         }
@@ -132,7 +134,7 @@
         }
 
         try {
-            m_admin.postEvent(new Event(topic.toString(), properties));
+            getEventAdmin().postEvent(new Event(topic.toString(), properties));
         } catch(IllegalStateException e) {
             // This is o.k. - indicates that we are stopped.
         }