Merge pull request #37 from rovarga/felix6315

FELIX-6315: do not deactivate factory component configurations
diff --git a/README.md b/README.md
index 25c2cc6..b668aa1 100644
--- a/README.md
+++ b/README.md
@@ -49,6 +49,7 @@
 - **ipojo** `/ipojo` - A *service component runtime* aiming to simplify OSGi application development.
 - **jaas support** `/jaas` - Bundle to simplify JAAS usage within OSGi environment.
 - **logback** `/logback` - A simple integration of the OSGi R7 Log (1.4) service to Logback backend.
+- **OSGi metrics** `/metrics/osgi` - Collecting and publishing metrics related to OSGi applications
 - **rootcause** `/rootcause` - Finding the root cause of problems with OSGi declarative services components.
 - **utils** `/utils` - Utility classes for OSGi (intended for embedding within other bundles.)
 - **webconsole** `/webconsole*` - Web Based Management Console for OSGi Frameworks.
diff --git a/eventadmin/impl/changelog.txt b/eventadmin/impl/changelog.txt
index f9241c2..570d04b 100644
--- a/eventadmin/impl/changelog.txt
+++ b/eventadmin/impl/changelog.txt
@@ -1,3 +1,12 @@
+Changes in 1.6.0
+----------------
+** Improvement
+    * [FELIX-4678] - Make list of denied event handlers available via JMX
+** Task
+    * [FELIX-6329] - Update event admin to Java 8
+    * [FELIX-6330] - Use term deny list for event handlers hitting the timeout
+
+
 Changes in 1.5.0
 ----------------
 ** Task
diff --git a/eventadmin/impl/pom.xml b/eventadmin/impl/pom.xml
index b63f9c9..9e2378f 100644
--- a/eventadmin/impl/pom.xml
+++ b/eventadmin/impl/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.apache.felix</groupId>
         <artifactId>felix-parent</artifactId>
-        <version>6</version>
+        <version>7</version>
         <relativePath>../../pom/pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -29,7 +29,7 @@
     <description>
 	    This bundle provides an implementation of the OSGi R7 EventAdmin service.
     </description>
-    <version>1.5.1-SNAPSHOT</version>
+    <version>1.6.0-SNAPSHOT</version>
     <artifactId>org.apache.felix.eventadmin</artifactId>
     <scm>
       <connection>scm:git:https://github.com/apache/felix-dev.git</connection>
@@ -83,38 +83,38 @@
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
-            <version>4.12</version>
+            <version>4.13</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.jmock</groupId>
             <artifactId>jmock-junit4</artifactId>
-            <version>2.5.1</version>
+            <version>2.12.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-simple</artifactId>
-            <version>1.5.2</version>
+            <version>1.7.5</version>
             <scope>test</scope>
         </dependency>
       <!-- Integration Testing with Pax Exam -->
         <dependency>
             <groupId>org.ops4j.pax.exam</groupId>
             <artifactId>pax-exam-container-forked</artifactId>
-            <version>4.11.0</version>
+            <version>4.13.1</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.ops4j.pax.exam</groupId>
             <artifactId>pax-exam-junit4</artifactId>
-            <version>4.11.0</version>
+            <version>4.13.1</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.ops4j.pax.exam</groupId>
             <artifactId>pax-exam-link-mvn</artifactId>
-            <version>4.11.0</version>
+            <version>4.13.1</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -132,13 +132,13 @@
         <dependency>
             <groupId>org.apache.geronimo.specs</groupId>
             <artifactId>geronimo-atinject_1.0_spec</artifactId>
-            <version>1.0</version>
+            <version>1.2</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.framework</artifactId>
-            <version>5.6.10</version>
+            <version>6.0.3</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
@@ -152,7 +152,7 @@
             <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
-                <version>3.5.0</version>
+                <version>5.1.1</version>
                 <extensions>true</extensions>
                 <configuration>
                     <instructions>
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 39378ad..53fdfaa 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
@@ -25,7 +25,7 @@
 /**
  * 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,
+ * framework. It features timeout-based denying of event-handlers for both,
  * asynchronous and synchronous event-dispatching (as a spec conform optional
  * extension).
  *
@@ -51,6 +51,7 @@
      *
      * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
      */
+    @Override
     public void start(final BundleContext context)
     {
         // init the LogWrapper. Subsequently, the static methods of the LogWrapper
@@ -76,6 +77,7 @@
      *
      * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
      */
+    @Override
     public void stop(final BundleContext context)
     {
         if ( 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
index 7ca1dbf..e0faaf9 100644
--- 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
@@ -61,12 +61,12 @@
  * </p>
  * <p>
  * <p>
- *      <tt>org.apache.felix.eventadmin.Timeout</tt> - The black-listing timeout in
+ *      <tt>org.apache.felix.eventadmin.Timeout</tt> - The deny-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.
+ * to each <tt>EventHandler</tt> before it gets put on the denylist.
  * </p>
  * <p>
  * <p>
@@ -167,12 +167,15 @@
     private volatile EventAdminImpl m_admin;
 
     // The registration of the security decorator factory (i.e., the service)
-    private volatile ServiceRegistration m_registration;
+    private volatile ServiceRegistration<EventAdmin> m_registration;
+
+    // The registration of the mbean
+    private volatile ServiceRegistration<Object> m_mbeanreg;
 
     // all adapters
     private AbstractAdapter[] m_adapters;
 
-    private ServiceRegistration m_managedServiceReg;
+    private ServiceRegistration<?> m_managedServiceReg;
 
     // the access control context
     private final AccessControlContext acc;
@@ -285,7 +288,7 @@
 
             // 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.
+            // before it gets denied.
             m_timeout = getIntProperty(PROP_TIMEOUT,
                     m_bundleContext.getProperty(PROP_TIMEOUT), 5000, Integer.MIN_VALUE);
 
@@ -434,8 +437,13 @@
             // 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 = m_bundleContext.registerService(EventAdmin.class.getName(),
+            m_registration = m_bundleContext.registerService(EventAdmin.class,
                     new SecureEventAdminFactory(m_admin), null);
+
+            final Dictionary<String, Object> mbeanProps = new Hashtable<>();
+            mbeanProps.put("jmx.objectname", "org.apache.felix.eventadmin:type=handlerinfo,name=EventAdmin");
+
+            m_mbeanreg = m_bundleContext.registerService(Object.class, m_admin.getHandlerInfoMBean(), mbeanProps);
         }
         else
         {
@@ -469,6 +477,10 @@
                 m_managedServiceReg = null;
             }
             // We need to unregister manually
+            if ( m_mbeanreg != null ) {
+                m_mbeanreg.unregister();
+                m_mbeanreg = null;
+            }
             if ( m_registration != null )
             {
                 m_registration.unregister();
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
index 46830fe..0c5e343 100644
--- 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
@@ -111,9 +111,9 @@
                     m_asyncThreadPoolRatio));
 
             adList.add( new AttributeDefinitionImpl( Configuration.PROP_TIMEOUT, "Timeout",
-                    "The black-listing timeout in milliseconds. The default value is 5000. Increase or decrease " +
+                    "The deny-list 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",
+                    "in milliseconds granted to each event handler before it gets denied",
                     m_timeout ) );
 
             adList.add( new AttributeDefinitionImpl( Configuration.PROP_REQUIRE_TOPIC, "Require Topic",
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 58106c8..5719999 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
@@ -68,8 +68,7 @@
 
         properties.put(EventConstants.EVENT, event);
 
-        properties.put("bundle.id", new Long(event.getBundle()
-            .getBundleId()));
+        properties.put("bundle.id", event.getBundle().getBundleId());
 
         final String symbolicName = event.getBundle().getSymbolicName();
 
@@ -81,7 +80,7 @@
 
         properties.put("bundle", event.getBundle());
 
-        final StringBuffer topic = new StringBuffer(BundleEvent.class
+        final StringBuilder topic = new StringBuilder(BundleEvent.class
             .getName().replace('.', '/')).append('/');
 
         switch (event.getType())
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 a5a55f4..9a971d3 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
@@ -66,7 +66,7 @@
     @Override
     public void frameworkEvent(final FrameworkEvent event)
     {
-        final Dictionary<String, Object> properties = new Hashtable<String, Object>();
+        final Dictionary<String, Object> properties = new Hashtable<>();
 
         properties.put(EventConstants.EVENT, event);
 
@@ -74,7 +74,7 @@
 
         if (null != bundle)
         {
-            properties.put("bundle.id", new Long(bundle.getBundleId()));
+            properties.put("bundle.id", bundle.getBundleId());
 
             final String symbolicName = bundle.getSymbolicName();
 
@@ -105,7 +105,7 @@
             properties.put(EventConstants.EXCEPTION, thrown);
         }
 
-        final StringBuffer topic = new StringBuffer(
+        final StringBuilder topic = new StringBuilder(
             FrameworkEvent.class.getName().replace('.', '/'))
             .append('/');
 
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 7cce20e..84dcd70 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
@@ -21,7 +21,6 @@
 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;
@@ -75,7 +74,7 @@
             m_context.addServiceListener(this, "(" + Constants.OBJECTCLASS
                 + "=org.osgi.service.log.LogReaderService)");
 
-            final ServiceReference[] refs;
+            final ServiceReference<?>[] refs;
 
             refs = m_context.getServiceReferences(
                 "org.osgi.service.log.LogReaderService", null);
@@ -161,11 +160,9 @@
 
                     if (null != bundle)
                     {
-                        properties.put("bundle.id", new Long(bundle
-                            .getBundleId()));
+                        properties.put("bundle.id", bundle.getBundleId());
 
                         final String symbolicName = bundle.getSymbolicName();
-
                         if (null != symbolicName)
                         {
                             properties.put(EventConstants.BUNDLE_SYMBOLICNAME,
@@ -175,13 +172,12 @@
                         properties.put("bundle", bundle);
                     }
 
-                    properties.put("log.level", new Integer(entry.getLevel()));
+                    properties.put("log.level", entry.getLevel());
 
-		    properties.put(EventConstants.MESSAGE,
-				   (entry.getMessage()) != null ? entry.getMessage() : "" );
+		            properties.put(EventConstants.MESSAGE,
+				        (entry.getMessage()) != null ? entry.getMessage() : "" );
 
-                    properties.put(EventConstants.TIMESTAMP, new Long(
-                        entry.getTime()));
+                    properties.put(EventConstants.TIMESTAMP, entry.getTime());
 
                     properties.put("log.entry", entry);
 
@@ -203,62 +199,25 @@
                         properties.put(EventConstants.EXCEPTION, exception);
                     }
 
-                    final ServiceReference service = entry
-                        .getServiceReference();
+                    final ServiceReference<?> service = entry.getServiceReference();
 
                     if (null != service)
                     {
                         properties.put(EventConstants.SERVICE, service);
+                        properties.put(EventConstants.SERVICE_ID, service.getProperty(EventConstants.SERVICE_ID));
+                        properties.put(
+                                EventConstants.SERVICE_OBJECTCLASS,
+                                service.getProperty(Constants.OBJECTCLASS));
 
-                        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);
-
+                        final Object pid = service.getProperty(EventConstants.SERVICE_PID);
                         if (null != pid)
                         {
-                            properties.put(EventConstants.SERVICE_PID,
-                                pid.toString());
+                            properties.put(EventConstants.SERVICE_PID, pid);
                         }
 
-                        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(
+                    final StringBuilder topic = new StringBuilder(
                         org.osgi.service.log.LogEntry.class.getName().replace(
                             '.', '/')).append('/');
 
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 cf55b8b..74ab293 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
@@ -21,7 +21,6 @@
 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;
@@ -71,53 +70,22 @@
 
         properties.put(EventConstants.EVENT, event);
 
-        properties.put(EventConstants.SERVICE, event
-            .getServiceReference());
+        properties.put(EventConstants.SERVICE, event.getServiceReference());
 
-        final Object id = event.getServiceReference().getProperty(
-            EventConstants.SERVICE_ID);
+        properties.put(EventConstants.SERVICE_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);
-            }
-        }
+        properties.put(EventConstants.SERVICE_OBJECTCLASS,
+                event.getServiceReference().getProperty(Constants.OBJECTCLASS));
 
         final Object pid = event.getServiceReference().getProperty(
-            EventConstants.SERVICE_PID);
-
+                EventConstants.SERVICE_PID);
         if (null != pid)
         {
-            properties.put(EventConstants.SERVICE_PID, pid.toString());
+            properties.put(EventConstants.SERVICE_PID, pid);
         }
 
-        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
+        final StringBuilder topic = new StringBuilder(ServiceEvent.class
             .getName().replace('.', '/')).append('/');
 
         switch (event.getType())
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventAdminImpl.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventAdminImpl.java
index 249bbea..05977c0 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventAdminImpl.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventAdminImpl.java
@@ -18,6 +18,9 @@
  */
 package org.apache.felix.eventadmin.impl.handler;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.apache.felix.eventadmin.impl.tasks.AsyncDeliverTasks;
 import org.apache.felix.eventadmin.impl.tasks.DefaultThreadPool;
 import org.apache.felix.eventadmin.impl.tasks.SyncDeliverTasks;
@@ -184,4 +187,24 @@
             throw new NullPointerException(name + " may not be null");
         }
     }
+
+    public interface EventHandlerMBean {
+
+        String[] getDeniedEventHandlers();
+    }
+
+    public Object getHandlerInfoMBean() {
+        return new EventHandlerMBean() {
+
+            @Override
+            public String[] getDeniedEventHandlers() {
+                final List<String> names = new ArrayList<>();
+                for(final EventHandlerProxy p : tracker.getDeniedHandlers()) {
+                    names.add(p.getInfo());
+                }
+
+                return names.toArray(new String[names.size()]);
+            }
+        };
+    }
 }
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerProxy.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerProxy.java
index 10adfc2..5ff9938 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerProxy.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerProxy.java
@@ -36,7 +36,7 @@
  * on demand and prepares some information for faster processing.
  *
  * It checks the timeout handling for the implementation as well as
- * blacklisting the handler.
+ * putting the handler on the deny list.
  *
  * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
  */
@@ -57,8 +57,8 @@
     /** Lazy fetched event handler. */
     private volatile EventHandler handler;
 
-    /** Is this handler blacklisted? */
-    private volatile boolean blacklisted;
+    /** Is this handler denied? */
+    private volatile boolean denied;
 
     /** Use timeout. */
     private boolean useTimeout;
@@ -85,7 +85,7 @@
      */
     public boolean update()
     {
-        this.blacklisted = false;
+        this.denied = false;
         boolean valid = true;
         // First check, topic
         final Object topicObj = reference.getProperty(EventConstants.EVENT_TOPIC);
@@ -268,6 +268,13 @@
     }
 
     /**
+     * Get some info about the event handler
+     */
+    public String getInfo() {
+        return this.reference.toString() + " [Bundle " + this.reference.getBundle() + "]";
+    }
+
+    /**
      * Dispose the proxy and release the handler
      */
     public void dispose()
@@ -327,13 +334,13 @@
 
     /**
      * Check if this handler is allowed to receive the event
-     * - blacklisted
+     * - denied
      * - check filter
      * - check permission
      */
     public boolean canDeliver(final Event event)
     {
-        if ( this.blacklisted )
+        if ( this.denied )
         {
             return false;
         }
@@ -427,19 +434,23 @@
     }
 
     /**
-     * Blacklist the handler.
+     * Deny the handler.
      */
-    public void blackListHandler()
+    public void denyEventHandler()
     {
-    	if(!this.blacklisted)
+    	if(!this.denied)
     	{
 	        LogWrapper.getLogger().log(
 	                        LogWrapper.LOG_WARNING,
-	                        "Blacklisting ServiceReference [" + this.reference + " | Bundle("
+	                        "Denying event handler from ServiceReference [" + this.reference + " | Bundle("
 	                                        + this.reference.getBundle() + ")] due to timeout!");
-	        this.blacklisted = true;
+	        this.denied = true;
 	        // we can free the handler now.
 	        this.release();
     	}
     }
+
+    public boolean isDenied() {
+        return this.denied;
+    }
 }
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerTracker.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerTracker.java
index 8979a0e..7c3c5fd 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerTracker.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerTracker.java
@@ -224,6 +224,39 @@
 		return handlers;
 	}
 
+	   /**
+     * Get all handlers for this event
+     *
+     * @param event The event topic
+     * @return All handlers for the event
+     */
+    public Collection<EventHandlerProxy> getDeniedHandlers() {
+        final Set<EventHandlerProxy> handlers = new HashSet<>();
+
+        for(final EventHandlerProxy p : this.matchingAllEvents) {
+            if ( p.isDenied() ) {
+                handlers.add(p);
+            }
+        }
+
+        for(final List<EventHandlerProxy> l : this.matchingPrefixTopic.values()) {
+            for(final EventHandlerProxy p :l) {
+                if ( p.isDenied() ) {
+                    handlers.add(p);
+                }
+            }
+        }
+        for(final List<EventHandlerProxy> l : this.matchingTopic.values()) {
+            for(final EventHandlerProxy p :l) {
+                if ( p.isDenied() ) {
+                    handlers.add(p);
+                }
+            }
+        }
+
+        return handlers;
+    }
+
 	/**
 	 * Checks each handler from the proxy list if it can deliver the event
 	 * If the event can be delivered, the proxy is added to the handlers.
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/BlacklistLatch.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/DenylistLatch.java
similarity index 79%
rename from eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/BlacklistLatch.java
rename to eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/DenylistLatch.java
index 2a844ec..4d5d1b4 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/BlacklistLatch.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/DenylistLatch.java
@@ -28,10 +28,10 @@
 
 /**
  *
- * A latch that checks handlers for blacklisting on an interval.
+ * A latch that checks handlers for denying on an interval.
  *
  */
-public class BlacklistLatch {
+public class DenylistLatch {
 
 	private final Semaphore internalSemaphore;
 
@@ -43,15 +43,15 @@
 
 	/**
 	 * @param count Number of handlers that must call countdown
-	 * @param timeout Timeout in Milliseconds to check for blacklisting handlers
+	 * @param timeout Timeout in Milliseconds to check for denying handlers
 	 */
-	public BlacklistLatch(final int count, final long timeout)
+	public DenylistLatch(final int count, final long timeout)
 	{
 		this.handlerTasks = new ArrayList<HandlerTask>(count);
 		this.count = count;
 		this.timeout = timeout;
-		internalSemaphore = new Semaphore(count);
-		internalSemaphore.drainPermits();
+		this.internalSemaphore = new Semaphore(count);
+		this.internalSemaphore.drainPermits();
 	}
 
 	/**
@@ -66,11 +66,11 @@
 
 	/**
 	 *
-	 * Adds a handler task to the timeout based blackout checking.
+	 * Adds a handler task to the timeout based deny list checking.
 	 *
 	 * @param task
 	 */
-	public void addToBlacklistCheck(final HandlerTask task)
+	public void addToDenylistCheck(final HandlerTask task)
 	{
 		this.handlerTasks.add(task);
 	}
@@ -78,10 +78,10 @@
 	/**
 	 *
 	 * Causes current thread to wait until each handler has called countDown.
-	 * Checks on timeout interval to determine if a handler needs blacklisting.
+	 * Checks on timeout interval to determine if a handler needs deny listing.
 	 *
 	 */
-	public void awaitAndBlacklistCheck()
+	public void awaitAndDenylistCheck()
 	{
 		try
         {
@@ -91,7 +91,7 @@
             	while(handlerTaskIt.hasNext())
             	{
             		HandlerTask currentTask = handlerTaskIt.next();
-            		currentTask.checkForBlacklist();
+            		currentTask.checkForDenylist();
             	}
             }
         }
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java
index db39165..bf9b0d4 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java
@@ -33,7 +33,7 @@
 
 	private final long timeout;
 
-	private final BlacklistLatch handlerLatch;
+	private final DenylistLatch handlerLatch;
 
 	private volatile long startTime;
 
@@ -44,10 +44,10 @@
 	 *
 	 * @param task Proxy to the event handler
 	 * @param event The event to send to the handler
-	 * @param timeout Timeout for handler blacklisting
+	 * @param timeout Timeout for handler denying
 	 * @param handlerLatch The latch used to ensure events fire in proper order
 	 */
-	public HandlerTask(final EventHandlerProxy task, final Event event, final long timeout, final BlacklistLatch handlerLatch)
+	public HandlerTask(final EventHandlerProxy task, final Event event, final long timeout, final DenylistLatch handlerLatch)
 	{
 		this.task = task;
 		this.event = event;
@@ -69,7 +69,7 @@
             // execute the task
             task.sendEvent(event);
             endTime = System.currentTimeMillis();
-            checkForBlacklist();
+            checkForDenylist();
         }
         finally
         {
@@ -77,7 +77,7 @@
         }
     }
 
-    public void runWithoutBlacklistTiming()
+    public void runWithoutDenylistTiming()
     {
     	task.sendEvent(event);
     	handlerLatch.countDown();
@@ -98,14 +98,14 @@
     }
 
     /**
-     * Check to see if we need to blacklist this handler
+     * Check to see if we need to deny this handler
      *
      */
-    public void checkForBlacklist()
+    public void checkForDenylist()
     {
     	if (useTimeout() && getTaskTime() > this.timeout)
 		{
-			task.blackListHandler();
+			task.denyEventHandler();
 		}
     }
 
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java
index 120fc23..abec088 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java
@@ -91,7 +91,7 @@
         final SyncThread syncThread = sleepingThread instanceof SyncThread ? (SyncThread)sleepingThread : null;
 
         final Iterator<EventHandlerProxy> i = tasks.iterator();
-        final BlacklistLatch handlerLatch = new BlacklistLatch(tasks.size(), this.timeout/2);
+        final DenylistLatch handlerLatch = new DenylistLatch(tasks.size(), this.timeout/2);
 
         while ( i.hasNext() )
         {
@@ -101,7 +101,7 @@
 //            {
                 if( !handlerTask.useTimeout() )
                 {
-                	handlerTask.runWithoutBlacklistTiming();
+                	handlerTask.runWithoutDenylistTiming();
                 }
             	else if ( syncThread != null  )
                 {
@@ -112,7 +112,7 @@
                 else
                 {
 
-                	handlerLatch.addToBlacklistCheck(handlerTask);
+                	handlerLatch.addToDenylistCheck(handlerTask);
                     if ( !this.pool.executeTask(handlerTask) )
                     {
                         // scheduling failed: last resort, call directly
@@ -122,7 +122,7 @@
 
 //            }
         }
-        handlerLatch.awaitAndBlacklistCheck();
+        handlerLatch.awaitAndDenylistCheck();
 
     }
 }
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/util/LogWrapper.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/util/LogWrapper.java
index 1a94ae7..9b0b22e 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/util/LogWrapper.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/util/LogWrapper.java
@@ -85,7 +85,7 @@
     public static final int LOG_DEBUG = 4;
 
     // A set containing the currently available LogServices. Furthermore used as lock
-    private final Set<ServiceReference> m_loggerRefs = new HashSet<ServiceReference>();
+    private final Set<ServiceReference<?>> 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.
@@ -173,7 +173,7 @@
                 logWrapper.m_logServiceListener = listener;
 
                 // Add all available LogService references to the singleton.
-                final ServiceReference[] refs = context.getServiceReferences( "org.osgi.service.log.LogService", null );
+                final ServiceReference<?>[] refs = context.getServiceReferences( "org.osgi.service.log.LogService", null );
 
                 if ( null != refs )
                 {
@@ -213,7 +213,7 @@
     /*
      * Add a reference to a newly available LogService
      */
-    void addLoggerRef( final ServiceReference ref )
+    void addLoggerRef( final ServiceReference<?> ref )
     {
         synchronized (m_loggerRefs)
         {
@@ -255,9 +255,9 @@
             {
                 // There is at least one LogService available hence, we can use the
                 // class as well.
-                for (Iterator<ServiceReference> iter = m_loggerRefs.iterator(); iter.hasNext();)
+                for (Iterator<ServiceReference<?>> iter = m_loggerRefs.iterator(); iter.hasNext();)
                 {
-                    final ServiceReference next = iter.next();
+                    final ServiceReference<?> next = iter.next();
 
                     org.osgi.service.log.LogService logger =
                         (org.osgi.service.log.LogService) m_context.getService(next);
@@ -307,9 +307,9 @@
             {
                 // There is at least one LogService available hence, we can use the
                 // class as well.
-                for (Iterator<ServiceReference> iter = m_loggerRefs.iterator(); iter.hasNext();)
+                for (Iterator<ServiceReference<?>> iter = m_loggerRefs.iterator(); iter.hasNext();)
                 {
-                    final ServiceReference next = iter.next();
+                    final ServiceReference<?> next = iter.next();
 
                     org.osgi.service.log.LogService logger =
                         (org.osgi.service.log.LogService) m_context.getService(next);
@@ -343,7 +343,7 @@
      * @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)
+    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)
@@ -359,9 +359,9 @@
             {
                 // There is at least one LogService available hence, we can use the
                 // class as well.
-                for (Iterator<ServiceReference> iter = m_loggerRefs.iterator(); iter.hasNext();)
+                for (Iterator<ServiceReference<?>> iter = m_loggerRefs.iterator(); iter.hasNext();)
                 {
-                    final ServiceReference next = iter.next();
+                    final ServiceReference<?> next = iter.next();
 
                     org.osgi.service.log.LogService logger =
                         (org.osgi.service.log.LogService) m_context.getService(next);
@@ -396,7 +396,7 @@
      * @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,
+    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.
@@ -413,9 +413,9 @@
             {
                 // There is at least one LogService available hence, we can use the
                 // class as well.
-                for (Iterator<ServiceReference> iter = m_loggerRefs.iterator(); iter.hasNext();)
+                for (Iterator<ServiceReference<?>> iter = m_loggerRefs.iterator(); iter.hasNext();)
                 {
-                       final ServiceReference next = iter.next();
+                       final ServiceReference<?> next = iter.next();
 
                     org.osgi.service.log.LogService logger =
                         (org.osgi.service.log.LogService) m_context.getService(next);
@@ -445,7 +445,7 @@
      * 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,
+    private void _log(final ServiceReference<?> sr, final int level, final String msg,
         Throwable ex)
     {
         String s = (sr == null) ? null : "SvcRef " + sr;
diff --git a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/AbstractTest.java b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/AbstractTest.java
index a952362..325c0da 100644
--- a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/AbstractTest.java
+++ b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/AbstractTest.java
@@ -23,7 +23,6 @@
 import static org.ops4j.pax.exam.CoreOptions.options;
 import static org.ops4j.pax.exam.CoreOptions.provision;
 import static org.ops4j.pax.exam.CoreOptions.systemProperty;
-import static org.ops4j.pax.exam.CoreOptions.vmOption;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -34,7 +33,6 @@
 import java.util.Map;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.CopyOnWriteArrayList;
 
 import javax.inject.Inject;
 
@@ -68,7 +66,7 @@
     private EventAdmin eventAdmin;
 
     /** Event admin reference. */
-    private ServiceReference eventAdminReference;
+    private ServiceReference<EventAdmin> eventAdminReference;
 
     private volatile boolean running = false;
 
@@ -81,8 +79,8 @@
     private volatile String prefix;
 
     private volatile long startTime;
-    
-    private final Queue<Event> eventRecievedList = new ConcurrentLinkedQueue();
+
+    private final Queue<Event> eventRecievedList = new ConcurrentLinkedQueue<>();
 
     /** Wait lock for syncing. */
     private final Object waitLock = new Object();
@@ -108,10 +106,10 @@
      * @param sync
      */
     protected void send(String topic, Dictionary<String, Object> properties, int index, boolean sync) {
-        
-    	if(properties == null)
+
+    	if (properties == null)
     	{
-    		properties = new Hashtable();
+    		properties = new Hashtable<>();
     	}
         properties.put("thread", Thread.currentThread().getId());
         properties.put("index", index);
@@ -232,11 +230,10 @@
     /**
      * Helper method to get a service of the given type
      */
-    @SuppressWarnings("unchecked")
 	protected <T> T getService(Class<T> clazz) {
-    	final ServiceReference ref = bundleContext.getServiceReference(clazz.getName());
+    	final ServiceReference<T> ref = bundleContext.getServiceReference(clazz);
     	assertNotNull("getService(" + clazz.getName() + ") must find ServiceReference", ref);
-    	final T result = (T)(bundleContext.getService(ref));
+    	final T result = (bundleContext.getService(ref));
     	assertNotNull("getService(" + clazz.getName() + ") must find service", result);
     	return result;
     }
@@ -244,10 +241,10 @@
     protected EventAdmin getEventAdmin() {
         if ( eventAdminReference == null || eventAdminReference.getBundle() == null ) {
             eventAdmin = null;
-            eventAdminReference = bundleContext.getServiceReference(EventAdmin.class.getName());
+            eventAdminReference = bundleContext.getServiceReference(EventAdmin.class);
         }
         if ( eventAdmin == null && eventAdminReference != null ) {
-            eventAdmin = (EventAdmin) bundleContext.getService(eventAdminReference);
+            eventAdmin = bundleContext.getService(eventAdminReference);
         }
         return eventAdmin;
     }
@@ -274,7 +271,7 @@
              // with build server issue
              new DirectURLJUnitBundlesOption(),
              systemProperty("pax.exam.invoker").value("junit"),
-             //vmOption("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"), 
+             //vmOption("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"),
              bundle("link:classpath:META-INF/links/org.ops4j.pax.exam.invoker.junit.link")
         );
     }
@@ -293,7 +290,7 @@
         {
         	Integer index = (Integer)currentEvent.getProperty("index");
         	Long threadId = (Long)currentEvent.getProperty("thread");
-        	
+
         	if(index != null && threadId != null){
         		Integer previousIndex = orderVerifyMap.get(threadId);
         		if(previousIndex == null)
diff --git a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/Listener.java b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/Listener.java
index b06e6e3..b92d239 100644
--- a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/Listener.java
+++ b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/Listener.java
@@ -26,7 +26,7 @@
 
 public class Listener implements EventHandler {
 
-    private final ServiceRegistration reg;
+    private final ServiceRegistration<EventHandler> reg;
 
     private final Object payload;
 
@@ -44,7 +44,7 @@
             props.put("event.topics", "*");
         }
         this.test = test;
-        this.reg = ctx.registerService(EventHandler.class.getName(), this, props);
+        this.reg = ctx.registerService(EventHandler.class, this, props);
         this.payload = payload;
     }
 
diff --git a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/perftests/PerformanceTestIT.java b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/perftests/PerformanceTestIT.java
index 7c19b4c..3613a5b 100644
--- a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/perftests/PerformanceTestIT.java
+++ b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/perftests/PerformanceTestIT.java
@@ -16,6 +16,27 @@
  */
 package org.apache.felix.eventadmin.perftests;
 
+import static org.ops4j.pax.exam.Constants.START_LEVEL_SYSTEM_BUNDLES;
+import static org.ops4j.pax.exam.CoreOptions.bundle;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.provision;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+import static org.ops4j.pax.exam.CoreOptions.vmOption;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.inject.Inject;
+
 import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -33,21 +54,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.inject.Inject;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-
-import static org.ops4j.pax.exam.Constants.START_LEVEL_SYSTEM_BUNDLES;
-import static org.ops4j.pax.exam.CoreOptions.*;
-
 @RunWith(PaxExam.class)
 public class PerformanceTestIT {
     // the name of the system property providing the bundle file to be installed and tested
@@ -62,7 +68,7 @@
     protected BundleContext bundleContext;
 
     /** Event admin reference. */
-    private ServiceReference eventAdminReference;
+    private ServiceReference<EventAdmin> eventAdminReference;
 
     /** Event admin. */
     private EventAdmin eventAdmin;
@@ -102,10 +108,10 @@
     protected EventAdmin loadEventAdmin() {
         if ( eventAdminReference == null || eventAdminReference.getBundle() == null ) {
             eventAdmin = null;
-            eventAdminReference = bundleContext.getServiceReference(EventAdmin.class.getName());
+            eventAdminReference = bundleContext.getServiceReference(EventAdmin.class);
         }
         if ( eventAdmin == null && eventAdminReference != null ) {
-            eventAdmin = (EventAdmin) bundleContext.getService(eventAdminReference);
+            eventAdmin = bundleContext.getService(eventAdminReference);
         }
         return eventAdmin;
     }
@@ -278,7 +284,7 @@
 
 
     private static abstract class Listener implements EventHandler {
-        private ServiceRegistration registration;
+        private ServiceRegistration<EventHandler> registration;
 
         protected Listener() {
         }
@@ -290,7 +296,7 @@
             } else {
                 props.put("event.topics", "*");
             }
-            this.registration = bundleContext.registerService(EventHandler.class.getName(), this, props);
+            this.registration = bundleContext.registerService(EventHandler.class, this, props);
         }
 
         public void unregister() {
diff --git a/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java b/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java
index a6d47ab..036732b 100644
--- a/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java
@@ -18,6 +18,16 @@
  */
 package org.apache.felix.framework;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+
 import org.apache.felix.framework.cache.Content;
 import org.apache.felix.framework.util.FelixConstants;
 import org.apache.felix.framework.util.MultiReleaseContent;
@@ -36,17 +46,6 @@
 import org.osgi.resource.Requirement;
 import org.osgi.resource.Resource;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.security.ProtectionDomain;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Map;
-
 public class BundleRevisionImpl implements BundleRevision, Resource
 {
     public final static int EAGER_ACTIVATION = 0;
@@ -647,7 +646,7 @@
                 m_bundle.getFramework()._getProperty(Constants.FRAMEWORK_UUID) + "_" + m_id + ":" + port + path,
                 getBundle().getFramework().getBundleStreamHandler());
         }
-        catch (MalformedURLException ex)
+        catch (Exception ex)
         {
             m_bundle.getFramework().getLogger().log(
                 m_bundle,
diff --git a/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java b/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
index fa1f729..437d962 100644
--- a/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
@@ -54,7 +54,6 @@
 
 import java.io.IOException;
 import java.lang.reflect.Constructor;
-import java.net.MalformedURLException;
 import java.net.URL;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
@@ -711,7 +710,7 @@
     private ClassLoader getClassLoaderInternal()
     {
         ClassLoader classLoader = m_classLoader;
-        if (m_classLoader != null)
+        if (classLoader != null)
         {
             return classLoader;
         }
@@ -1081,37 +1080,6 @@
         return m_revision.getBundle();
     }
 
-    //
-    // Class loader implementation methods.
-    //
-
-    private URL createURL(int port, String path)
-    {
-        // Add a slash if there is one already, otherwise
-        // the is no slash separating the host from the file
-        // in the resulting URL.
-        if (!path.startsWith("/"))
-        {
-            path = "/" + path;
-        }
-
-        try
-        {
-            return BundleRevisionImpl.getSecureAction().createURL(null,
-                    FelixConstants.BUNDLE_URL_PROTOCOL + "://" +
-                    getBundle().getFramework()._getProperty(Constants.FRAMEWORK_UUID) + "_" + m_revision.getId() + ":" + port + path,
-                    getBundle().getFramework().getBundleStreamHandler());
-        }
-        catch (MalformedURLException ex)
-        {
-            m_logger.log(m_revision.getBundle(),
-                    Logger.LOG_ERROR,
-                    "Unable to create resource URL.",
-                    ex);
-        }
-        return null;
-    }
-
     public Enumeration getResourcesByDelegation(String name)
     {
         Set requestSet = (Set) m_cycleCheck.get();
diff --git a/framework/src/main/java/org/apache/felix/framework/URLHandlersBundleURLConnection.java b/framework/src/main/java/org/apache/felix/framework/URLHandlersBundleURLConnection.java
index 2cb7ee7..794af28 100644
--- a/framework/src/main/java/org/apache/felix/framework/URLHandlersBundleURLConnection.java
+++ b/framework/src/main/java/org/apache/felix/framework/URLHandlersBundleURLConnection.java
@@ -39,18 +39,22 @@
     private long m_contentTime;
     private String m_contentType;
     private InputStream m_is;
+    private final String m_path;
 
     public URLHandlersBundleURLConnection(URL url, Felix framework)
         throws IOException
     {
         super(url);
 
+        String urlString = url.toExternalForm();
+
+        m_path = urlString.substring(urlString.indexOf(url.getPath()));
+
         // If this is an attempt to create a connection to the root of
         // the bundle, then throw an exception since this isn't possible.
         // We only allow "/" as a valid URL so it can be used as context
         // for creating other URLs.
-        String path = url.getPath();
-        if ((path == null) || (path.length() == 0) || path.equals("/"))
+        if ((m_path == null) || (m_path.length() == 0) || m_path.equals("/"))
         {
             throw new IOException("Resource does not exist: " + url);
         }
@@ -121,11 +125,11 @@
             m_classPathIdx = 0;
         }
         if (!((BundleRevisionImpl) m_targetRevision)
-            .hasInputStream(m_classPathIdx, url.getPath()))
+            .hasInputStream(m_classPathIdx, m_path))
         {
             BundleWiring wiring = m_targetRevision.getWiring();
             ClassLoader cl = (wiring != null) ? wiring.getClassLoader() : null;
-            URL newurl = (cl != null) ? cl.getResource(url.getPath()) : null;
+            URL newurl = (cl != null) ? cl.getResource(m_path) : null;
             if (newurl == null)
             {
                 throw new IOException("Resource does not exist: " + url);
@@ -143,9 +147,9 @@
                 throw new IOException("Resource does not exist: " + url);
             }
             m_is = ((BundleRevisionImpl)
-                m_targetRevision).getInputStream(m_classPathIdx, url.getPath());
+                m_targetRevision).getInputStream(m_classPathIdx, m_path);
             m_contentLength = (m_is == null) ? 0 : m_is.available();
-            m_contentType = URLConnection.guessContentTypeFromName(url.getFile());
+            m_contentType = URLConnection.guessContentTypeFromName(m_path);
             connected = true;
         }
     }
@@ -234,6 +238,6 @@
             return url;
         }
         return ((BundleRevisionImpl)
-            m_targetRevision).getLocalURL(m_classPathIdx, url.getPath());
+            m_targetRevision).getLocalURL(m_classPathIdx, m_path);
     }
 }
\ No newline at end of file
diff --git a/framework/src/test/java/org/apache/felix/framework/ResourceLoadingTest.java b/framework/src/test/java/org/apache/felix/framework/ResourceLoadingTest.java
new file mode 100644
index 0000000..ce1167c
--- /dev/null
+++ b/framework/src/test/java/org/apache/felix/framework/ResourceLoadingTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.framework;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import junit.framework.TestCase;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWiring;
+
+public class ResourceLoadingTest extends TestCase
+{
+    private File tempDir;
+    private Framework felix;
+    private File cacheDir;
+
+    @Override
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        tempDir = File.createTempFile("felix-temp", ".dir");
+        assertTrue("precondition", tempDir.delete());
+        assertTrue("precondition", tempDir.mkdirs());
+
+        cacheDir = new File(tempDir, "felix-cache");
+        assertTrue("precondition", cacheDir.mkdir());
+
+        String cache = cacheDir.getPath();
+
+        Map<String,String> params = new HashMap<String, String>();
+        params.put("felix.cache.profiledir", cache);
+        params.put("felix.cache.dir", cache);
+        params.put(Constants.FRAMEWORK_STORAGE, cache);
+
+        felix = new Felix(params);
+        felix.init();
+        felix.start();
+    }
+
+    @Override
+    protected void tearDown() throws Exception
+    {
+        super.tearDown();
+
+        felix.stop(); // Note that this method is async
+        felix = null;
+
+        deleteDir(tempDir);
+        tempDir = null;
+        cacheDir = null;
+    }
+
+    public void testResourceLoadingWithHash() throws Exception {
+        String bmf = "Bundle-SymbolicName: cap.bundle\n"
+            + "Bundle-Version: 1.2.3.Blah\n"
+            + "Bundle-ManifestVersion: 2\n"
+            + "Import-Package: org.osgi.framework\n";
+        File bundleFile = File.createTempFile("felix-bundle", ".jar", tempDir);
+
+        Manifest mf = new Manifest(new ByteArrayInputStream(bmf.getBytes("utf-8")));
+        mf.getMainAttributes().putValue("Manifest-Version", "1.0");
+        JarOutputStream os = new JarOutputStream(new FileOutputStream(bundleFile), mf);
+
+        String name = "bla/ bli/@@€ ß&&????ßß &&$$\" \'##&&/ äöüß/ @@ foo#bar#baz ?a=a.txt?d=ä#dlksl";
+        os.putNextEntry(new ZipEntry(name));
+        os.write("This is a Test".getBytes());
+        os.close();
+
+        Bundle testBundle = felix.getBundleContext().installBundle(bundleFile.toURI().toASCIIString());
+
+        testBundle.start();
+
+        assertEquals(Bundle.ACTIVE, testBundle.getState());
+        assertNotNull(testBundle.getResource(name));
+        assertNotNull(testBundle.getEntry(name));
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(testBundle.getResource(name).openStream()));
+        assertEquals("This is a Test", reader.readLine());
+
+        reader = new BufferedReader(new InputStreamReader(testBundle.getEntry(name).openStream()));
+        assertEquals("This is a Test", reader.readLine());
+
+        reader = new BufferedReader(new InputStreamReader(testBundle.adapt(BundleWiring.class).getClassLoader().getResourceAsStream(name)));
+        assertEquals("This is a Test", reader.readLine());
+
+        reader = new BufferedReader(new InputStreamReader(testBundle.adapt(BundleWiring.class).getClassLoader().getResource(name).openStream()));
+        assertEquals("This is a Test", reader.readLine());
+
+        URL url = testBundle.adapt(BundleWiring.class).getClassLoader().getResource(name);
+
+        URL testURL = new URL(url.getProtocol() + "://" +  url.getHost() + ":" +  url.getPort() + "/" + name);
+
+        reader = new BufferedReader(new InputStreamReader(testURL.openStream()));
+        assertEquals("This is a Test", reader.readLine());
+    }
+
+    private static void deleteDir(File root) throws IOException
+    {
+        if (root.isDirectory())
+        {
+            for (File file : root.listFiles())
+            {
+                deleteDir(file);
+            }
+        }
+        assertTrue(root.delete());
+    }
+}
diff --git a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/DsComponentsCheck.java b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/DsComponentsCheck.java
index 9fcbbd2..cc63898 100644
--- a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/DsComponentsCheck.java
+++ b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/DsComponentsCheck.java
@@ -29,7 +29,6 @@
 import org.apache.felix.hc.api.HealthCheck;
 import org.apache.felix.hc.api.Result;
 import org.apache.felix.hc.api.ResultLog.Entry;
-import org.apache.felix.hc.core.impl.util.lang.StringUtils;
 import org.apache.felix.hc.generalchecks.scrutil.DsRootCauseAnalyzer;
 import org.osgi.framework.BundleContext;
 import org.osgi.service.component.annotations.Activate;
@@ -94,10 +93,10 @@
 
     @Override
     public Result execute() {
+        FormattingResultLog log = new FormattingResultLog();
 
         Collection<ComponentDescriptionDTO> componentDescriptionDTOs = scr.getComponentDescriptionDTOs();
         List<ComponentDescriptionDTO> watchedComps = new LinkedList<ComponentDescriptionDTO>();
-        FormattingResultLog log = new FormattingResultLog();
         List<String> missingComponents = new LinkedList<String>(componentsList);
         for (ComponentDescriptionDTO desc : componentDescriptionDTOs) {
             if (componentsList.contains(desc.name)) {
@@ -106,7 +105,7 @@
             }
         }
         for (String missingComp : missingComponents) {
-            log.temporarilyUnavailable("Not found {}", missingComp);
+            log.info("No component with name {} is registered in SCR runtime", missingComp);
         }
 
         int countEnabled = 0;
@@ -143,17 +142,15 @@
             }
 
             if (!isActive) {
-                if (analyzer != null) {
-                    analyzer.logNotEnabledComponent(log, dsComp, statusForMissing);
-                } else {
-                    log.add(new Entry(statusForMissing, "Not active: " + dsComp.name));
-                }
+                analyzer.logNotEnabledComponent(log, dsComp);
             }
-
         }
 
+        if (!missingComponents.isEmpty()) {
+            log.add(new Entry(statusForMissing, missingComponents.size() + " required components are missing in SCR runtime"));
+        }
         if (countDisabled > 0) {
-            log.temporarilyUnavailable("{} required components are not active", countDisabled);
+            log.add(new Entry(statusForMissing, countDisabled + " required components are not active"));
         }
         log.info("{} required components are active", countEnabled);
 
diff --git a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ServicesCheck.java b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ServicesCheck.java
index 01e88c6..fefe8c7 100644
--- a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ServicesCheck.java
+++ b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ServicesCheck.java
@@ -116,7 +116,7 @@
 
         for (String missingServiceName : missingServiceNames) {
             if (!missingServiceName.startsWith("(")) {
-                analyzer.logMissingService(log, missingServiceName, statusForMissing);
+                analyzer.logMissingService(log, missingServiceName);
             } else {
                 log.info("Service '{}' is missing", missingServiceName);
             }
diff --git a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAdapter.java b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAdapter.java
index 75a80db..c81d373 100644
--- a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAdapter.java
+++ b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAdapter.java
@@ -39,28 +39,25 @@
         this.analyzer = new DSRootCause(scr);
     }
 
-    public void logMissingService(FormattingResultLog log, String missingServiceName, Status status) {
+    public void logMissingService(FormattingResultLog log, String missingServiceName) {
         Optional<DSComp> rootCauseOptional = analyzer.getRootCause(missingServiceName);
         if (rootCauseOptional.isPresent()) {
-            logRootCause(log, rootCauseOptional.get(), status);
+            logRootCause(log, rootCauseOptional.get());
         } else {
-            log.add(new Entry(status, "Missing service without matching DS component: " + missingServiceName));
+            log.info("Missing service without matching DS component: " + missingServiceName);
         }
     }
 
-    public void logNotEnabledComponent(FormattingResultLog log, ComponentDescriptionDTO desc, Status status) {
+    public void logNotEnabledComponent(FormattingResultLog log, ComponentDescriptionDTO desc) {
         DSComp component = analyzer.getRootCause(desc);
-        logRootCause(log, component, status);
+        logRootCause(log, component);
     }
 
-    private void logRootCause(FormattingResultLog log, DSComp component, Status status) {
+    private void logRootCause(FormattingResultLog log, DSComp component) {
         new RootCausePrinter(new Consumer<String>() {
-            private boolean firstLineLogged = false;
-
             @Override
             public void accept(String str) {
-                log.add(new Entry(!firstLineLogged ? status : Status.OK, str.replaceFirst("    ", "-- ").replaceFirst("  ", "- ")));
-                firstLineLogged = true;
+                log.add(new Entry(Status.OK, str.replaceFirst("    ", "-- ").replaceFirst("  ", "- ")));
             }
         }).print(component);
     }
diff --git a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAnalyzer.java b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAnalyzer.java
index c130945..51a2839 100644
--- a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAnalyzer.java
+++ b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAnalyzer.java
@@ -53,15 +53,19 @@
         }
     }
 
-    public void logMissingService(FormattingResultLog log, String missingServiceName, Status status) {
+    public void logMissingService(FormattingResultLog log, String missingServiceName) {
         if (dsRootCauseAdapter != null) {
-            dsRootCauseAdapter.logMissingService(log, missingServiceName, status);
+            dsRootCauseAdapter.logMissingService(log, missingServiceName);
+        } else {
+            log.info("Service '{}' is missing", missingServiceName);
         }
     }
 
-    public void logNotEnabledComponent(FormattingResultLog log, ComponentDescriptionDTO desc, Status status) {
+    public void logNotEnabledComponent(FormattingResultLog log, ComponentDescriptionDTO desc) {
         if (dsRootCauseAdapter != null) {
-            dsRootCauseAdapter.logNotEnabledComponent(log, desc, status);
+            dsRootCauseAdapter.logNotEnabledComponent(log, desc);
+        } else {
+            log.info("Component '{}' is missing", desc.name);
         }
     }
 }
diff --git a/http/README.md b/http/README.md
index b716e1c..a49290a 100644
--- a/http/README.md
+++ b/http/README.md
@@ -19,10 +19,41 @@
   * `org.apache.felix.http.cometd` - Adds Comet/Ajax Push functionality to the HTTP Service implementation.
   * `org.apache.felix.http.proxy` - Proxy that is needed inside WAR when deployed inside an application server.
 
-Note that as of version **3.x**, the Serlvet APIs are **no longer** packaged with the implementation bundles! If you are migrating from lower versions, be sure to add the
+Note that as of version **3.x**, the Servlet APIs are **no longer** packaged with the implementation bundles! If you are migrating from lower versions, be sure to add the
 `org.apache.felix.http.servlet-api` (or any other compatible Serlvet API bundle) to your
 classpath and deployment!
 
+## Installing additional bundles for the optional HTTP/2 support with jetty
+
+The jetty implementation uses the OSGi ServiceLoader mediator technique to find certain pluggable components that are required for HTTP/2 support.  Your OSGi runtime must first have the bundles needed for OSGi ServiceLoader support deployed. Background information about the OSGi ServiceLoader integration is discussed [here](https://blog.osgi.org/2013/02/javautilserviceloader-in-osgi.html)
+
+Deploying the following set of bundles would be one way to enable the ServiceLoader mediator support:
+
+  * `org.apache.aries.spifly:org.apache.aries.spifly.dynamic.bundle:1.3.0`
+  * `org.apache.aries:org.apache.aries.util:1.1.3`
+  * `org.ow2.asm:asm:8.0.1`
+  * `org.ow2.asm:asm-analysis:8.0.1`
+  * `org.ow2.asm:asm-commons:8.0.1`
+  * `org.ow2.asm:asm-tree:8.0.1`
+  * `org.ow2.asm:asm-util:8.0.1`
+
+Next, depending on your server environment you must choose only one of the following sets
+of additional bundles to deploy [as described in the jetty documentation](https://www.eclipse.org/jetty/documentation/current/alpn-chapter.html)
+
+1. For java 9 or later:
+    * `org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715`
+    * `org.eclipse.jetty:jetty-alpn-java-server:${jetty.version}`
+
+2. For java 8:
+    * `org.eclipse.jetty:jetty-alpn-openjdk8-server:${jetty.version}`
+
+    * For java 8 versions earlier than 1.8.0_252 download and add -Xbootclasspath/p:/path_to_file_here/alpn-boot-8.1.13.v20181017.jar as an argument to the java process and then deploy the following bundle:
+        * `org.eclipse.jetty.osgi:jetty-osgi-alpn:${jetty.version}`
+
+    * For java 8 version 1.8.0_252 or later skip the bootclasspath argument and deploy the following bundle instead:
+        * `org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715`
+
+
 ## Using the OSGi Http Whiteboard
 
 The OSGi whiteboard implementation simplifies the task of registering servlets, filters, resources, listeners, and servlet contexts. For a complete introduction, please refer to the OSGi R7 Compendium or Enterprise specification.
@@ -342,6 +373,12 @@
 | `org.apache.felix.jetty.gzip.excludedPaths` | The additional path specs to exclude. Inclusion takes precedence over exclusion. Default is none. |
 | `org.apache.felix.jetty.gzip.includedMimeTypes` | The included mime types. Inclusion takes precedence over exclusion. Default is none. |
 | `org.apache.felix.jetty.gzip.excludedMimeTypes` | The excluded mime types. Inclusion takes precedence over exclusion. Default is none. |
+| `org.apache.felix.http2.enable` | Whether to enable HTTP/2. Default is false.  |
+| `org.apache.felix.jetty.http2.maxConcurrentStreams` | The max number of concurrent streams per connection. Default is 128. |
+| `org.apache.felix.jetty.http2.initialStreamRecvWindow` | The initial stream receive window (client to server). Default is 524288. |
+| `org.apache.felix.jetty.http2.initialSessionRecvWindow` | The initial session receive window (client to server). Default is 1048576. |
+| `org.apache.felix.jetty.alpn.protocols` | The ALPN protocols to consider. Default is h2, http/1.1. |
+| `org.apache.felix.jetty.alpn.defaultProtocol` | The default protocol when negotiation fails. Default is http/1.1. |
 
 ### All-in-one-bundle configuration properties
 
diff --git a/http/jetty/pom.xml b/http/jetty/pom.xml
index 04477cb..8e317c5 100644
--- a/http/jetty/pom.xml
+++ b/http/jetty/pom.xml
@@ -49,6 +49,98 @@
 
     <build>
         <plugins>
+
+            <!-- Use a groovy script to preserve the META-INF/services/* files for the artifacts that are embeded in the uber jar -->
+            <plugin>
+                <groupId>org.codehaus.gmaven</groupId>
+                <artifactId>groovy-maven-plugin</artifactId>
+                <version>2.1.1</version>
+                <executions>
+                    <execution>
+                        <id>groovy-magic</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>execute</goal>
+                        </goals>
+                        <configuration>
+                            <source><![CDATA[
+                                // make an output dir for the merged resource files
+                                def slDir = new File(project.build.directory, "serviceloader-resources");
+                                slDir.mkdirs();
+
+                                // scan each of the artifacts to preserve the information found in any META-INF/services/* files
+                                project.artifacts.each() { artifact ->
+
+                                    if (artifact.getArtifactHandler().isAddedToClasspath() && !org.apache.maven.artifact.Artifact.SCOPE_TEST.equals( artifact.getScope() )) {
+                                        def jar;
+                                        try {
+                                            jar = new java.util.jar.JarFile(artifact.file)
+                                            jar.stream().each() { entry ->
+                                               if (!entry.isDirectory() && entry.name.startsWith("META-INF/services/")) {
+
+                                                   // check if we already have a file with this name
+                                                   def svcFile = new File(slDir, entry.name)
+                                                   def svcSet = new LinkedHashSet();
+                                                   if (svcFile.exists()) {
+                                                       // found existing file, so load the items from the existing file so we can merge
+                                                       svcFile.eachLine { className ->
+                                                           className = className.trim();
+                                                           if (!className.isEmpty()) {
+                                                               svcSet.add(className);
+                                                           }
+                                                       }
+                                                   }
+
+                                                   // read the content of the found entry
+                                                   def lineReader;
+                                                   try {
+                                                       lineReader = new BufferedReader(new InputStreamReader(jar.getInputStream(entry), java.nio.charset.StandardCharsets.UTF_8));
+                                                       def className;
+                                                       while ( ( className = lineReader.readLine() ) != null ) {
+                                                           className = className.trim();
+                                                           if (!className.isEmpty()) {
+                                                               svcSet.add(className);
+                                                           }
+                                                       }
+                                                   } finally {
+                                                       // cleanup
+                                                       if (lineReader != null) {
+                                                           lineReader.close()
+                                                       }
+                                                   }
+
+                                                   // write the merged data to the output file
+                                                   if (!svcSet.isEmpty()) {
+                                                       // make any missing folders
+                                                       svcFile.getParentFile().mkdirs();
+
+                                                       svcFile.withWriter('utf-8') { writer ->
+                                                           svcSet.each() { item ->
+                                                               writer.writeLine item;
+                                                           }
+
+                                                           // finish up with a blank line
+                                                           writer.println();
+                                                       }
+                                                   }
+
+                                               }
+                                            }
+                                        } finally {
+                                            // cleanup
+                                            if (jar != null) {
+                                                jar.close();
+                                            }
+                                        }
+                                    }
+
+                                }
+                            ]]></source>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
             <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
@@ -112,11 +204,19 @@
                             osgi.service;objectClass:List&lt;String&gt;="org.osgi.service.http.runtime.HttpServiceRuntime";
                             uses:="org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto",
                             osgi.service;objectClass:List&lt;String&gt;="org.osgi.service.http.HttpService";
-                            uses:="org.osgi.service.http"
+                            uses:="org.osgi.service.http",
+                            osgi.serviceloader;osgi.serviceloader="org.eclipse.jetty.http.HttpFieldPreEncoder"
                         </Provide-Capability>
                         <Require-Capability>
-                            osgi.contract;filter:="(&amp;(osgi.contract=JavaServlet)(version=3.1))"
+                            osgi.contract;filter:="(&amp;(osgi.contract=JavaServlet)(version=3.1))",
+                            osgi.extender;filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional,
+                            osgi.extender;filter:="(osgi.extender=osgi.serviceloader.processor)";resolution:=optional,
+                            osgi.serviceloader;filter:="(osgi.serviceloader=org.eclipse.jetty.http.HttpFieldPreEncoder)";resolution:=optional;cardinality:=multiple,
+                            osgi.serviceloader;filter:="(osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Server)";resolution:=optional;cardinality:=multiple
                         </Require-Capability>
+                        <Include-Resource>
+                            {maven-resources},${project.build.directory}/serviceloader-resources
+                        </Include-Resource>
                         <_removeheaders>
                             Private-Package,Conditional-Package
                         </_removeheaders>
@@ -164,6 +264,23 @@
                                     org.eclipse.jetty.webapp;resolution:=optional,
                                     *
                                 </Import-Package>
+                                <!-- We need to override this from the base configuration to exclude the ServiceLoader capabilities -->
+                                <Provide-Capability>
+                                    osgi.implementation;osgi.implementation="osgi.http";version:Version="1.1";
+                                    uses:="javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard",
+                                    osgi.service;objectClass:List&lt;String&gt;="org.osgi.service.http.runtime.HttpServiceRuntime";
+                                    uses:="org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto",
+                                    osgi.service;objectClass:List&lt;String&gt;="org.osgi.service.http.HttpService";
+                                    uses:="org.osgi.service.http"
+                                </Provide-Capability>
+                                <!-- We need to override this from the base configuration to exclude the ServiceLoader capabilities -->
+                                <Require-Capability>
+                                    osgi.contract;filter:="(&amp;(osgi.contract=JavaServlet)(version=3.1))"
+                                </Require-Capability>
+                                <!-- We need to override this from the base configuration to exclude the ServiceLoader resources -->
+                                <Include-Resource>
+                                    {maven-resources}
+                                </Include-Resource>
                                 <_removeheaders>
                                     X-Jetty-Version,Private-Package,Conditional-Package 
                                 </_removeheaders>
@@ -249,6 +366,26 @@
             <version>${jetty.version}</version>
         </dependency>
         <dependency>
+            <groupId>org.eclipse.jetty.http2</groupId>
+            <artifactId>http2-server</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.http2</groupId>
+            <artifactId>http2-common</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.http2</groupId>
+            <artifactId>http2-hpack</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-alpn-server</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.service.http</artifactId>
             <version>1.2.1</version>
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java
index 9e48814..db08887 100644
--- a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java
@@ -432,6 +432,41 @@
                 "If not -1, stop timeout for the server in milliseconds.", -1L,
                 bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_STOP_TIMEOUT)));
 
+        adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_HTTP2_ENABLE,
+                "Enable Http/2",
+                "Whether to enable HTTP/2. Default is false.",
+                false,
+                bundle.getBundleContext().getProperty(JettyConfig.FELIX_HTTP2_ENABLE)));
+        adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_HTTP2_MAX_CONCURRENT_STREAMS,
+                "Http/2 Max Concurrent Streams",
+                "The max number of concurrent streams per connection. Default is 128.",
+                128,
+                bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_HTTP2_MAX_CONCURRENT_STREAMS)));
+        adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_HTTP2_INITIAL_STREAM_RECV_WINDOW,
+                "Http/2 Initial Stream Recieve Window",
+                "The initial stream receive window (client to server). Default is 524288.",
+                524288,
+                bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_HTTP2_INITIAL_STREAM_RECV_WINDOW)));
+        adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_HTTP2_INITIAL_SESSION_RECV_WINDOW,
+                "Http/2 Initial Session Recieve Window",
+                "The initial session receive window (client to server). Default is 1048576.",
+                1048576,
+                bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_HTTP2_INITIAL_SESSION_RECV_WINDOW)));
+
+        adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_ALPN_PROTOCOLS,
+                "ALPN Protocols",
+                "The ALPN protocols to consider. Default is h2, http/1.1.",
+                AttributeDefinition.STRING,
+                new String[] {"h2", "http/1.1"},
+                2147483647,
+                null, null,
+                getStringArray(bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_ALPN_PROTOCOLS))));
+        adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_ALPN_DEFAULT_PROTOCOL,
+                "ALPN Default Protocol",
+                "The default protocol when negotiation fails. Default is http/1.1.",
+                "http/1.1",
+                bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_ALPN_DEFAULT_PROTOCOL)));
+
         return new ObjectClassDefinition()
         {
 
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
index 549c7fe..aa340d7 100644
--- a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
@@ -267,6 +267,24 @@
     /** Felix specific property to specify the stop timeout of the jetty server */
     public static final String FELIX_JETTY_STOP_TIMEOUT = "org.apache.felix.jetty.stopTimeout";
 
+    /** Felix specific property to control whether to enable HTTP/2. */
+    public static final String FELIX_HTTP2_ENABLE = "org.apache.felix.http2.enable";
+
+    /** Felix specific property to specify the max number of concurrent streams per connection  */
+    public static final String FELIX_JETTY_HTTP2_MAX_CONCURRENT_STREAMS = "org.apache.felix.jetty.http2.maxConcurrentStreams";
+
+    /** Felix specific property to specify the initial stream receive window (client to server)  */
+    public static final String FELIX_JETTY_HTTP2_INITIAL_STREAM_RECV_WINDOW = "org.apache.felix.jetty.http2.initialStreamRecvWindow";
+
+    /** Felix specific property to specify the initial session receive window (client to server)  */
+    public static final String FELIX_JETTY_HTTP2_INITIAL_SESSION_RECV_WINDOW = "org.apache.felix.jetty.http2.initialSessionRecvWindow";
+
+    /** Felix specific property to specify the ALPN protocols to consider  */
+    public static final String FELIX_JETTY_ALPN_PROTOCOLS = "org.apache.felix.jetty.alpn.protocols";
+
+    /** Felix specific property to specify the default protocol when negotiation fails  */
+    public static final String FELIX_JETTY_ALPN_DEFAULT_PROTOCOL = "org.apache.felix.jetty.alpn.defaultProtocol";
+
     private static String validateContextPath(String ctxPath)
     {
         // undefined, empty, or root context path
@@ -530,6 +548,35 @@
         return useHttps && getHttpsPort() > 0;
     }
 
+    /**
+     * Returns <code>true</code> if HTTP/2 is configured to be used (
+     * {@link #FELIX_HTTP2_ENABLE})
+     */
+    public boolean isUseHttp2()
+    {
+        return getBooleanProperty(FELIX_HTTP2_ENABLE, false);
+    }
+
+    public int getHttp2MaxConcurrentStreams() {
+        return getIntProperty(FELIX_JETTY_HTTP2_MAX_CONCURRENT_STREAMS, 128);
+    }
+
+    public int getHttp2InitialStreamRecvWindow() {
+        return getIntProperty(FELIX_JETTY_HTTP2_INITIAL_STREAM_RECV_WINDOW, 524288);
+    }
+
+    public int getHttp2InitialSessionRecvWindow() {
+        return getIntProperty(FELIX_JETTY_HTTP2_INITIAL_SESSION_RECV_WINDOW, 1048576);
+    }
+
+    public String[] getAlpnProtocols() {
+        return getStringArrayProperty(FELIX_JETTY_ALPN_PROTOCOLS, new String[] {"h2", "http/1.1"} );
+    }
+
+    public String getAlpnDefaultProtocol() {
+        return getProperty(FELIX_JETTY_ALPN_DEFAULT_PROTOCOL, "http/1.1");
+    }
+
     public boolean isProxyLoadBalancerConnection()
     {
         return getBooleanProperty(FELIX_PROXY_LOAD_BALANCER_CONNECTION_ENABLE, false);
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
index a4b99c8..a067dde 100644
--- a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
@@ -34,7 +34,9 @@
 import org.apache.felix.http.base.internal.HttpServiceController;
 import org.apache.felix.http.base.internal.logger.SystemLogger;
 import org.apache.felix.http.jetty.internal.webapp.WebAppBundleTracker;
+import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
 import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
 import org.eclipse.jetty.io.ConnectionStatistics;
 import org.eclipse.jetty.security.HashLoginService;
 import org.eclipse.jetty.security.UserStore;
@@ -471,6 +473,27 @@
             httpConfiguration.addCustomizer(customizerWrapper);
         }
 
+        if (this.config.isUseHttp2()) {
+            //add ALPN factory
+            SslConnectionFactory alpnConnFactory = new SslConnectionFactory(sslContextFactory, "alpn");
+            connector.addConnectionFactory(alpnConnFactory);
+
+            ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(this.config.getAlpnProtocols());
+            alpn.setDefaultProtocol(this.config.getAlpnDefaultProtocol());
+            connector.addConnectionFactory(alpn);
+
+            //Configure a HTTP2 on the ssl connector
+            HTTP2ServerConnectionFactory http2factory = new HTTP2ServerConnectionFactory(httpConfiguration);
+            http2factory.setMaxConcurrentStreams(this.config.getHttp2MaxConcurrentStreams());
+            http2factory.setInitialStreamRecvWindow(this.config.getHttp2InitialStreamRecvWindow());
+            http2factory.setInitialSessionRecvWindow(this.config.getHttp2InitialSessionRecvWindow());
+            connector.addConnectionFactory(http2factory);
+
+            //use http/2 cipher comparator
+            sslContextFactory.setCipherComparator(org.eclipse.jetty.http2.HTTP2Cipher.COMPARATOR);
+            sslContextFactory.setUseCipherSuitesOrder(true);
+        }
+
         configureConnector(connector, this.config.getHttpsPort());
         return startConnector(connector);
     }
diff --git a/log/pom.xml b/log/pom.xml
index 420b7c8..a3e2188 100644
--- a/log/pom.xml
+++ b/log/pom.xml
@@ -29,7 +29,7 @@
   <description>
     A simple implementation of the OSGi R7 Log service.
   </description>
-  <version>1.2.3-SNAPSHOT</version>
+  <version>1.2.5-SNAPSHOT</version>
   <artifactId>org.apache.felix.log</artifactId>
 
   <properties>
@@ -41,6 +41,7 @@
     <connection>scm:git:https://github.com/apache/felix-dev.git</connection>
     <developerConnection>scm:git:https://github.com/apache/felix-dev.git</developerConnection>
     <url>https://gitbox.apache.org/repos/asf?p=felix-dev.git</url>
+    <tag>HEAD</tag>
   </scm>
 
   <dependencies>
diff --git a/metrics/osgi/README.md b/metrics/osgi/README.md
new file mode 100644
index 0000000..c0a2e24
--- /dev/null
+++ b/metrics/osgi/README.md
@@ -0,0 +1,70 @@
+# Apache Felix OSGi Metrics
+
+The OSGi metrics module defines a simple mechanism to gather OSGi-related metrics for application startup.
+
+The module is split into two bundles:
+
+- _collector_ - a zero-dependencies bundle that uses the OSGi APIs to gather various metrics
+- _consumers_ - a single bundle that contains various consumers
+
+## Metric collection
+
+The metrics are collected by the `org.apache.felix.metrics.osgi.collector` bundle. This bundle requires no configuration and imports a minimal set of packages, to allow starting as early as possible.
+
+As soon as startup is completed the metrics are made available to consumers that implement the `StartupMetricsListener` interface. The metrics are published after an optional delay, to prevent on-off bounces in startup completion.
+
+Startup completion is delegated to either the `org.apache.felix.systemready` or the `org.apache.felix.healtchecks.api` bundles, which publish marker services once the system is considered ready.
+
+## Metric publication
+
+The `org.apache.felix.metrics.osgi.consumers` bundle contains three out-of-the-box implementation for publishing the metrics
+
+- DropWizard metrics using a `MetricRegistry`
+- JSON file written in the bundle data directory
+- Log entries using the SLF4j API
+
+### JSON metrics file sample
+
+The following (truncated) JSON file exemplifies how the metrics are written
+
+```json
+{
+  "application": {
+    "startTimeMillis": 1587469534671,
+    "startDurationMillis": 14635
+  },
+  "bundles": [
+    {
+      "symbolicName": "org.osgi.util.pushstream",
+      "startTimeMillis": 1587469535933,
+      "startDurationMillis": 0
+    },
+    {
+      "symbolicName": "org.apache.aries.util",
+      "startTimeMillis": 1587469535935,
+      "startDurationMillis": 0
+    },
+    {
+      "symbolicName": "org.apache.felix.configadmin",
+      "startTimeMillis": 1587469536313,
+      "startDurationMillis": 58
+    }
+  ],
+  "services": [
+    {
+      "identifier": "jmx.objectname=org.apache.sling.classloader:name=FSClassLoader,type=ClassLoader",
+      "restarts": 2
+    }
+  ]
+}
+```
+
+Similar metrics are reported through the other collectors.
+
+## Usage
+
+1. Add the `org.apache.felix/org.apache.felix.metrics.osgi.collector` bundle and ensure that
+   it starts as early as possible
+1. Add the `org.apache.felix/org.apache.felix.metrics.osgi.consumers` bundle.
+1. Add the required bundles, either Apache Felix SystemReady or Apache Felix Health Checks
+1. Start up the application
diff --git a/metrics/osgi/collector/bnd.bnd b/metrics/osgi/collector/bnd.bnd
new file mode 100644
index 0000000..6dee250
--- /dev/null
+++ b/metrics/osgi/collector/bnd.bnd
@@ -0,0 +1,2 @@
+Import-Package: org.slf4j;resolution:=optional, *
+DynamicImport-Package: org.slf4j
\ No newline at end of file
diff --git a/metrics/osgi/collector/pom.xml b/metrics/osgi/collector/pom.xml
new file mode 100644
index 0000000..192f732
--- /dev/null
+++ b/metrics/osgi/collector/pom.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0"?>
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling-bundle-parent</artifactId>
+        <version>38</version>
+        <relativePath />
+    </parent>
+
+    <groupId>org.apache.felix</groupId>
+    <artifactId>org.apache.felix.metrics.osgi.collector</artifactId>
+    <version>0.1.0-SNAPSHOT</version>
+
+    <name>Apache Felix OSGi Metrics Collector</name>
+    <description> 
+        Collects metrics related to the OSGi framework and makes them available to consumers.
+    </description>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>biz.aQute.bnd</groupId>
+                <artifactId>bnd-baseline-maven-plugin</artifactId>
+                <configuration>
+                    <failOnMissing>false</failOnMissing>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>integration-test</goal>
+                            <goal>verify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.annotation.versioning</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.annotation.bundle</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+        </dependency>
+
+        <!-- note this is set to optional in bnd.bnd, to help the bundle start as soon as possible -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <!--  testing dependencies -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>3.3.3</version>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- IT dependencies -->
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-container-native</artifactId>
+            <version>${pax-exam.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-junit4</artifactId>
+            <version>${pax-exam.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-link-mvn</artifactId>
+            <version>${pax-exam.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+            <artifactId>pax-url-aether</artifactId>
+            <version>${pax-url.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.framework</artifactId>
+            <version>6.0.3</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.systemready</artifactId>
+            <version>0.4.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.healthcheck.api</artifactId>
+            <version>2.0.4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.healthcheck.core</artifactId>
+            <version>2.0.8</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.healthcheck.generalchecks</artifactId>
+            <version>2.0.4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+        </dependency>
+    </dependencies>
+
+    <properties>
+        <pax-exam.version>4.13.2</pax-exam.version>
+        <pax-url.version>2.6.2</pax-url.version>
+    </properties>
+</project>
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/BundleStartDuration.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/BundleStartDuration.java
new file mode 100644
index 0000000..214b47b
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/BundleStartDuration.java
@@ -0,0 +1,45 @@
+/*
+ * 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.metrics.osgi;
+
+import java.time.Duration;
+import java.time.Instant;
+
+public final class BundleStartDuration {
+
+    private final String symbolicName;
+    private final Instant startingAt;
+    private final Duration startedAfter;
+
+    public BundleStartDuration(String symbolicName, Instant startingAt, Duration startedAfter) {
+        this.symbolicName = symbolicName;
+        this.startingAt = startingAt;
+        this.startedAfter = startedAfter;
+    }
+    
+    public String getSymbolicName() {
+        return symbolicName;
+    }
+
+    public Instant getStartingAt() {
+        return startingAt;
+    }
+    
+    public Duration getStartedAfter() {
+        return startedAfter;
+    }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/ServiceRestartCounter.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/ServiceRestartCounter.java
new file mode 100644
index 0000000..8be29ef
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/ServiceRestartCounter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.metrics.osgi;
+
+public final class ServiceRestartCounter {
+    
+    private final String serviceIdentifier;
+    private final int serviceRestarts;
+
+    public ServiceRestartCounter(String serviceIdentifier, int serviceRestarts) {
+        this.serviceIdentifier = serviceIdentifier;
+        this.serviceRestarts = serviceRestarts;
+    }
+
+    /**
+     * @return a opaque service identifier, used for describing the service that has restarted
+     */
+    public String getServiceIdentifier() {
+        return serviceIdentifier;
+    }
+    
+    public int getServiceRestarts() {
+        return serviceRestarts;
+    }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/StartupMetrics.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/StartupMetrics.java
new file mode 100644
index 0000000..d878766
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/StartupMetrics.java
@@ -0,0 +1,101 @@
+/*
+ * 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.metrics.osgi;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides metrics about the OSGi framework startup and associated services
+ *
+ * <p>The calculation of the application being "ready" is based on the Apache Felix SystemReady bundle and
+ * requires a proper configuration of all checks.</p>
+ */
+public final class StartupMetrics {
+    
+    public static final class Builder {
+        
+        private StartupMetrics startupMetrics = new StartupMetrics();
+        
+        public static Builder withJvmStartup(Instant jvmStartup) {
+            Builder builder = new Builder();
+            builder.startupMetrics.jvmStartup = jvmStartup;
+            return builder;
+        }
+        
+        public Builder withStartupTime(Duration startupTime) {
+            startupMetrics.startupTime = startupTime;
+            return this;
+        }
+        
+        public Builder withBundleStartDurations(List<BundleStartDuration> bundleStartDurations) {
+            startupMetrics.bundleStartDurations = Collections.unmodifiableList(bundleStartDurations);
+            return this;
+        }
+        
+        public Builder withServiceRestarts(List<ServiceRestartCounter> serviceRestarts) {
+            startupMetrics.serviceRestarts = Collections.unmodifiableList(serviceRestarts);
+            return this;
+        }
+        
+        public StartupMetrics build() {
+            return startupMetrics;
+        }
+    }
+
+    private Instant jvmStartup;
+    private Duration startupTime;
+    private List<BundleStartDuration> bundleStartDurations;
+    private List<ServiceRestartCounter> serviceRestarts;
+ 
+    private StartupMetrics() { }
+
+    /**
+     * Returns the instant when the JVM has started
+     * 
+     * <p>Note that this is different from the OSGi startup process, and may lead to unexpected results if the
+     * OSGi framework starts considerably later compared to the JVM.</p>
+     * 
+     * @return the instant when the JVM has started
+     */
+    public Instant getJvmStartup() {
+        return jvmStartup;
+    }
+    
+    /**
+     * @return the time between the {@link #getJvmStartup()} and the application being ready
+     */
+    public Duration getStartupTime() {
+        return startupTime;
+    }
+    
+    /**
+     * @return all bundle start durations
+     */
+    public List<BundleStartDuration> getBundleStartDurations() {
+        return bundleStartDurations;
+    }
+    
+    /**
+     * @return tracked services with at least one restart
+     */
+    public List<ServiceRestartCounter> getServiceRestarts() {
+        return serviceRestarts;
+    }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/StartupMetricsListener.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/StartupMetricsListener.java
new file mode 100644
index 0000000..07022d2
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/StartupMetricsListener.java
@@ -0,0 +1,38 @@
+/*
+ * 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.metrics.osgi;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * A listener that is notified of the startup metrics
+ * 
+ * <p>The time of the notification can be delayed after the actual application start, as
+ * the implementation may choose to delay it to ensure that the startup is not affected
+ * by e.g. bouncing services.</p>
+ * 
+ * <p>Listeners that register after the application startup will receive a notification anyway.</p>
+ *
+ */
+@ConsumerType
+public interface StartupMetricsListener {
+    
+    /**
+     * @param metrics the startup metrics
+     */
+    void onStartupComplete(StartupMetrics metrics);
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/Activator.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/Activator.java
new file mode 100644
index 0000000..abce910
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/Activator.java
@@ -0,0 +1,51 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import org.osgi.annotation.bundle.Header;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+
+@Header(name = Constants.BUNDLE_ACTIVATOR, value = "${@class}")
+// avoid dependency to SCR so we can start early on
+public class Activator implements BundleActivator {
+
+    private BundleStartTimeCalculator bstc;
+    private StartupTimeCalculator stc;
+    private ServiceRestartCountCalculator srcc;
+
+    @Override
+    public void start(BundleContext context) throws Exception {
+        
+        bstc = new BundleStartTimeCalculator(context.getBundle().getBundleId());
+        context.addBundleListener(bstc);
+
+        srcc = new ServiceRestartCountCalculator();
+        context.addServiceListener(srcc);
+
+        stc = new StartupTimeCalculator(context, bstc, srcc);
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        stc.close();
+        context.removeServiceListener(srcc);
+        context.removeBundleListener(bstc);
+    }
+
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/BundleStartTimeCalculator.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/BundleStartTimeCalculator.java
new file mode 100644
index 0000000..449497f
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/BundleStartTimeCalculator.java
@@ -0,0 +1,112 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.felix.metrics.osgi.BundleStartDuration;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.Constants;
+import org.osgi.framework.SynchronousBundleListener;
+
+public class BundleStartTimeCalculator implements SynchronousBundleListener {
+
+    private Map<Long, StartTime> bundleToStartTime = new HashMap<>();
+    private Clock clock = Clock.systemUTC();
+    private final long ourBundleId;
+
+    public BundleStartTimeCalculator(long ourBundleId) {
+        this.ourBundleId = ourBundleId;
+    }
+
+    @Override
+    public void bundleChanged(BundleEvent event) {
+        Bundle bundle = event.getBundle();
+        
+        // this bundle is already starting by the time this is invoked. We also can't get proper timing
+        // from the framework bundle
+        
+        if ( bundle.getBundleId() == Constants.SYSTEM_BUNDLE_ID 
+                || bundle.getBundleId() == ourBundleId ) {
+            return;
+        }
+        
+        synchronized (bundleToStartTime) {
+
+            switch (event.getType()) {
+                case BundleEvent.STARTING:
+                    bundleToStartTime.put(bundle.getBundleId(), new StartTime(bundle.getSymbolicName(), clock.millis()));
+                    break;
+    
+                case BundleEvent.STARTED:
+                    StartTime startTime = bundleToStartTime.get(bundle.getBundleId());
+                    if ( startTime == null ) {
+                        Log.debug(getClass(), "No previous data for started bundle {}/{}", new Object[] { bundle.getBundleId(), bundle.getSymbolicName() });
+                        return;
+                    }
+                    startTime.started(clock.millis());
+                    break;
+                
+                default: // nothing to do here
+                    break;
+            }
+        }
+    }
+    
+    public List<BundleStartDuration> getBundleStartDurations() {
+        
+        synchronized (bundleToStartTime) {
+            return bundleToStartTime.values().stream()
+                .map( StartTime::toBundleStartDuration )
+                .collect( Collectors.toList() );                    
+        }
+    }
+
+    class StartTime {
+        private final String bundleSymbolicName;
+        private long startingTimestamp;
+        private long startedTimestamp;
+        
+        public StartTime(String bundleSymbolicName, long startingTimestamp) {
+            this.bundleSymbolicName = bundleSymbolicName;
+            this.startingTimestamp = startingTimestamp;
+        }
+
+        public long getDuration() {
+            return startedTimestamp - startingTimestamp;
+        }
+        
+        public String getBundleSymbolicName() {
+            return bundleSymbolicName;
+        }
+
+        public void started(long startedTimestamp) {
+            this.startedTimestamp = startedTimestamp;
+        }
+        
+        public BundleStartDuration toBundleStartDuration() {
+            return new BundleStartDuration(bundleSymbolicName, Instant.ofEpochMilli(startingTimestamp), Duration.ofMillis(startedTimestamp - startingTimestamp));
+        }
+    }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/Log.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/Log.java
new file mode 100644
index 0000000..2c3e1ad
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/Log.java
@@ -0,0 +1,39 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Delegates to slf4j if available, otherwise silently fails 
+ *
+ */
+public abstract class Log {
+    public static void debug(Class<?> caller, String message, Object... args) {
+        try {
+            Logger logger = LoggerFactory.getLogger(caller);
+            logger.debug(message, args);
+        } catch ( NoClassDefFoundError e ) {
+            // not available, just carry on
+        }
+    }
+
+    private Log() {
+        
+    }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/ServiceRestartCountCalculator.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/ServiceRestartCountCalculator.java
new file mode 100644
index 0000000..bf6abbd
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/ServiceRestartCountCalculator.java
@@ -0,0 +1,239 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.felix.metrics.osgi.ServiceRestartCounter;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+
+public class ServiceRestartCountCalculator implements ServiceListener {
+
+    private static final String[] GENERAL_IDENTIFIER_PROPERTIES = new String[] { Constants.SERVICE_PID, "component.name", "jmx.objectname" };
+    private static final Map<String, Collection<String>> SPECIFIC_IDENTIFIER_PROPERTIES = new HashMap<>();
+    static {
+        SPECIFIC_IDENTIFIER_PROPERTIES.put("org.apache.sling.commons.metrics.Gauge", Arrays.asList("name"));
+        SPECIFIC_IDENTIFIER_PROPERTIES.put("org.apache.sling.spi.resource.provider.ResourceProvider", Arrays.asList("provider.root"));
+        SPECIFIC_IDENTIFIER_PROPERTIES.put("org.apache.sling.servlets.post.PostOperation", Arrays.asList("sling.post.operation"));
+        SPECIFIC_IDENTIFIER_PROPERTIES.put("javax.servlet.Servlet", Arrays.asList("felix.webconsole.label"));
+        SPECIFIC_IDENTIFIER_PROPERTIES.put("org.apache.felix.inventory.InventoryPrinter", Arrays.asList("felix.inventory.printer.name"));
+    }
+    
+    private final Map<ServiceIdentifier, ServiceRegistrationsTracker> registrations = new HashMap<>();
+    private final Map<String, Integer> unidentifiedRegistrationsByClassName = new HashMap<>();
+    
+    @Override
+    public void serviceChanged(ServiceEvent event) {
+        
+        if ( shouldIgnore(event) ) 
+            return;
+
+        ServiceIdentifier id = tryFindIdFromGeneralProperties(event);
+        if ( id == null )
+            id = tryFindIdFromSpecificProperties(event);
+        
+        if ( id == null ) {
+            logUnknownService(event);
+            if ( event.getType() == ServiceEvent.UNREGISTERING )
+                recordUnknownServiceUnregistration(event);
+            return;
+        }
+
+        ServiceRegistrationsTracker tracker;
+        synchronized (registrations) {
+            
+            if ( event.getType() == ServiceEvent.REGISTERED ) {
+                tracker = registrations.computeIfAbsent(id, ServiceRegistrationsTracker::new);
+                tracker.registered();
+            } else if ( event.getType() == ServiceEvent.UNREGISTERING ) {
+                
+                tracker = registrations.get(id);
+                if (tracker == null) {
+                    Log.debug(getClass(), "Service with identifier {} was unregistered, but no previous registration data was found", id);
+                    return;
+                }
+                tracker.unregistered();
+            }
+        }
+    }
+
+    private boolean shouldIgnore(ServiceEvent event) {
+        
+        return event.getType() != ServiceEvent.REGISTERED && event.getType() != ServiceEvent.UNREGISTERING;
+    }
+
+    private ServiceIdentifier tryFindIdFromGeneralProperties(ServiceEvent event) {
+        for ( String identifierProp : GENERAL_IDENTIFIER_PROPERTIES ) {
+            Object identifierVal = event.getServiceReference().getProperty(identifierProp);
+            if ( identifierVal != null )
+                return new ServiceIdentifier(identifierProp, identifierVal.toString() );
+        }
+        
+        return null;
+    }
+
+    private ServiceIdentifier tryFindIdFromSpecificProperties(ServiceEvent event) {
+        for ( Map.Entry<String, Collection<String>> entry : SPECIFIC_IDENTIFIER_PROPERTIES.entrySet() ) {
+            String[] classNames = (String[]) event.getServiceReference().getProperty(Constants.OBJECTCLASS);
+            for ( String className : classNames ) {
+                if ( entry.getKey().equals(className) ) {
+                    StringBuilder propKey = new StringBuilder();
+                    StringBuilder propValue = new StringBuilder();
+                    
+                    for ( String idPropName : entry.getValue() ) {
+                        Object idPropVal = event.getServiceReference().getProperty(idPropName);
+                        if ( idPropVal != null ) {
+                            propKey.append(idPropName).append('~');
+                            propValue.append(idPropVal).append('~');
+                        }
+                    }
+                    
+                    if ( propKey.length() != 0 ) {
+                        propKey.deleteCharAt(propKey.length() - 1);
+                        propValue.deleteCharAt(propValue.length() - 1);
+                        ServiceIdentifier id = new ServiceIdentifier(propKey.toString(), propValue.toString());
+                        id.setAdditionalInfo(Constants.OBJECTCLASS + "=" + Arrays.toString(classNames));
+                        return id;
+                    }
+                }
+            }
+        }
+        
+        return null;
+    }
+
+    private void logUnknownService(ServiceEvent event) {
+        if ( event.getType() == ServiceEvent.UNREGISTERING ) {
+            Map<String, Object> props = new HashMap<>();
+            for ( String propertyName : event.getServiceReference().getPropertyKeys() ) {
+                Object propVal = event.getServiceReference().getProperty(propertyName);
+                if ( propVal.getClass() == String[].class )
+                    propVal = Arrays.toString((String[]) propVal);
+                props.put(propertyName, propVal);
+            }
+
+            Log.debug(getClass(), "Ignoring unregistration of service with props {}, as it has none of identifier properties {}", props, Arrays.toString(GENERAL_IDENTIFIER_PROPERTIES));
+        }
+    }
+
+    private void recordUnknownServiceUnregistration(ServiceEvent event) {
+        String[] classNames = (String[]) event.getServiceReference().getProperty(Constants.OBJECTCLASS);
+        synchronized (unidentifiedRegistrationsByClassName) {
+            for ( String className : classNames )
+                unidentifiedRegistrationsByClassName.compute(className, (k,v) -> v == null ? 1 : ++v);
+        }
+    }
+    
+    // visible for testing
+    Map<ServiceIdentifier, ServiceRegistrationsTracker> getRegistrations() {
+        synchronized (registrations) {
+            return new HashMap<>(registrations);
+        }
+    }
+    
+    // visible for testing
+    Map<String, Integer> getUnidentifiedRegistrationsByClassName() {
+        synchronized (unidentifiedRegistrationsByClassName) {
+            return unidentifiedRegistrationsByClassName;
+        }
+    }
+    
+    public List<ServiceRestartCounter> getServiceRestartCounters() {
+        synchronized (registrations) {
+            return registrations.values().stream()
+                .filter( r -> r.restartCount() > 0)
+                .map( ServiceRegistrationsTracker::toServiceRestartCounter )
+                .collect(Collectors.toList());
+        }
+    }
+    
+    static class ServiceIdentifier {
+        private String key;
+        private String value;
+        private String additionalInfo;
+
+        public ServiceIdentifier(String key, String value) {
+            this.key = key;
+            this.value = value;
+        }
+        
+        public void setAdditionalInfo(String additionalInfo) {
+            this.additionalInfo = additionalInfo;
+        }
+
+        @Override
+        public int hashCode() {
+            
+            return Objects.hash(key, value);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            ServiceIdentifier other = (ServiceIdentifier) obj;
+            
+            return Objects.equals(key, other.key) && Objects.equals(value, other.value);
+        }
+        
+        @Override
+        public String toString() {
+            return this.key + "=" + this.value + ( additionalInfo != null ? "(" + additionalInfo + ")" : "") ;
+        }
+    }
+    
+    static class ServiceRegistrationsTracker {
+        private final ServiceIdentifier id;
+        private int registrationCount;
+        private int unregistrationCount;
+        
+        public ServiceRegistrationsTracker(ServiceIdentifier id) {
+            this.id = id;
+        }
+        
+        public void registered() {
+            this.registrationCount++;
+        }
+        
+        public void unregistered() {
+            this.unregistrationCount++;
+        }
+        
+        public int restartCount() {
+            if ( unregistrationCount == 0 )
+                return 0;
+            
+            return registrationCount - 1;
+        }
+        
+        public ServiceRestartCounter toServiceRestartCounter() {
+            return new ServiceRestartCounter(id.toString(), restartCount());
+        }
+    }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/ServiceTrackerCustomizerAdapter.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/ServiceTrackerCustomizerAdapter.java
new file mode 100644
index 0000000..bfa6054
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/ServiceTrackerCustomizerAdapter.java
@@ -0,0 +1,33 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+public abstract class ServiceTrackerCustomizerAdapter<S, T> implements ServiceTrackerCustomizer<S, T> {
+
+    @Override
+    public void modifiedService(ServiceReference<S> reference, T service) {
+        // nothing by default
+    }
+
+    @Override
+    public void removedService(ServiceReference<S> reference, T service) {
+        // nothing by default
+    }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/StartupTimeCalculator.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/StartupTimeCalculator.java
new file mode 100644
index 0000000..3fbeb42
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/StartupTimeCalculator.java
@@ -0,0 +1,136 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import java.lang.management.ManagementFactory;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import org.apache.felix.metrics.osgi.BundleStartDuration;
+import org.apache.felix.metrics.osgi.ServiceRestartCounter;
+import org.apache.felix.metrics.osgi.StartupMetrics;
+import org.apache.felix.metrics.osgi.StartupMetricsListener;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class StartupTimeCalculator {
+
+    // delay activation until the system is marked as ready
+    // don't explicitly import the systemready class as this bundle must be started as early as possible in order
+    // to record bundle starup times
+    
+    static final String PROPERTY_READINESS_DELAY = "org.apache.felix.metrics.osgi.additionalReadinessDelayMillis";
+    private ServiceTracker<Object, Object> readyTracker;
+    private ServiceTracker<StartupMetricsListener, StartupMetricsListener> listenersTracker;
+    private BundleStartTimeCalculator bundleCalculator;
+    private ServiceRestartCountCalculator serviceCalculator;
+    private ScheduledExecutorService executor;
+    private Future<Void> future;
+    private Supplier<StartupMetrics> metricsSupplier;
+    private long additionalReadinessDelayMillis = TimeUnit.SECONDS.toMillis(5);
+
+    public StartupTimeCalculator(BundleContext ctx, BundleStartTimeCalculator bundleCalculator, ServiceRestartCountCalculator serviceCalculator) throws InvalidSyntaxException {
+        executor = Executors.newScheduledThreadPool(1);
+        try {
+            String readinessDelay = ctx.getProperty(PROPERTY_READINESS_DELAY);
+            additionalReadinessDelayMillis = Long.parseLong(readinessDelay);
+        } catch ( NumberFormatException e) {
+            Log.debug(getClass(), "Failed parsing readiness delay", e);
+        }
+        this.bundleCalculator = bundleCalculator;
+        this.serviceCalculator = serviceCalculator;
+        this.readyTracker = new ServiceTracker<>(ctx, 
+                ctx.createFilter("(|(" + Constants.OBJECTCLASS+"=org.apache.felix.systemready.SystemReady)(&(" + Constants.OBJECTCLASS+ "=org.apache.felix.hc.api.condition.Healthy)(tag=systemalive)))"),
+                new ServiceTrackerCustomizerAdapter<Object, Object>() {
+
+                    @Override
+                    public Object addingService(ServiceReference<Object> reference) {
+                        if ( future == null ) 
+                            future = calculate();
+                        return ctx.getService(reference);
+                    }
+                    
+                    @Override
+                    public void removedService(ServiceReference<Object> reference, Object service) {
+                        if ( future != null && !future.isDone() ) {
+                            boolean cancelled = future.cancel(false);
+                            if ( cancelled ) {
+                                metricsSupplier = null;
+                                future = null;
+                            }
+                        }
+                    }
+                });
+        this.readyTracker.open();
+        
+        this.listenersTracker = new ServiceTracker<>(ctx, StartupMetricsListener.class, new ServiceTrackerCustomizerAdapter<StartupMetricsListener, StartupMetricsListener>() {
+            @Override
+            public StartupMetricsListener addingService(ServiceReference<StartupMetricsListener> reference) {
+                StartupMetricsListener service = ctx.getService(reference);
+                // TODO - there is still a minor race condition, between the supplier being set and the registration of services
+                // which can cause the listener to receive the event twice
+                if ( metricsSupplier != null )
+                    service.onStartupComplete(metricsSupplier.get());
+                return service;
+            }
+        });
+        this.listenersTracker.open();
+    }
+
+    public void close() {
+        this.readyTracker.close();
+    }
+
+    private Future<Void> calculate() {
+
+        long currentMillis = Clock.systemUTC().millis();
+        
+        return executor.schedule(() -> {
+            long startupMillis = ManagementFactory.getRuntimeMXBean().getStartTime();
+            
+            Duration startupDuration = Duration.ofMillis(currentMillis - startupMillis);
+            Instant startupInstant = Instant.ofEpochMilli(startupMillis);
+            List<BundleStartDuration> bundleDurations = bundleCalculator.getBundleStartDurations();
+            List<ServiceRestartCounter> serviceRestarts = serviceCalculator.getServiceRestartCounters();
+            
+            metricsSupplier = () -> {
+                return StartupMetrics.Builder.withJvmStartup(startupInstant)
+                    .withStartupTime(startupDuration)
+                    .withBundleStartDurations(bundleDurations)
+                    .withServiceRestarts(serviceRestarts)
+                    .build();
+            };
+            
+            for ( StartupMetricsListener listener : listenersTracker.getServices(new StartupMetricsListener[0]) )
+                listener.onStartupComplete(metricsSupplier.get());
+            
+            return null;
+        }, additionalReadinessDelayMillis, TimeUnit.MILLISECONDS);
+        
+
+    }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/package-info.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/package-info.java
new file mode 100644
index 0000000..12d77b2
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.felix.metrics.osgi;
diff --git a/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/AbstractIT.java b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/AbstractIT.java
new file mode 100644
index 0000000..f7fc59a
--- /dev/null
+++ b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/AbstractIT.java
@@ -0,0 +1,126 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.CoreOptions.bundle;
+import static org.ops4j.pax.exam.CoreOptions.composite;
+import static org.ops4j.pax.exam.CoreOptions.frameworkProperty;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import org.apache.felix.metrics.osgi.StartupMetrics;
+import org.apache.felix.metrics.osgi.StartupMetricsListener;
+import org.apache.felix.metrics.osgi.impl.StartupTimeCalculator;
+import org.junit.Test;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+
+public abstract class AbstractIT {
+
+    private static final String TESTED_BUNDLE_LOCATION = "reference:file:target/classes";
+
+    @Inject
+    protected BundleContext bc;
+
+    @Configuration
+    public Option[] config() {
+        return options(
+            // lower timeout, we don't have bounces
+            frameworkProperty(StartupTimeCalculator.PROPERTY_READINESS_DELAY).value("100"),
+            bundle(TESTED_BUNDLE_LOCATION),
+            junitBundles(),
+            mavenBundle("org.apache.felix", "org.apache.felix.scr", "2.1.16"),
+            mavenBundle("org.osgi", "org.osgi.util.promise", "1.1.1"),
+            mavenBundle("org.osgi", "org.osgi.util.function", "1.1.0"),
+            composite(specificOptions())
+        );
+    }
+    
+    protected abstract Option[] specificOptions();
+
+    @Test
+    public void registerListenerAfterSystemIsReady() throws InterruptedException {
+        runBasicTest(false);
+    }
+
+    @Test
+    public void registerListenerBeforeSystemIsReady() throws InterruptedException {
+        runBasicTest(true);
+    }
+
+    private void runBasicTest(boolean registerListenerFirst) throws InterruptedException {
+        
+        Set<String> expectedBundleNames = Arrays.stream(bc.getBundles())
+            .filter( b -> b.getBundleId() != Constants.SYSTEM_BUNDLE_ID ) // no framework bundle
+            .filter( b -> !b.getLocation().equals(TESTED_BUNDLE_LOCATION) ) // not the bundle under test
+            .map ( b -> b.getSymbolicName() )
+            .filter( bsn ->  ! bsn.startsWith("org.ops4j")  ) // no ops4j bundles
+            .filter( bsn ->  ! bsn.startsWith("PAXEXAM")  ) // no ops4j bundles
+            .filter( bsn -> ! bsn.contains("geronimo-atinject")) // injected early on by Pax-Exam
+            .collect(Collectors.toSet());
+        
+        WaitForResultsStartupMetricsListener listener = new WaitForResultsStartupMetricsListener();
+
+        // service that will be tracked as restarting
+        Runnable foo = () -> {};
+        Dictionary<String, Object> props = new Hashtable<>();
+        props.put(Constants.SERVICE_PID, "some.service.pid");
+        ServiceRegistration<Runnable> reg = bc.registerService(Runnable.class, foo, props);
+        reg.unregister();
+        reg = bc.registerService(Runnable.class, foo, props);
+        reg.unregister();
+
+        if ( registerListenerFirst ) {
+            markSystemReady();
+            bc.registerService(StartupMetricsListener.class, listener, null);
+        } else {
+            markSystemReady();
+            bc.registerService(StartupMetricsListener.class, listener, null);
+        }
+        
+        StartupMetrics metrics = listener.getMetrics();
+        
+        assertThat(metrics, notNullValue());
+        Set<String> trackedBundleNames = metrics.getBundleStartDurations().stream()
+                .map( bsd -> bsd.getSymbolicName())
+                .collect(Collectors.toSet());
+        
+        assertTrue("Tracked bundle names " + trackedBundleNames + " did not contain " + expectedBundleNames, 
+            trackedBundleNames.containsAll(expectedBundleNames));
+        
+        assertThat("Service restarts", metrics.getServiceRestarts().size(), equalTo(1));
+        assertThat("Restarted component service identifier", metrics.getServiceRestarts().get(0).getServiceIdentifier(), equalTo(Constants.SERVICE_PID+"="+props.get(Constants.SERVICE_PID)));
+    }
+
+    protected abstract void markSystemReady();
+}
diff --git a/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/BundleStartTimeCalculatorTest.java b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/BundleStartTimeCalculatorTest.java
new file mode 100644
index 0000000..70d18e7
--- /dev/null
+++ b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/BundleStartTimeCalculatorTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.time.Instant;
+
+import org.apache.felix.metrics.osgi.BundleStartDuration;
+import org.apache.felix.metrics.osgi.impl.BundleStartTimeCalculator;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+
+public class BundleStartTimeCalculatorTest {
+
+    @Test
+    public void bundleStarted() {
+
+        Instant now = Instant.now();
+        
+        BundleStartTimeCalculator c = new BundleStartTimeCalculator(1l);
+        Bundle mockBundle = newMockBundle(5l, "foo");
+        c.bundleChanged(new BundleEvent(BundleEvent.STARTING, mockBundle));
+        c.bundleChanged(new BundleEvent(BundleEvent.STARTED, mockBundle));
+        
+        assertThat("Expected one entry for bundle durations",c.getBundleStartDurations().size(), CoreMatchers.equalTo(1));
+        BundleStartDuration duration = c.getBundleStartDurations().get(0);
+        assertThat("Bundle duration refers to wrong bundle symbolic name", duration.getSymbolicName(), CoreMatchers.equalTo("foo"));
+        
+        assertTrue("Bundle STARTING time (" + duration.getStartingAt() + " must be after test start time(" + now + ")", 
+                duration.getStartingAt().isAfter(now));
+        assertFalse("Bundle start duration (" + duration.getStartedAfter() + ") must not be negative",
+                duration.getStartedAfter().isNegative());
+    }
+    
+    private Bundle newMockBundle(long id, String symbolicName) {
+        
+        Bundle mockBundle = Mockito.mock(Bundle.class);
+        Mockito.when(mockBundle.getBundleId()).thenReturn(id);
+        Mockito.when(mockBundle.getSymbolicName()).thenReturn(symbolicName);
+        return mockBundle;
+    }
+}
diff --git a/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/HealthCheckSmokeIT.java b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/HealthCheckSmokeIT.java
new file mode 100644
index 0000000..f30fcdb
--- /dev/null
+++ b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/HealthCheckSmokeIT.java
@@ -0,0 +1,61 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import static org.ops4j.pax.exam.CoreOptions.frameworkProperty;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.inject.Inject;
+
+import org.apache.felix.hc.api.condition.Healthy;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.BundleContext;
+
+@RunWith(PaxExam.class)
+public class HealthCheckSmokeIT extends AbstractIT {
+
+    @Inject
+    private BundleContext bc;
+    
+    @Override
+    protected Option[] specificOptions() {
+        return options(
+            frameworkProperty("org.apache.felix.http.enable").value("false"),
+            mavenBundle("org.apache.felix", "org.apache.felix.healthcheck.api", "2.0.4"),
+            mavenBundle("org.apache.felix", "org.apache.felix.healthcheck.core", "2.0.8"),
+            mavenBundle("org.apache.felix", "org.apache.felix.healthcheck.generalchecks", "2.0.4"),
+            mavenBundle("org.apache.felix", "org.apache.felix.http.servlet-api", "1.1.2"),
+            mavenBundle("org.apache.felix", "org.apache.felix.http.jetty", "4.0.18"),
+            mavenBundle("org.apache.commons", "commons-lang3", "3.9"),
+            mavenBundle("org.apache.felix", "org.apache.felix.eventadmin", "1.5.0"),
+            mavenBundle("org.apache.felix", "org.apache.felix.rootcause", "0.1.0")
+        );
+    }
+    
+    @Override
+    protected void markSystemReady() {
+        Dictionary<String, Object> regProps = new Hashtable<>();
+        regProps.put("tag", "systemalive");
+        bc.registerService(Healthy.class, new Healthy() {}, regProps);
+    }
+}
diff --git a/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/ServiceRegistrationsTrackerTest.java b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/ServiceRegistrationsTrackerTest.java
new file mode 100644
index 0000000..e724829
--- /dev/null
+++ b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/ServiceRegistrationsTrackerTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import org.apache.felix.metrics.osgi.impl.ServiceRestartCountCalculator.ServiceIdentifier;
+import org.apache.felix.metrics.osgi.impl.ServiceRestartCountCalculator.ServiceRegistrationsTracker;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Constants;
+
+public class ServiceRegistrationsTrackerTest {
+
+    private ServiceRegistrationsTracker tracker;
+    
+    @Before
+    public void prepare() {
+        tracker = new ServiceRegistrationsTracker(new ServiceIdentifier(Constants.SERVICE_PID, "foo"));
+    }
+
+    @Test
+    public void singleRegister() {
+        tracker.registered();
+        assertThat(tracker.restartCount(), equalTo(0));
+    }
+
+    @Test
+    public void registerUnregister() {
+        tracker.registered();
+        tracker.unregistered();
+        assertThat(tracker.restartCount(), equalTo(0));
+    }
+
+    @Test
+    public void singleRestart() {
+        tracker.registered();
+        tracker.unregistered();
+        tracker.registered();
+        assertThat(tracker.restartCount(), equalTo(1));
+    }
+
+    @Test
+    public void singleRestartAndUnregister() {
+        tracker.registered();
+        tracker.unregistered();
+        tracker.registered();
+        tracker.unregistered();
+        assertThat(tracker.restartCount(), equalTo(1));
+    }
+    
+    @Test
+    public void twoRestarts() {
+        tracker.registered();
+        tracker.unregistered();
+        tracker.registered();
+        tracker.unregistered();
+        tracker.registered();
+        assertThat(tracker.restartCount(), equalTo(2));
+    }
+}
diff --git a/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/ServiceRestartCountCalculatorTest.java b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/ServiceRestartCountCalculatorTest.java
new file mode 100644
index 0000000..5b89ee8
--- /dev/null
+++ b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/ServiceRestartCountCalculatorTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.felix.metrics.osgi.impl.ServiceRestartCountCalculator;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+public class ServiceRestartCountCalculatorTest {
+
+    @Test
+    public void ignoredEventTypes() {
+        
+        ServiceRestartCountCalculator srcc = new ServiceRestartCountCalculator();
+        srcc.serviceChanged(new ServiceEvent(ServiceEvent.MODIFIED, new DummyServiceReference<>(new HashMap<>())));
+        srcc.serviceChanged(new ServiceEvent(ServiceEvent.MODIFIED_ENDMATCH, new DummyServiceReference<>(new HashMap<>())));
+        
+        assertThat(srcc.getRegistrations().size(), equalTo(0));
+    }
+
+    @Test
+    public void serviceWithServicePidProperty() {
+        
+        assertServiceWithPropertyIsTracked(Constants.SERVICE_PID);
+    }
+
+    @Test
+    public void serviceWithComponentNameProperty() {
+        
+        assertServiceWithPropertyIsTracked("component.name");
+    }
+
+    @Test
+    public void serviceWithJmxObjectNameProperty() {
+        
+        assertServiceWithPropertyIsTracked("jmx.objectname");
+    }
+
+    @Test
+    public void metricsGaugesAreTracked() {
+        HashMap<String, Object> props = new HashMap<>();
+        props.put(Constants.OBJECTCLASS, new String[] { "org.apache.sling.commons.metrics.Gauge" });
+        props.put("name", "commons.threads.tp.script-cache-thread-pool.Name");
+        DummyServiceReference<Object> dsr = new DummyServiceReference<>(props);
+        
+        ServiceRestartCountCalculator srcc = new ServiceRestartCountCalculator();
+        srcc.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, dsr));
+        
+        assertThat(srcc.getRegistrations().size(), equalTo(1));
+    }
+
+    @Test
+    public void unknownServiceIsNotTracked() {
+        HashMap<String, Object> props = new HashMap<>();
+        props.put(Constants.OBJECTCLASS, new String[] { "foo" });
+        DummyServiceReference<Object> dsr = new DummyServiceReference<>(props);
+        
+        ServiceRestartCountCalculator srcc = new ServiceRestartCountCalculator();
+        srcc.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, dsr));
+        
+        assertThat(srcc.getRegistrations().size(), equalTo(0));
+        assertThat(srcc.getUnidentifiedRegistrationsByClassName().size(), equalTo(0));
+    }
+    
+    @Test
+    public void unknownServiceUnregistrationsAreTracked() {
+        HashMap<String, Object> props = new HashMap<>();
+        props.put(Constants.OBJECTCLASS, new String[] { "foo", "bar" });
+        DummyServiceReference<Object> sr1 = new DummyServiceReference<>(props);
+        
+        HashMap<String, Object> props2 = new HashMap<>();
+        props2.put(Constants.OBJECTCLASS, new String[] { "foo"} );
+        DummyServiceReference<Object> sr2 = new DummyServiceReference<>(props2);
+        
+        ServiceRestartCountCalculator srcc = new ServiceRestartCountCalculator();
+        srcc.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, sr1));
+        srcc.serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, sr1));
+        
+        srcc.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, sr2));
+        srcc.serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, sr2));
+        
+        assertThat(srcc.getRegistrations().size(), equalTo(0));
+        Map<String, Integer> unidentifiedRegistrations = srcc.getUnidentifiedRegistrationsByClassName();
+        assertThat(unidentifiedRegistrations.size(), equalTo(2));
+        assertThat(unidentifiedRegistrations.get("foo"), equalTo(2));
+        assertThat(unidentifiedRegistrations.get("bar"), equalTo(1));
+    }
+    
+    private void assertServiceWithPropertyIsTracked(String propertyName) {
+        
+        HashMap<String, Object> props = new HashMap<>();
+        props.put(propertyName, new String[] { "foo.bar" });
+        DummyServiceReference<Object> dsr = new DummyServiceReference<>(props);
+        
+        ServiceRestartCountCalculator srcc = new ServiceRestartCountCalculator();
+        srcc.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, dsr));
+        
+        assertThat(srcc.getRegistrations().size(), CoreMatchers.equalTo(1));
+    }
+    
+    static class DummyServiceRegistration<S> implements ServiceRegistration<S> {
+        
+        private final DummyServiceReference<S> sr;
+
+        public DummyServiceRegistration(Map<String, Object> props) {
+            this.sr = new DummyServiceReference<>(props);
+        }
+
+        @Override
+        public ServiceReference<S> getReference() {
+            return sr;
+        }
+
+        @Override
+        public void setProperties(Dictionary<String, ?> properties) {
+            throw new UnsupportedOperationException();
+            
+        }
+
+        @Override
+        public void unregister() {
+            throw new UnsupportedOperationException();            
+        }
+    }
+    
+    static class DummyServiceReference<S> implements ServiceReference<S> {
+        
+        private final Map<String, Object> props;
+
+        public DummyServiceReference(Map<String, Object> props) {
+            this.props = props;
+        }
+
+        @Override
+        public Object getProperty(String key) {
+            return props.get(key);
+        }
+
+        @Override
+        public String[] getPropertyKeys() {
+            return props.keySet().toArray(new String[0]);
+        }
+
+        @Override
+        public Bundle getBundle() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Bundle[] getUsingBundles() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean isAssignableTo(Bundle bundle, String className) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int compareTo(Object reference) {
+            throw new UnsupportedOperationException();
+        }
+        
+    }
+
+}
diff --git a/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/SystemReadySmokeIT.java b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/SystemReadySmokeIT.java
new file mode 100644
index 0000000..2d9cfc2
--- /dev/null
+++ b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/SystemReadySmokeIT.java
@@ -0,0 +1,47 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+
+import javax.inject.Inject;
+
+import org.apache.felix.systemready.SystemReady;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.BundleContext;
+
+@RunWith(PaxExam.class)
+public class SystemReadySmokeIT extends AbstractIT {
+
+    @Inject
+    protected BundleContext bc;
+
+    @Override
+    protected Option[] specificOptions() {
+        return options(
+            mavenBundle("org.apache.felix", "org.apache.felix.systemready", "0.4.2"),
+            mavenBundle("org.apache.felix", "org.apache.felix.rootcause", "0.1.0")
+        );
+    }
+    protected void markSystemReady() {
+        bc.registerService(SystemReady.class, new SystemReady() {}, null);
+    }
+
+}
diff --git a/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/WaitForResultsStartupMetricsListener.java b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/WaitForResultsStartupMetricsListener.java
new file mode 100644
index 0000000..42bbba1
--- /dev/null
+++ b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/WaitForResultsStartupMetricsListener.java
@@ -0,0 +1,45 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import java.util.concurrent.CountDownLatch;
+
+import org.apache.felix.metrics.osgi.StartupMetrics;
+import org.apache.felix.metrics.osgi.StartupMetricsListener;
+
+class WaitForResultsStartupMetricsListener implements StartupMetricsListener {
+    
+    private final CountDownLatch latch = new CountDownLatch(1);
+    private StartupMetrics metrics;
+
+    @Override
+    public void onStartupComplete(StartupMetrics metrics) {
+        this.metrics = metrics;
+        latch.countDown();
+    }
+
+    public StartupMetrics getMetrics() {
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException(e);
+        }
+        return metrics;
+    }
+    
+}
\ No newline at end of file
diff --git a/metrics/osgi/consumers/bnd.bnd b/metrics/osgi/consumers/bnd.bnd
new file mode 100644
index 0000000..0be6687
--- /dev/null
+++ b/metrics/osgi/consumers/bnd.bnd
@@ -0,0 +1 @@
+Conditional-Package: org.apache.felix.utils.json
\ No newline at end of file
diff --git a/metrics/osgi/consumers/pom.xml b/metrics/osgi/consumers/pom.xml
new file mode 100644
index 0000000..5b02583
--- /dev/null
+++ b/metrics/osgi/consumers/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling-bundle-parent</artifactId>
+        <version>38</version>
+        <relativePath />
+    </parent>
+
+    <groupId>org.apache.felix</groupId>
+    <artifactId>org.apache.felix.metrics.osgi.consumers</artifactId>
+    <version>0.1.0-SNAPSHOT</version>
+
+    <name>Apache Felix OSGi Metrics Consumers</name>
+    <description> 
+        Provides various out-of-the-box consumers for OSGi framework metrics.
+    </description>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>biz.aQute.bnd</groupId>
+                <artifactId>bnd-baseline-maven-plugin</artifactId>
+                <configuration>
+                    <failOnMissing>false</failOnMissing>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.annotation.versioning</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.annotation.bundle</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.metatype.annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.dropwizard.metrics</groupId>
+            <artifactId>metrics-core</artifactId>
+            <version>3.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.metrics.osgi.collector</artifactId>
+            <version>0.1.0-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+             <artifactId>org.apache.felix.utils</artifactId>
+             <version>1.11.4</version>
+             <scope>provided</scope>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>3.3.3</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/dropwizard/DropwizardMetricsListener.java b/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/dropwizard/DropwizardMetricsListener.java
new file mode 100644
index 0000000..7333576
--- /dev/null
+++ b/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/dropwizard/DropwizardMetricsListener.java
@@ -0,0 +1,85 @@
+/*
+ * 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.metrics.osgi.consumers.impl.dropwizard;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.felix.metrics.osgi.StartupMetrics;
+import org.apache.felix.metrics.osgi.StartupMetricsListener;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricRegistry;
+
+@Component
+@Designate(ocd = DropwizardMetricsListener.Config.class)
+public class DropwizardMetricsListener implements StartupMetricsListener {
+
+    @ObjectClassDefinition(name = "Apache Felix Dropwizard Startup Metrics Listener")
+    public @interface Config {
+        @AttributeDefinition(name = "Service Restart Threshold", description="Minimum number of service restarts during startup needed to create a metric for the service")
+        int serviceRestartThreshold() default 3;
+        @AttributeDefinition(name = "Slow Bundle Startup Threshold", description="Minimum bundle startup duration in milliseconds needed to create a metric for the bundle")
+        long slowBundleThresholdMillis() default 200;
+    }
+
+    private static final String APPLICATION_STARTUP_GAUGE_NAME = "osgi.application_startup_time_millis";
+    private static final String BUNDLE_STARTUP_GAUGE_NAME_PREFIX = "osgi.slow_bundle_startup_time_millis.";
+    private static final String SERVICE_RESTART_GAUGE_NAME_PREFIX = "osgi.excessive_service_restarts_count.";
+    
+    @Reference
+    private MetricRegistry registry;
+    
+    private int serviceRestartThreshold;
+    private long slowBundleThresholdMillis;
+    private List<String> registeredMetricNames = new ArrayList<>();
+    
+    @Activate
+    protected void activate(Config cfg) {
+        this.serviceRestartThreshold = cfg.serviceRestartThreshold();
+        this.slowBundleThresholdMillis = cfg.slowBundleThresholdMillis();
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        registeredMetricNames.forEach( m -> registry.remove(m) );
+    }
+    
+    @Override
+    public void onStartupComplete(StartupMetrics event) {
+        register(APPLICATION_STARTUP_GAUGE_NAME, (Gauge<Long>) () -> event.getStartupTime().toMillis() );
+        event.getBundleStartDurations().stream()
+            .filter( bsd -> bsd.getStartedAfter().toMillis() >= slowBundleThresholdMillis )
+            .forEach( bsd -> register(BUNDLE_STARTUP_GAUGE_NAME_PREFIX + bsd.getSymbolicName(), (Gauge<Long>) () -> bsd.getStartedAfter().toMillis()));
+        event.getServiceRestarts().stream()
+            .filter( src -> src.getServiceRestarts() >= serviceRestartThreshold )
+            .forEach( src -> register(SERVICE_RESTART_GAUGE_NAME_PREFIX + src.getServiceIdentifier(), (Gauge<Integer>) src::getServiceRestarts) );
+    }
+    
+    private void register(String name, Metric metric) {
+        registry.register(name, metric);
+        registeredMetricNames.add(name);
+    }
+}
diff --git a/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/json/JsonWritingMetricsListener.java b/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/json/JsonWritingMetricsListener.java
new file mode 100644
index 0000000..4174e39
--- /dev/null
+++ b/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/json/JsonWritingMetricsListener.java
@@ -0,0 +1,95 @@
+/*
+ * 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.metrics.osgi.consumers.impl.json;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import org.apache.felix.metrics.osgi.BundleStartDuration;
+import org.apache.felix.metrics.osgi.ServiceRestartCounter;
+import org.apache.felix.metrics.osgi.StartupMetrics;
+import org.apache.felix.metrics.osgi.StartupMetricsListener;
+import org.apache.felix.utils.json.JSONWriter;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+public class JsonWritingMetricsListener implements StartupMetricsListener {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+    
+    private BundleContext ctx;
+
+    @Activate
+    protected void activate(BundleContext ctx) {
+        this.ctx = ctx;
+    }
+
+    @Override
+    public void onStartupComplete(StartupMetrics metrics) {
+
+        File metricsFile = ctx.getDataFile("startup-metrics-" + System.currentTimeMillis() + ".json");
+        if ( metricsFile == null ) {
+            logger.warn("Unable to get data file in the bundle area, startup metrics will not be written");
+            return;
+        }
+        
+        try {
+            try ( FileWriter fw = new FileWriter(metricsFile)) {
+                JSONWriter w = new JSONWriter(fw);
+                w.object();
+                // application metrics
+                w.key("application");
+                w.object();
+                w.key("startTimeMillis").value(metrics.getJvmStartup().toEpochMilli());
+                w.key("startDurationMillis").value(metrics.getStartupTime().toMillis());
+                w.endObject();
+                
+                // bundle metrics
+                w.key("bundles");
+                w.array();
+                for ( BundleStartDuration bsd : metrics.getBundleStartDurations() ) {
+                    w.object();
+                    w.key("symbolicName").value(bsd.getSymbolicName());
+                    w.key("startTimeMillis").value(bsd.getStartingAt().toEpochMilli());
+                    w.key("startDurationMillis").value(bsd.getStartedAfter().toMillis());
+                    w.endObject();
+                }
+                w.endArray();
+                
+                // service metrics
+                w.key("services");
+                w.array();
+                for ( ServiceRestartCounter src : metrics.getServiceRestarts() ) {
+                    w.object();
+                    w.key("identifier").value(src.getServiceIdentifier());
+                    w.key("restarts").value(src.getServiceRestarts());
+                    w.endObject();
+                }
+                w.endArray();
+                
+                w.endObject();
+            }
+        } catch (IOException e) {
+            logger.warn("Failed wrting startup metrics", e);
+        }
+    }
+}
diff --git a/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/log/LoggingMetricsListener.java b/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/log/LoggingMetricsListener.java
new file mode 100644
index 0000000..e96619e
--- /dev/null
+++ b/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/log/LoggingMetricsListener.java
@@ -0,0 +1,92 @@
+/*
+ * 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.metrics.osgi.consumers.impl.log;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.felix.metrics.osgi.BundleStartDuration;
+import org.apache.felix.metrics.osgi.ServiceRestartCounter;
+import org.apache.felix.metrics.osgi.StartupMetrics;
+import org.apache.felix.metrics.osgi.StartupMetricsListener;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+@Designate(ocd = LoggingMetricsListener.Config.class)
+public class LoggingMetricsListener implements StartupMetricsListener {
+    
+    @ObjectClassDefinition(name = "Apache Felix Logging Startup Metrics Listener")
+    public @interface Config {
+        
+        @AttributeDefinition(name = "Service Restart Threshold", description="Minimum number of service restarts during startup needed log the number of service restarts")
+        int serviceRestartThreshold() default 3;
+        @AttributeDefinition(name = "Slow Bundle Startup Threshold", description="Minimum bundle startup duration in milliseconds needed to log the bundle startup time")
+        long slowBundleThresholdMillis() default 200;
+    }
+
+    private int serviceRestartThreshold;
+    private long slowBundleThresholdMillis;
+    
+    @Activate
+    protected void activate(Config cfg) {
+        this.serviceRestartThreshold = cfg.serviceRestartThreshold();
+        this.slowBundleThresholdMillis = cfg.slowBundleThresholdMillis();
+    }
+    
+    @Override
+    public void onStartupComplete(StartupMetrics event) {
+        Logger log = LoggerFactory.getLogger(getClass());
+        log.info("Application startup completed in {}", event.getStartupTime());
+
+        List<BundleStartDuration> slowStartBundles = event.getBundleStartDurations().stream()
+                .filter( bsd -> bsd.getStartedAfter().toMillis() >= slowBundleThresholdMillis )
+                .collect(Collectors.toList());
+        
+        if ( !slowStartBundles.isEmpty() && log.isInfoEnabled() ) {
+            StringBuilder logEntry = new StringBuilder();
+            logEntry.append("The following bundles started in more than ")
+                .append(slowBundleThresholdMillis)
+                .append(" milliseconds: \n");
+            slowStartBundles
+                .forEach( ssb -> logEntry.append("- ").append(ssb.getSymbolicName()).append(" : ").append(ssb.getStartedAfter()).append('\n'));
+            
+            log.info(logEntry.toString());
+        }
+        
+        List<ServiceRestartCounter> oftenRestartedServices = event.getServiceRestarts().stream()
+                .filter( src -> src.getServiceRestarts() >= serviceRestartThreshold )
+                .collect(Collectors.toList());
+        
+        if ( !oftenRestartedServices.isEmpty() && log.isInfoEnabled() ) {
+            StringBuilder logEntry = new StringBuilder();
+            logEntry.append("The following services have restarted more than ")
+                .append(serviceRestartThreshold)
+                .append(" times during startup :\n");
+            oftenRestartedServices
+                .forEach(ors -> logEntry.append("- ").append(ors.getServiceIdentifier()).append(" : ").append(ors.getServiceRestarts()).append(" restarts\n"));
+            
+            log.info(logEntry.toString());
+        }
+    }
+
+}
diff --git a/metrics/osgi/consumers/src/test/java/org/apache/felix/metrics/osgi/consumers/impl/json/JsonWritingMetricsListenerTest.java b/metrics/osgi/consumers/src/test/java/org/apache/felix/metrics/osgi/consumers/impl/json/JsonWritingMetricsListenerTest.java
new file mode 100644
index 0000000..bd877f4
--- /dev/null
+++ b/metrics/osgi/consumers/src/test/java/org/apache/felix/metrics/osgi/consumers/impl/json/JsonWritingMetricsListenerTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.metrics.osgi.consumers.impl.json;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+
+import org.apache.felix.metrics.osgi.BundleStartDuration;
+import org.apache.felix.metrics.osgi.ServiceRestartCounter;
+import org.apache.felix.metrics.osgi.StartupMetrics;
+import org.apache.felix.metrics.osgi.consumers.impl.json.JsonWritingMetricsListener;
+import org.apache.felix.utils.json.JSONParser;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mockito;
+import org.osgi.framework.BundleContext;
+
+public class JsonWritingMetricsListenerTest {
+
+    @Rule
+    public TemporaryFolder tmp = new TemporaryFolder();
+    
+    @Test
+    public void metricsArePersisted() throws IOException {
+        // bridge the mock bundle context with the temporary folder
+        BundleContext mockBundleContext = mock(BundleContext.class);
+        when(mockBundleContext.getDataFile(Mockito.anyString())).thenAnswer( i -> tmp.newFile(i.getArgument(0, String.class)));
+        
+        JsonWritingMetricsListener listener = new JsonWritingMetricsListener();
+        listener.activate(mockBundleContext);
+        
+        StartupMetrics metrics = StartupMetrics.Builder
+                .withJvmStartup(Instant.now())
+                .withStartupTime(Duration.ofMillis(50))
+                .withBundleStartDurations(Arrays.asList(new BundleStartDuration("foo", Instant.now(), Duration.ofMillis(5))))
+                .withServiceRestarts(Arrays.asList(new ServiceRestartCounter("some.service", 1)))
+                .build();
+        
+        listener.onStartupComplete(metrics);
+        
+        File[] files = tmp.getRoot().listFiles();
+        
+        assertThat("Bundle data area should hold one file", files.length, equalTo(1));
+        
+        File metricsFile = files[0];
+        try ( FileInputStream fis = new FileInputStream(metricsFile)) {
+            JSONParser p = new JSONParser(fis);
+            assertThat(p.getParsed().keySet(), hasItem("application"));
+            assertThat(p.getParsed().keySet(), hasItem("bundles"));
+            assertThat(p.getParsed().keySet(), hasItem("services"));
+        }
+    }
+}
diff --git a/metrics/osgi/pom.xml b/metrics/osgi/pom.xml
new file mode 100644
index 0000000..0e6ac13
--- /dev/null
+++ b/metrics/osgi/pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+--><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache</groupId>
+        <artifactId>apache</artifactId>
+        <version>18</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>org.apache.felix</groupId>
+    <artifactId>felix-metrics-osgi-builder</artifactId>
+    <packaging>pom</packaging>
+    <version>1</version>
+
+    <name>Apache Felix OSGi Metrics (Builder)</name>
+
+    <modules>
+          <module>collector</module>
+          <module>consumers</module>
+    </modules>
+</project>
+
diff --git a/scr/pom.xml b/scr/pom.xml
index 88a1347..a3c4371 100644
--- a/scr/pom.xml
+++ b/scr/pom.xml
@@ -30,7 +30,7 @@
         Implementation of the Declarative Services specification 1.4
     </description>
     <artifactId>org.apache.felix.scr</artifactId>
-    <version>2.1.21-SNAPSHOT</version>
+    <version>2.1.23-SNAPSHOT</version>
     <scm>
         <connection>scm:git:https://github.com/apache/felix-dev.git</connection>
         <developerConnection>scm:git:https://github.com/apache/felix-dev.git</developerConnection>
@@ -144,7 +144,18 @@
             <version>2.28.2</version>
             <scope>test</scope>
         </dependency>
-
+		<dependency>
+			<groupId>org.assertj</groupId>
+			<artifactId>assertj-core</artifactId>
+			<version>2.9.1</version>
+            <scope>test</scope>
+		</dependency>
+         <dependency>
+            <groupId>biz.aQute.bnd</groupId>
+            <artifactId>biz.aQute.bndlib</artifactId>
+            <version>5.1.1</version>
+            <scope>test</scope>
+        </dependency>
         <!-- Integration Testing with Pax Exam -->
         <dependency>
             <groupId>org.ops4j.pax.exam</groupId>
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/Activator.java b/scr/src/main/java/org/apache/felix/scr/impl/Activator.java
index 5235f04..a419f43 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/Activator.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/Activator.java
@@ -43,6 +43,7 @@
 import org.apache.felix.scr.impl.config.ScrConfigurationImpl;
 import org.apache.felix.scr.impl.inject.internal.ClassUtils;
 import org.apache.felix.scr.impl.logger.InternalLogger.Level;
+import org.apache.felix.scr.impl.logger.ScrLogManager;
 import org.apache.felix.scr.impl.logger.ScrLogger;
 import org.apache.felix.scr.impl.manager.ComponentHolder;
 import org.apache.felix.scr.impl.metadata.ComponentMetadata;
@@ -114,14 +115,13 @@
     {
         m_context = context;
         m_bundle = context.getBundle();
-        // require the log service
-        logger = new ScrLogger(m_configuration, m_context);
         // set bundle context for PackageAdmin tracker
         ClassUtils.setBundleContext( context );
         // get the configuration
         m_configuration.start( m_context ); //this will call restart, which calls super.start.
     }
 
+
     public void restart(boolean globalExtender)
     {
         m_componentMetadataStore = load(m_context, logger,
@@ -208,7 +208,7 @@
         super.stop( context );
         m_configuration.stop();
         store(m_componentMetadataStore, context, logger, m_configuration.cacheMetadata());
-        logger.closeTracker();
+        logger.close();
     }
 
     @Override
@@ -651,4 +651,17 @@
             }
         }
     }
+
+    public void setLogger()
+    {
+        // TODO we only set the logger once
+        // If the need arises to be able to dynamically set the logger type
+        // then more work is needed to do that switch
+        // for now we only can configure ds.log.extension with context properties
+        if (logger == null)
+        {
+            logger = ScrLogManager.scr(m_context, m_configuration);
+        }
+
+    }
 }
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/BundleComponentActivator.java b/scr/src/main/java/org/apache/felix/scr/impl/BundleComponentActivator.java
index 1e880a3..1bf5108 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/BundleComponentActivator.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/BundleComponentActivator.java
@@ -199,7 +199,7 @@
     throws ComponentException
     {
         // create a logger on behalf of the bundle
-        this.logger = new BundleLogger(context.getBundle(), scrLogger);
+        this.logger = scrLogger.bundle(context.getBundle());
         // keep the parameters for later
         m_componentRegistry = componentRegistry;
         m_componentActor = componentActor;
@@ -432,7 +432,7 @@
 
     void validateAndRegister(ComponentMetadata metadata)
     {
-        final ComponentLogger componentLogger = new ComponentLogger(metadata, logger);
+        final ComponentLogger componentLogger = logger.component(m_bundle, metadata.getImplementationClassName(), metadata.getName());
         ComponentRegistryKey key = null;
         try
         {
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/config/ScrConfigurationImpl.java b/scr/src/main/java/org/apache/felix/scr/impl/config/ScrConfigurationImpl.java
index f504238..6d6fd20 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/config/ScrConfigurationImpl.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/config/ScrConfigurationImpl.java
@@ -82,6 +82,7 @@
     private boolean infoAsService;
 
     private boolean cacheMetadata;
+    private boolean logExtension;
 
     private long lockTimeout = DEFAULT_LOCK_TIMEOUT_MILLISECONDS;
 
@@ -178,6 +179,7 @@
                         serviceChangecountTimeout = DEFAULT_SERVICE_CHANGECOUNT_TIMEOUT_MILLISECONDS;
                         newGlobalExtender = false;
                         cacheMetadata = false;
+                        logExtension = false;
                     }
                     else
                     {
@@ -190,6 +192,7 @@
                         serviceChangecountTimeout = getServiceChangecountTimeout();
                         newGlobalExtender = getDefaultGlobalExtender();
                         cacheMetadata = getDefaultCacheMetadata();
+                        logExtension = getDefaultLogExtension();
                     }
                 }
                 else
@@ -210,6 +213,7 @@
                 newGlobalExtender = VALUE_TRUE.equalsIgnoreCase( String.valueOf( config.get( PROP_GLOBAL_EXTENDER) ) );
                 cacheMetadata = VALUE_TRUE.equalsIgnoreCase(
                     String.valueOf(config.get(PROP_CACHE_METADATA)));
+                logExtension = VALUE_TRUE.equalsIgnoreCase(String.valueOf(config.get(PROP_LOG_EXTENSION)));
             }
             if ( scrCommand != null )
             {
@@ -218,13 +222,14 @@
             oldGlobalExtender = this.globalExtender;
             this.globalExtender = newGlobalExtender;
         }
+        activator.setLogger();
         if ( newGlobalExtender != oldGlobalExtender )
         {
             activator.restart( newGlobalExtender );
         }
     }
 
-    /**
+	/**
      * Returns the current log level.
      * Note that this log level is not used with an R7 LogService implementation.
      * @return
@@ -249,7 +254,8 @@
         return keepInstances;
     }
 
-    @Override
+    @SuppressWarnings("deprecation")
+	@Override
     public boolean infoAsService()
     {
         return infoAsService;
@@ -402,4 +408,14 @@
         // default log level (errors only)
         return Level.ERROR;
     }
+
+	@Override
+	public boolean isLogExtension() {
+		return logExtension;
+	}
+
+    private boolean getDefaultLogExtension() {
+        return VALUE_TRUE.equalsIgnoreCase( bundleContext.getProperty( PROP_LOG_EXTENSION) );
+	}
+
 }
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/AbstractLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/AbstractLogger.java
deleted file mode 100644
index 87236c6..0000000
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/AbstractLogger.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * 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.scr.impl.logger;
-
-import java.text.MessageFormat;
-
-import org.apache.felix.scr.impl.logger.InternalLogger.Level;
-import org.osgi.framework.Bundle;
-
-/**
- * This is a common base for all loggers
- *
- */
-public abstract class AbstractLogger
-{
-
-    /**
-     * The prefix put for each log message
-     */
-    private volatile String prefix;
-
-    AbstractLogger(final String prefix)
-    {
-        this.prefix = prefix;
-    }
-
-    void setPrefix(final String value)
-    {
-        this.prefix = value;
-    }
-
-    String getPrefix()
-    {
-        return this.prefix;
-    }
-
-    /**
-     * Get the internal logger
-     * @return The internal logger
-     */
-    abstract InternalLogger getLogger();
-
-    /**
-     * Returns {@code true} if logging for the given level is enabled.
-     */
-    public boolean isLogEnabled(final Level level)
-    {
-        final InternalLogger l = getLogger();
-        return l.isLogEnabled(level);
-    }
-
-    /**
-     * Method to actually emit the log message. If the LogService is available,
-     * the message will be logged through the LogService. Otherwise the message
-     * is logged to stdout (or stderr in case of LOG_ERROR level messages),
-     *
-     * @param level The log level to log the message at
-     * @param pattern The {@code java.text.MessageFormat} message format
-     *      string for preparing the message
-     * @param ex An optional <code>Throwable</code> whose stack trace is written,
-     * @param arguments The format arguments for the <code>pattern</code>
-     *      string.
-     */
-    public boolean log(final Level level, final String pattern, final Throwable ex,
-        final Object... arguments)
-    {
-        if ( isLogEnabled( level ) )
-        {
-            getLogger().log(level, format(pattern, arguments), ex);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Method to actually emit the log message. If the LogService is available,
-     * the message will be logged through the LogService. Otherwise the message
-     * is logged to stdout (or stderr in case of LOG_ERROR level messages),
-     *
-     * @param level The log level of the messages. This corresponds to the log
-     *          levels defined by the OSGi LogService.
-     * @param message The message to print
-     * @param ex The <code>Throwable</code> causing the message to be logged.
-     */
-    public boolean log(final Level level, final String message, final Throwable ex)
-    {
-        if ( isLogEnabled( level ) )
-        {
-            getLogger().log(level, prefix.concat(" ").concat(message), ex);
-            return true;
-        }
-        return false;
-    }
-
-    static String getBundleIdentifier(final Bundle bundle)
-    {
-        final StringBuilder sb = new StringBuilder("bundle ");
-        // symbolic name might be null
-        if ( bundle.getSymbolicName() != null )
-        {
-            sb.append(bundle.getSymbolicName());
-            sb.append(':');
-            sb.append(bundle.getVersion());
-            sb.append( " (" );
-            sb.append( bundle.getBundleId() );
-            sb.append( ")" );
-        }
-        else
-        {
-            sb.append( bundle.getBundleId() );
-        }
-
-        return sb.toString();
-    }
-
-    private String format( final String pattern, final Object... arguments )
-    {
-        final String message;
-        if ( arguments == null || arguments.length == 0 )
-        {
-            message = pattern;
-        }
-        else
-        {
-            for(int i=0;i<arguments.length;i++)
-            {
-                if ( arguments[i] instanceof Bundle )
-                {
-                    arguments[i] = getBundleIdentifier((Bundle)arguments[i]);
-                }
-            }
-            message = MessageFormat.format( pattern, arguments );
-        }
-        return prefix.concat(message);
-    }
-}
\ No newline at end of file
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/BundleLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/BundleLogger.java
index 241b9c8..6f9c6c2 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/BundleLogger.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/logger/BundleLogger.java
@@ -18,83 +18,14 @@
  */
 package org.apache.felix.scr.impl.logger;
 
-import org.apache.felix.scr.impl.logger.InternalLogger.Level;
 import org.osgi.framework.Bundle;
-import org.osgi.service.log.Logger;
-import org.osgi.service.log.LoggerFactory;
 
 /**
  * The {@code BundleLogger} defines a simple API to enable some logging on behalf of
  * an extended bundle. This avoids that all clients doing logging on behalf of
  * a component bundle need to pass in things like {@code BundleContext}.
  */
-public class BundleLogger extends LogServiceEnabledLogger
-{
-    private final ScrLogger parent;
+public interface BundleLogger  extends InternalLogger  {
 
-    public BundleLogger(final Bundle bundle, final ScrLogger parent)
-    {
-        super(bundle, parent.getLoggerFactoryTracker());
-        this.parent = parent;
-    }
-
-    @Override
-    InternalLogger getDefaultLogger()
-    {
-        return new InternalLogger()
-        {
-
-            @Override
-            public void log(final Level level, final String message,
-                final Throwable ex)
-            {
-                parent.getLogger().log(level, message, ex);
-            }
-
-            @Override
-            public boolean isLogEnabled(final Level level)
-            {
-                return parent.getLogger().isLogEnabled(level);
-            }
-        };
-    }
-
-    int getTrackingCount()
-    {
-        return trackingCount;
-    }
-
-    InternalLogger getLogger(final String className)
-    {
-        if ( className != null )
-        {
-            final LoggerFactory factory = this.loggingFactoryTracker.getService();
-            if (factory != null)
-            {
-                return new OSGiLogger(factory.getLogger(bundle, className, Logger.class));
-            }
-        }
-        return this.getLogger();
-    }
-
-    @Override
-    public boolean log(final Level level, final String pattern, final Throwable ex,
-        final Object... arguments)
-    {
-        // delegate to parent if not logging
-        if ( !super.log(level, pattern, ex, arguments) ) {
-            return this.parent.log(level, pattern, ex, arguments);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean log(final Level level, final String message, final Throwable ex)
-    {
-        // delegate to parent if not logging
-        if ( !super.log(level, message, ex) ) {
-            return this.parent.log(level, message, ex);
-        }
-        return false;
-    }
+	ComponentLogger component(Bundle m_bundle, String implementationClassName, String name);
 }
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/ComponentLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/ComponentLogger.java
index 13252c5..facf595 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/ComponentLogger.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/logger/ComponentLogger.java
@@ -18,98 +18,11 @@
  */
 package org.apache.felix.scr.impl.logger;
 
-import org.apache.felix.scr.impl.logger.InternalLogger.Level;
-import org.apache.felix.scr.impl.metadata.ComponentMetadata;
-
 /**
  * The {@code ComponentLogger} is the logger to be used to log on behalf of a component.
  * This avoids avoids that all clients doing logging on behalf of a component need to
  * pass in things like {@code ComponentMetadata} or the component Id.
  */
-public class ComponentLogger extends AbstractLogger
-{
-    private final String name;
-
-    private final String className;
-
-    private final BundleLogger parent;
-
-    private volatile int trackingCount = -3;
-
-    private volatile InternalLogger currentLogger;
-
-    public ComponentLogger(final ComponentMetadata metadata, final BundleLogger parent)
-    {
-        super(""); // we set the prefix later
-        this.parent = parent;
-        if ( metadata.getName() != null )
-        {
-            this.name = metadata.getName();
-        }
-        else if ( metadata.getImplementationClassName() != null )
-        {
-            this.name = "implementation class " + metadata.getImplementationClassName();
-        }
-        else
-        {
-            this.name = "UNKNOWN";
-        }
-        if ( metadata.getImplementationClassName() != null )
-        {
-            this.className = metadata.getImplementationClassName();
-        }
-        else
-        {
-            this.className = null;
-        }
-        this.setComponentId(-1);
-    }
-
-    /**
-     * Update the logger with the correct component id.
-     * @param id The component id
-     */
-    public void setComponentId(final long id)
-    {
-        if ( id > -1 )
-        {
-            this.setPrefix(this.parent.getPrefix() + "[" + name + "(" + id + ")] : ");
-        }
-        else
-        {
-            this.setPrefix(this.parent.getPrefix() + "[" + name + "] : ");
-        }
-    }
-
-    @Override
-    InternalLogger getLogger()
-    {
-        if ( this.trackingCount < this.parent.getTrackingCount() )
-        {
-            this.currentLogger = this.parent.getLogger(this.className);
-            this.trackingCount = this.parent.getTrackingCount();
-        }
-        return currentLogger;
-    }
-
-    @Override
-    public boolean log(final Level level, final String pattern, final Throwable ex,
-        final Object... arguments)
-    {
-        // delegate to parent if not logging
-        if ( !super.log(level, pattern, ex, arguments) ) {
-            return this.parent.log(level, pattern, ex, arguments);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean log(final Level level, final String message, final Throwable ex)
-    {
-        // delegate to parent if not logging
-        if ( !super.log(level, message, ex) ) {
-            return this.parent.log(level, message, ex);
-        }
-        return false;
-    }
+public interface ComponentLogger extends InternalLogger {
+	void setComponentId(long m_componentId);
 }
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/ExtLogManager.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/ExtLogManager.java
new file mode 100644
index 0000000..1752987
--- /dev/null
+++ b/scr/src/main/java/org/apache/felix/scr/impl/logger/ExtLogManager.java
@@ -0,0 +1,79 @@
+/*
+ * 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.scr.impl.logger;
+
+import org.apache.felix.scr.impl.manager.ScrConfiguration;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Implements an extension to the SCR log manager that uses logger names to
+ * create a hierarchy of loggers. All messages will be logged via the SCR
+ * logger's bundle unlike the classic scr log manager that used the bundle's logger.
+ * 
+ * <ul>
+ * <li>An ScrLogger will log with the name {@value #SCR_LOGGER_NAME}
+ * <li>A BundleLogger will log with the name {@value #SCR_LOGGER_PREFIX} + the
+ * bundle symbolic name
+ * <li>A ComponentLogger will log with the name {@value #SCR_LOGGER_PREFIX} +
+ * the bundle symbolic name + "." + component name
+ * </ul>
+ */
+class ExtLogManager extends ScrLogManager {
+	public static String	SCR_LOGGER_NAME		= "org.apache.felix.scr.impl";
+	public static String	SCR_LOGGER_PREFIX	= "org.apache.felix.scr.";
+	private final Bundle	bundle;
+
+	ExtLogManager(BundleContext context, ScrConfiguration config) {
+		super(context, config);
+		this.bundle = context.getBundle();
+	}
+
+	@Override
+	public ScrLogger scr() {
+		return getLogger(bundle, SCR_LOGGER_NAME, ScrLoggerFacade.class);
+	}
+
+	@Override
+	public BundleLogger bundle(Bundle bundle) {
+		return getLogger(bundle, SCR_LOGGER_PREFIX.concat(bundle.getSymbolicName()), ScrLoggerFacade.class);
+	}
+
+	@Override
+	public ComponentLogger component(Bundle bundle, String implementationClass, String componentName) {
+
+		assert bundle != null;
+		assert bundle.getSymbolicName() != null : "scr requires recent bundles";
+		assert implementationClass != null;
+		assert componentName != null;
+
+		String loggerName = SCR_LOGGER_PREFIX.concat(bundle.getSymbolicName()).concat(".").concat(componentName);
+		ScrLoggerFacade logger = getLogger(bundle, loggerName, ScrLoggerFacade.class);
+		logger.setPrefix("["+componentName+"]");
+		return logger;
+	}
+
+	String componentPrefix(ScrLoggerFacade slf, long id) {
+		assert slf.prefix != null; 
+		if ( slf.prefix.indexOf(')')<0)
+			return slf.prefix.replace("]", "(" + id + ")]");
+		else
+			return slf.prefix;
+	}
+}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/InternalLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/InternalLogger.java
index 2295dc9..4eca75f 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/InternalLogger.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/logger/InternalLogger.java
@@ -18,19 +18,93 @@
  */
 package org.apache.felix.scr.impl.logger;
 
-public interface InternalLogger
-{
-    public enum Level
-    {
-        AUDIT, ERROR, WARN, INFO, DEBUG, TRACE;
+import java.text.MessageFormat;
 
-        public boolean implies(Level other)
-        {
-            return ordinal() >= other.ordinal();
-        }
-    }
+import org.apache.felix.scr.impl.manager.ScrConfiguration;
+import org.osgi.service.log.admin.LoggerAdmin;
 
-    void log(Level level, String message, Throwable ex);
+/**
+ * Base interface for the different SCR Loggers. Since this is not used outside
+ * this package, it could be private. However, then Level should be standalone,
+ * which would change most files. So minimize the code change, it is kept public.
+ */
+public interface InternalLogger {
+	/**
+	 * The level to log. This is aligned with the OSGi LogLevel
+	 */
+	public enum Level {
+		AUDIT {
+			boolean err() {
+				return true;
+			}
+		},
+		ERROR {
+			boolean err() {
+				return true;
+			}
+		},
+		WARN, INFO, DEBUG, TRACE;
 
-    boolean isLogEnabled(Level level);
+		/**
+		 * Check if this log level is higher or the same of the other level.
+		 * 
+		 * @param other
+		 *                  the other level
+		 * @return true if the level other should be logged
+		 */
+		boolean implies(Level other) {
+			return ordinal() >= other.ordinal();
+		}
+
+		boolean err() {
+			return false;
+		}
+
+	}
+
+	/**
+	 * Logs the message to an appropriate OSGi logger. If not such logger can be
+	 * found then it will log to stderr for ERROR & AUDIT messages and stdout
+	 * for other messages
+	 * 
+	 * @param level
+	 *                    only log when this level is implied by the current log
+	 *                    level
+	 * @param message
+	 *                    the message to log
+	 * @param ex
+	 *                    a Throwable or null
+	 */
+	void log(Level level, String message, Throwable ex);
+
+	/**
+	 * Formats the message using the {@link MessageFormat} class, i.e. with {}
+	 * place holders for the args. It then calls
+	 * {@link #log(Level, String, Throwable)}.
+	 * 
+	 * @param level
+	 *                    only log when this level is implied by the current log
+	 *                    level
+	 * @param message
+	 *                    the message to log
+	 * @param ex
+	 *                    a Throwable or null
+	 * @param args
+	 *                    the arguments to the {@link MessageFormat} formatting
+	 */
+	void log(Level level, String message, Throwable ex, Object... args);
+
+	/**
+	 * Answer true if the current logging level is enabled for the given level.
+	 * For stdout/stderr fallback the logging level is defined by the
+	 * {@link ScrConfiguration#getLogLevel()}. If there is an OSGi logger
+	 * available then the logger name will define the log level via
+	 * {@link LoggerAdmin}.
+	 *
+	 * @param level
+	 *                  the level to check
+	 * @return true if the given log level is enabled
+	 */
+	boolean isLogEnabled(Level level);
+
 }
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/LogManager.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/LogManager.java
new file mode 100644
index 0000000..d5cd008
--- /dev/null
+++ b/scr/src/main/java/org/apache/felix/scr/impl/logger/LogManager.java
@@ -0,0 +1,236 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.LoggerFactory;
+import org.osgi.util.tracker.ServiceTracker;
+
+/*
+ * A class that tracks the LoggerFactory of a bundle and its associated Logger objects. 
+ * When a bundle is stopped and/or the service is unregistered, the logger objects are cleaned.
+ * 
+ * The basic technique is to use a facade. Instead of returning a log object, we return a facade. The 
+ * methods delegate to the actual logger object. If there is no logger object, we create one.
+ * 
+ * The LogDomain represents every bundle. Per LogDomain, we keep the facades. If the factory goes,
+ * we reset the facades.
+ */
+class LogManager extends ServiceTracker<Object, Object> implements BundleListener {
+
+    private static final String LOGGER_FACTORY_CLASS_NAME = "org.osgi.service.log.LoggerFactory";
+
+	final BundleContext	scrContext;
+	final AtomicBoolean	closed	= new AtomicBoolean(false);
+
+	/*
+	 * Locks access to guarded fields
+	 */
+	class Lock {
+		final Map<Bundle, LogDomain>	domains	= new HashMap<>();
+		int								trackingCount;
+		Object					factory;
+		int								ranking=0;
+
+		synchronized LogDomain getLogDomain(Bundle bundle) {
+			LogDomain domain = domains.get(bundle);
+			if (domain == null) {
+				domain = new LogDomain(bundle);
+				domains.put(bundle, domain);
+			}
+			return domain;
+		}
+
+		synchronized void removedFactory(Object service) {
+			if (this.factory == service) {
+				this.factory = null;
+				reset();
+			}
+		}
+
+		synchronized void setFactory(int ranking, Object service) {
+			if (this.factory == null) {
+				this.factory = service;
+				this.ranking = ranking;
+			} else if (this.ranking < ranking) {
+				this.factory = service;
+				this.ranking = ranking;
+				reset();
+			}
+		}
+
+		synchronized void reset() {
+			for (LogDomain domain : domains.values()) {
+				domain.reset();
+			}
+		}
+
+		synchronized Object getLogger(LoggerFacade facade, Bundle bundle, String name) {
+			if (factory == null)
+				return facade.logger = null;
+			else
+				return facade.logger = ((LoggerFactory) factory).getLogger(bundle, name, Logger.class);
+		}
+
+		synchronized LogDomain remove(Bundle bundle) {
+			return domains.remove(bundle);
+		}
+
+		synchronized void close() {
+			reset();
+			domains.clear();
+		}
+
+	}
+
+	final Lock lock = new Lock();
+
+	LogManager(BundleContext context) {
+		super(context, LOGGER_FACTORY_CLASS_NAME, null);
+		this.scrContext = context;
+		scrContext.addBundleListener(this);
+	}
+
+	@Override
+	public Object addingService(ServiceReference<Object> reference) {
+		Object service = super.addingService(reference);
+		Integer ranking = (Integer) reference.getProperty(Constants.SERVICE_RANKING);
+		if (ranking == null)
+			ranking = 0;
+		lock.setFactory(ranking, service);
+		return service;
+	}
+
+	@Override
+	public void removedService(ServiceReference<Object> reference, Object service) {
+		super.removedService(reference, service);
+		lock.removedFactory(service);
+	}
+
+	<T> T getLogger(Bundle bundle, String name, Class<T> type) {
+		return type.cast(lock.getLogDomain(bundle).getLogger(name));
+	}
+
+	@SuppressWarnings("resource")
+	@Override
+	public void bundleChanged(BundleEvent event) {
+		if (event.getType() == BundleEvent.STOPPED && !closed.get()) {
+			LogDomain domain = lock.remove(event.getBundle());
+			if (domain != null) {
+				domain.close();
+			}
+		}
+	}
+
+	/*
+	 * Tracks a bundle's LoggerFactory service
+	 */
+	class LogDomain
+			implements Closeable {
+
+		private final Bundle			bundle;
+		private final Set<LoggerFacade>	facades	= new HashSet<>();
+
+		LogDomain(Bundle bundle) {
+			this.bundle = bundle;
+		}
+
+		private void reset() {
+			synchronized (facades) {
+				for (LoggerFacade facade : facades) {
+					facade.reset();
+				}
+			}
+		}
+
+		LoggerFacade getLogger(String name) {
+			LoggerFacade facade = createLoggerFacade(this, name);
+			synchronized (facades) {
+				facades.add(facade);
+			}
+			return facade;
+		}
+
+		@Override
+		public void close() {
+			reset();
+		}
+
+	}
+
+	class LoggerFacade {
+		private final String	name;
+		private final LogDomain	domain;
+		volatile Object			logger;
+		volatile String			prefix;
+
+		LoggerFacade(LogDomain logDomain, String name) {
+			this.domain = logDomain;
+			this.name = name;
+		}
+
+		void reset() {
+			logger = null;
+		}
+
+		Object getLogger() {
+			Object l = this.logger;
+			if (l == null) {
+				l = lock.getLogger(this, domain.bundle, name);
+			}
+			return l;
+		}
+
+		Bundle getBundle() {
+			return domain.bundle;
+		}
+
+		String getName() {
+			return name;
+		}
+
+	}
+
+	public void close() {
+		if (closed.compareAndSet(false, true)) {
+			lock.close();
+			super.close();
+			this.context.removeBundleListener(this);
+		}
+	}
+
+	LoggerFacade createLoggerFacade(LogDomain logDomain, String name) {
+		assert !closed.get();
+		return new LoggerFacade(logDomain, name);
+	}
+
+}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/LogServiceEnabledLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/LogServiceEnabledLogger.java
deleted file mode 100644
index 933b3e5..0000000
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/LogServiceEnabledLogger.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.scr.impl.logger;
-
-import org.osgi.framework.Bundle;
-import org.osgi.service.log.Logger;
-import org.osgi.service.log.LoggerFactory;
-import org.osgi.util.tracker.ServiceTracker;
-
-/**
- * This abstract class adds support for using a LogService
- * (or LoggerFactory for R7+).
- */
-abstract class LogServiceEnabledLogger extends AbstractLogger
-{
-    // the log service to log messages to
-    protected final ServiceTracker<LoggerFactory, LoggerFactory> loggingFactoryTracker;
-
-    protected final Bundle bundle;
-
-    private volatile InternalLogger currentLogger;
-
-    protected volatile int trackingCount = -2;
-
-    public LogServiceEnabledLogger(final Bundle bundle, ServiceTracker<LoggerFactory, LoggerFactory> loggingFactoryTracker)
-    {
-        super(getBundleIdentifier(bundle));
-        this.bundle = bundle;
-        this.loggingFactoryTracker = loggingFactoryTracker;
-    }
-
-    @Override
-    InternalLogger getLogger()
-    {
-        if (this.trackingCount < this.loggingFactoryTracker.getTrackingCount())
-        {
-            final LoggerFactory factory = this.loggingFactoryTracker.getService();
-            if (factory == null)
-            {
-                this.currentLogger = this.getDefaultLogger();
-            }
-            else
-            {
-                this.currentLogger = new OSGiLogger(
-                    factory.getLogger(bundle, Logger.ROOT_LOGGER_NAME, Logger.class));
-            }
-            this.trackingCount = this.loggingFactoryTracker.getTrackingCount();
-        }
-        return currentLogger;
-    }
-
-    abstract InternalLogger getDefaultLogger();
-
-    ServiceTracker<LoggerFactory, LoggerFactory> getLoggerFactoryTracker()
-    {
-        return loggingFactoryTracker;
-    }
-}
\ No newline at end of file
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/OSGiLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/OSGiLogger.java
deleted file mode 100644
index 1a9fc07..0000000
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/OSGiLogger.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.scr.impl.logger;
-
-import org.osgi.service.log.Logger;
-
-final class OSGiLogger implements InternalLogger
-{
-
-    private final Logger logger;
-
-    public OSGiLogger(Logger logger)
-    {
-        this.logger = logger;
-    }
-
-    public void log(Level level, String message, Throwable ex)
-    {
-        if ( ex == null )
-        {
-            switch ( level )
-            {
-                case AUDIT : logger.audit(message); break;
-                case ERROR : logger.error(message); break;
-                case WARN : logger.warn(message); break;
-                case INFO : logger.info(message); break;
-                case DEBUG : logger.debug(message); break;
-                case TRACE : logger.trace(message); break;
-                default : logger.debug(message);
-            }
-        }
-        else
-        {
-            switch ( level )
-            {
-                case AUDIT : logger.audit(message, ex); break;
-                case ERROR : logger.error(message, ex); break;
-                case WARN : logger.warn(message, ex); break;
-                case INFO : logger.info(message, ex); break;
-                case DEBUG : logger.debug(message, ex); break;
-                case TRACE : logger.trace(message, ex); break;
-                default : logger.debug(message, ex);
-            }
-        }
-    }
-
-    public boolean isLogEnabled(Level level)
-    {
-        switch (level)
-        {
-            case AUDIT: return true;
-            case ERROR: return logger.isErrorEnabled();
-            case WARN: return logger.isWarnEnabled();
-            case INFO: return logger.isInfoEnabled();
-            case DEBUG: return logger.isDebugEnabled();
-            case TRACE: return logger.isTraceEnabled();
-            default: return logger.isDebugEnabled();
-        }
-    }
-}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogManager.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogManager.java
new file mode 100644
index 0000000..1aa1f70
--- /dev/null
+++ b/scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogManager.java
@@ -0,0 +1,303 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+
+import org.apache.felix.scr.impl.logger.InternalLogger.Level;
+import org.apache.felix.scr.impl.manager.ScrConfiguration;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.Logger;
+
+/**
+ * Implements a SCR based log manager. This class was needed to not change the
+ * whole codebase. It looks very similar to the old logging but leverages the
+ * {@link LogManager}. This class implements the existing behavior and the
+ * {@link ExtLogManager} implements the extension behavior. The {@link #scr()}
+ * method makes this distinction based on the configuration.
+ */
+public class ScrLogManager extends LogManager {
+
+	private final Bundle			bundle;
+	private final ScrConfiguration	config;
+
+	/**
+	 * Get a new log manager based on the configuration.
+	 * 
+	 * @param context
+	 *                    the bundle context of the SCR bundle
+	 * @param config
+	 *                    the SCR configuration
+	 * @return a proper ScrLogManager
+	 */
+
+	public static ScrLogger scr(BundleContext context, ScrConfiguration config) {
+        ScrLogManager manager;
+		if (config.isLogExtension())
+            manager = new ExtLogManager(context, config);
+		else
+            manager = new ScrLogManager(context, config);
+        manager.open();
+        return manager.scr();
+	}
+
+	ScrLogManager(BundleContext context, ScrConfiguration config) {
+		super(context);
+		this.config = config;
+		this.bundle = context.getBundle();
+	}
+
+	/**
+	 * This logger is used for the main code of SCR. This will use the SCR
+	 * bundle & the {@link Logger#ROOT_LOGGER_NAME}
+	 * 
+	 * @return an Scr Logger.
+	 */
+	public ScrLogger scr() {
+		ScrLoggerFacade scrl = super.getLogger(bundle, Logger.ROOT_LOGGER_NAME, ScrLoggerFacade.class);
+		scrl.setPrefix(getBundleIdentifier(bundle));
+		return scrl;
+	}
+
+	/**
+	 * This logger is used for the logging on a per bundle basis. This will use
+	 * the target bundle & the {@link Logger#ROOT_LOGGER_NAME}
+	 * 
+	 * @param bundle
+	 *                   the target bundle
+	 * @return a logger suitable to log bundle entries
+	 */
+	public BundleLogger bundle(Bundle bundle) {
+		ScrLoggerFacade logger = getLogger(bundle, Logger.ROOT_LOGGER_NAME, ScrLoggerFacade.class);
+		logger.setPrefix(getBundleIdentifier(bundle));
+		return logger;
+	}
+
+	/**
+	 * This logger is used for the logging on a per bundle basis. This will use
+	 * the target bundle & the implementation class as logger name.
+	 * 
+	 * @param bundle
+	 *                   the target bundle
+	 * @return a logger suitable to log bundle entries
+	 */
+	public ComponentLogger component(Bundle bundle, String implementationClass, String name) {
+
+		// assert bundle != null;
+		// assert bundle.getSymbolicName() != null : "scr requires recent
+		// bundles";
+		// assert implementationClass != null;
+		// assert name != null;
+
+		ScrLoggerFacade facade = getLogger(bundle, implementationClass, ScrLoggerFacade.class);
+		facade.setComponentId(-1);
+		return (ComponentLogger) facade;
+	}
+
+	class ScrLoggerFacade extends LoggerFacade implements InternalLogger, ScrLogger, BundleLogger, ComponentLogger {
+		ScrLoggerFacade(LogDomain logDomain, String name) {
+			super(logDomain, name);
+		}
+
+		@Override
+		public void setComponentId(long id) {
+			setPrefix(componentPrefix(this, id));
+		}
+
+		public boolean isLogEnabled(Level level) {
+
+			// assert !closed.get();
+
+			Object checkLogger = getLogger();
+			if (checkLogger != null) {
+			    Logger logger = (Logger) checkLogger;
+				switch (level) {
+				case AUDIT:
+					return true;
+				case ERROR:
+					return logger.isErrorEnabled();
+				case WARN:
+					return logger.isWarnEnabled();
+				case INFO:
+					return logger.isInfoEnabled();
+				case TRACE:
+					return logger.isTraceEnabled();
+				case DEBUG:
+				default:
+					return logger.isDebugEnabled();
+				}
+			} else {
+				return getLogLevel().implies(level);
+			}
+		}
+
+		@Override
+		public void log(Level level, String format, Throwable ex, Object... arguments) {
+			if (isLogEnabled(level))
+				log0(level, format(format, arguments), ex);
+		}
+
+		@Override
+		public void log(Level level, String message, Throwable ex) {
+			if (isLogEnabled(level))
+				log0(level, message, ex);
+		}
+
+		void log0(Level level, String message, Throwable ex) {
+			if (prefix != null && prefix.length() > 0) {
+				message = prefix.concat(" ").concat(message);
+			}
+			Object checkLogger = getLogger();
+			if (checkLogger != null) {
+			    Logger logger = (Logger) checkLogger;
+				if (ex == null) {
+					switch (level) {
+					case AUDIT:
+						logger.audit(message);
+						break;
+					case ERROR:
+						logger.error(message);
+						break;
+					case WARN:
+						logger.warn(message);
+						break;
+					case INFO:
+						logger.info(message);
+						break;
+					case TRACE:
+						logger.trace(message);
+						break;
+					case DEBUG:
+					default:
+						logger.debug(message);
+					}
+				} else {
+					switch (level) {
+					case AUDIT:
+						logger.audit(message, ex);
+						break;
+					case ERROR:
+						logger.error(message, ex);
+						break;
+					case WARN:
+						logger.warn(message, ex);
+						break;
+					case INFO:
+						logger.info(message, ex);
+						break;
+					case TRACE:
+						logger.trace(message, ex);
+						break;
+					case DEBUG:
+					default:
+						logger.debug(message, ex);
+					}
+				}
+			} else {
+				StringWriter buf = new StringWriter();
+				String l = String.format("%-5s", level);
+				buf.append(l).append(" : ").append(message);
+				if (ex != null) {
+					try (PrintWriter pw = new PrintWriter(buf)) {
+						pw.println();
+						ex.printStackTrace(pw);
+					}
+				}
+
+				@SuppressWarnings("resource")
+				PrintStream out = level.err() ? System.err : System.out;
+				out.println(buf);
+			}
+		}
+
+		void setPrefix(String prefix) {
+			this.prefix = prefix;
+		}
+
+		@Override
+		public ComponentLogger component(Bundle bundle, String implementationClassName, String name) {
+			// assert !closed.get();
+			return ScrLogManager.this.component(bundle, implementationClassName, name);
+		}
+
+		@Override
+		public BundleLogger bundle(Bundle bundle) {
+			// assert !closed.get();
+			return ScrLogManager.this.bundle(bundle);
+		}
+
+		@Override
+		public void close() {
+			// assert !closed.get();
+			ScrLogManager.this.close();
+		}
+	};
+
+	LoggerFacade createLoggerFacade(LogDomain logDomain, String name) {
+		// assert !closed.get();
+		return new ScrLoggerFacade(logDomain, name);
+	}
+
+	Level getLogLevel() {
+		return config.getLogLevel();
+	}
+
+	String getBundleIdentifier(final Bundle bundle) {
+		final StringBuilder sb = new StringBuilder("bundle ");
+
+		if (bundle.getSymbolicName() != null) {
+			sb.append(bundle.getSymbolicName());
+			sb.append(':');
+			sb.append(bundle.getVersion());
+			sb.append(" (");
+			sb.append(bundle.getBundleId());
+			sb.append(")");
+		} else {
+			sb.append(bundle.getBundleId());
+		}
+
+		return sb.toString();
+	}
+
+	String componentPrefix(ScrLoggerFacade slf, long id) {
+		if (id >= 0) {
+			return getBundleIdentifier(slf.getBundle()) + "[" + slf.getName() + "(" + id + ")] :";
+		} else {
+			return getBundleIdentifier(slf.getBundle()) + "[" + slf.getName() + "] :";
+		}
+	}
+
+	String format(final String pattern, final Object... arguments) {
+		if (arguments == null || arguments.length == 0) {
+			return pattern;
+		} else {
+			for (int i = 0; i < arguments.length; i++) {
+				if (arguments[i] instanceof Bundle) {
+					arguments[i] = getBundleIdentifier((Bundle) arguments[i]);
+				}
+			}
+			return MessageFormat.format(pattern, arguments);
+		}
+	}
+
+}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogger.java
index 9ef2562..cbf8a42 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogger.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogger.java
@@ -16,43 +16,23 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
 package org.apache.felix.scr.impl.logger;
 
-import org.apache.felix.scr.impl.manager.ScrConfiguration;
-import org.osgi.framework.BundleContext;
-import org.osgi.service.log.LoggerFactory;
-import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.framework.Bundle;
 
 /**
- * This is the "global" logger used by the implementation for all logging
- * not directly related to an extended bundle (and its components)
+ * Logger used by SCR main code
  */
-public class ScrLogger extends LogServiceEnabledLogger
-{
-    private final ScrConfiguration config;
-    public ScrLogger(final ScrConfiguration config, BundleContext context)
-    {
-        super(context.getBundle(), getTracker(context));
-        this.config = config;
-    }
+public interface ScrLogger extends InternalLogger {
 
-    @Override
-    InternalLogger getDefaultLogger()
-    {
-        return new StdOutLogger(config);
-    }
+	/**
+	 * Create a bundle logger
+	 */
+	BundleLogger bundle(Bundle bundle);
 
-    private static ServiceTracker<LoggerFactory, LoggerFactory> getTracker(
-        BundleContext context)
-    {
-        ServiceTracker<LoggerFactory, LoggerFactory> tracker = new ServiceTracker<>(
-            context, "org.osgi.service.log.LoggerFactory", null);
-        tracker.open();
-        return tracker;
-    }
-
-    public void closeTracker()
-    {
-        getLoggerFactoryTracker().close();
-    }
-}
\ No newline at end of file
+	/**
+	 * Close the log manager
+	 */
+	void close();
+}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/StdOutLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/StdOutLogger.java
deleted file mode 100644
index 627b812..0000000
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/StdOutLogger.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.scr.impl.logger;
-
-import java.io.PrintStream;
-
-import org.apache.felix.scr.impl.manager.ScrConfiguration;
-
-/**
- * This logger logs to std out / err
- */
-class StdOutLogger implements InternalLogger
-{
-
-    private final ScrConfiguration config;
-
-    public StdOutLogger(ScrConfiguration config)
-    {
-        this.config = config;
-    }
-
-    @Override
-    public void log(final Level level, final String message, final Throwable ex)
-    {
-        if ( isLogEnabled(level) )
-        {
-            // output depending on level
-            final PrintStream out = (level == Level.ERROR) ? System.err : System.out;
-
-            // level as a string
-            final StringBuilder buf = new StringBuilder();
-            switch (level)
-            {
-                case AUDIT:
-                    buf.append("AUDIT");
-                    break;
-                case TRACE:
-                    buf.append("TRACE");
-                    break;
-                case DEBUG:
-                    buf.append( "DEBUG: " );
-                    break;
-                case INFO:
-                    buf.append( "INFO : " );
-                    break;
-                case WARN:
-                    buf.append( "WARN : " );
-                    break;
-                case ERROR:
-                    buf.append( "ERROR: " );
-                    break;
-                default:
-                    buf.append( "UNK  : " );
-                    break;
-            }
-
-            buf.append(message);
-
-            final String msg = buf.toString();
-
-            if ( ex == null )
-            {
-                out.println(msg);
-            }
-            else
-            {
-                // keep the message and the stacktrace together
-                synchronized ( out )
-                {
-                    out.println( msg );
-                    ex.printStackTrace( out );
-                }
-            }
-        }
-    }
-
-    @Override
-    public boolean isLogEnabled(Level level)
-    {
-        return config.getLogLevel().implies(level);
-
-    }
-}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/manager/AbstractComponentManager.java b/scr/src/main/java/org/apache/felix/scr/impl/manager/AbstractComponentManager.java
index fda0be7..8f51ad9 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/manager/AbstractComponentManager.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/manager/AbstractComponentManager.java
@@ -1467,8 +1467,6 @@
 
     }
 
-    abstract boolean hasInstance();
-
     public void setServiceProperties(MethodResult methodResult, Integer trackingCount)
     {
         if (methodResult.hasResult())
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/manager/ComponentFactoryImpl.java b/scr/src/main/java/org/apache/felix/scr/impl/manager/ComponentFactoryImpl.java
index ba279ef..6e8a85c 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/manager/ComponentFactoryImpl.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/manager/ComponentFactoryImpl.java
@@ -311,12 +311,6 @@
     }
 
     @Override
-    boolean hasInstance()
-    {
-        return false;
-    }
-
-    @Override
     protected boolean collectDependencies(ComponentContextImpl<S> componentContext)
     {
         return true;
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java b/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java
index 065e515..8d0b650 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java
@@ -22,6 +22,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.SortedMap;
@@ -805,7 +806,11 @@
     private class SingleDynamicCustomizer extends AbstractCustomizer
     {
 
-        private RefPair<S, T> refPair;
+        private RefPair<S, T> currentRefPair;
+        private RefPair<S, T> bindingRefPair;
+        // generally should be very small
+        private Collection<RefPair<S, T>> queuedRefPairs = new ArrayList<>();
+        private Thread bindingThread;
         private int trackingCount;
 
         @Override
@@ -820,7 +825,7 @@
         }
 
         @Override
-        public void addedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
+        public void addedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair , int trackingCount,
             int serviceCount, ExtendedServiceEvent event)
         {
             m_componentManager.getLogger().log(Level.DEBUG,
@@ -831,35 +836,12 @@
             {
                 if (isActive())
                 {
-                    boolean invokeBind = false;
+
                     ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker = getTracker();
                     Object monitor = tracker == null ? null : tracker.tracked();
                     if (monitor != null)
                     {
-                        synchronized (monitor)
-                        {
-                            invokeBind = this.refPair == null || (!isReluctant()
-                                && refPair.getRef().compareTo(this.refPair.getRef()) > 0);
-                        }
-                    }
-                    if (invokeBind)
-                    {
-                        m_componentManager.invokeBindMethod(DependencyManager.this, refPair, trackingCount);
-                        if (!refPair.isFailed())
-                        {
-                            if (this.refPair != null)
-                            {
-                                m_componentManager.invokeUnbindMethod(DependencyManager.this, this.refPair,
-                                    trackingCount);
-                                closeRefPair();
-                            }
-                        }
-                        else if (cardinalitySatisfied(0))
-                        {
-                            m_componentManager.registerMissingDependency(DependencyManager.this, serviceReference,
-                                trackingCount);
-                        }
-                        this.refPair = refPair;
+                        tryInvokeBind(tracker, monitor, refPair, trackingCount);
                     }
                 }
                 else if (isTrackerOpened() && cardinalityJustSatisfied(serviceCount))
@@ -879,6 +861,165 @@
             }
         }
 
+        private void tryInvokeBind(
+            ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker,
+            Object monitor, RefPair<S, T> next, int trackingCount)
+        {
+            boolean checkQueue = false;
+            do
+            {
+                try
+                {
+                    while ((next = tryInvokeBind0(tracker, monitor, next,
+                        trackingCount)) != null)
+                    {
+                    }
+                }
+                finally
+                {
+                    // be sure to clean up the bindingThread state
+                    synchronized (monitor)
+                    {
+                        if (bindingThread != null
+                            && bindingThread.equals(Thread.currentThread()))
+                        {
+                            // check that another thread didn't give us more work to do since
+                            // the time we gave up the lock in tryInvokeBind0
+                            if (queuedRefPairs.isEmpty())
+                            {
+                                // working thread is done, make sure state is cleared
+                                bindingRefPair = null;
+                                bindingThread = null;
+                            }
+                            else
+                            {
+                                next = getBestFromQueue();
+                                // NOTE - getBestFromQueue cannot return null at this point
+                                // because queuedRefPairs is not empty; always loop
+                                // around to tryInvokeBind again in this case
+                                checkQueue = true;
+                            }
+                        }
+                    }
+                }
+            }
+            while (checkQueue);
+        }
+
+        private RefPair<S, T> getBestFromQueue()
+        {
+            RefPair<S, T> currentBest = null;
+            for (RefPair<S, T> betterCandidate : queuedRefPairs)
+            {
+                if (currentBest == null
+                    || betterCandidate.getRef().compareTo(currentBest.getRef()) > 0)
+                {
+                    currentBest = betterCandidate;
+                }
+            }
+            queuedRefPairs.clear();
+            return currentBest;
+        }
+
+        private RefPair<S, T> tryInvokeBind0(
+            ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker,
+            Object monitor, RefPair<S, T> refPair , int trackingCount)
+        {
+            boolean invokeBind = false;
+            RefPair<S, T> current;
+            synchronized (monitor)
+            {
+                current = this.currentRefPair;
+                invokeBind = current == null || current.isDeleted() || (!isReluctant()
+                    && refPair.getRef().compareTo(current.getRef()) > 0);
+                if (invokeBind)
+                {
+                    if (bindingThread != null && !bindingThread.equals(Thread.currentThread()))
+                    {
+                        // this thread "lost" see if it is better than the current bindingRefPair
+                        if (refPair.getRef().compareTo(bindingRefPair.getRef()) > 0)
+                        {
+                            // got a better option, add it to the queue
+                            queuedRefPairs.add(refPair);
+                        }
+                        return null;
+                    }
+                    // this thread "won" it gets to do the work
+                    bindingRefPair = refPair;
+                    bindingThread = Thread.currentThread();
+                }
+            }
+
+            if (invokeBind)
+            {
+                boolean invokedUnbind = false;
+                m_componentManager.invokeBindMethod(DependencyManager.this,
+                    bindingRefPair, trackingCount);
+                if (!bindingRefPair.isFailed())
+                {
+                    if (current != null)
+                    {
+                        m_componentManager.invokeUnbindMethod(DependencyManager.this,
+                            current, trackingCount);
+                        invokedUnbind = true;
+                        ungetService(current);
+                    }
+                }
+                else if (cardinalitySatisfied(0))
+                {
+                    m_componentManager.registerMissingDependency(DependencyManager.this,
+                        bindingRefPair.getRef(), trackingCount);
+                }
+                RefPair<S, T> next = null;
+                synchronized (monitor)
+                {
+                    if (!queuedRefPairs.isEmpty())
+                    {
+                        // One of more threads offered better options
+                        // Find the best option
+                        next = getBestFromQueue();
+                        this.bindingRefPair = next;
+                        if (invokedUnbind)
+                        {
+                            // unbind was invoked clear the current RefPair
+                            this.currentRefPair = null;
+                        }
+                    }
+                    else
+                    {
+                        if (bindingRefPair.isDeleted())
+                        {
+                            // what we just bound got unregistered
+                            // look for next best service
+                            Iterator<RefPair<S, T>> iNext = getTracker().getTracked(null,
+                                new AtomicInteger()).values().iterator();
+                            next = iNext.hasNext() ? iNext.next() : null;
+                            this.bindingRefPair = next;
+                            if (invokedUnbind)
+                            {
+                                // unbind was invoked clear the current RefPair
+                                this.currentRefPair = null;
+                            }
+                        }
+                        else
+                        {
+                            // all done; set the current to the ref bound
+                            this.currentRefPair = bindingRefPair;
+                            this.bindingRefPair = null;
+                            this.bindingThread = null;
+                            return null;
+                        }
+                    }
+                }
+                // unbind the last refPair that we bound to try again with the next candidate
+                m_componentManager.invokeUnbindMethod(DependencyManager.this, refPair,
+                    trackingCount);
+                ungetService(refPair);
+                return next;
+            }
+            return null;
+        }
+
         @Override
         public void modifiedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
             ExtendedServiceEvent event)
@@ -893,7 +1034,7 @@
             {
                 synchronized (monitor)
                 {
-                    invokeUpdated = isActive() && refPair == this.refPair;
+                    invokeUpdated = isActive() && refPair == this.currentRefPair;
                 }
             }
             if (invokeUpdated)
@@ -914,7 +1055,6 @@
             m_componentManager.getLogger().log(Level.DEBUG,
                 "dm {0} tracking {1} SingleDynamic removed {2} (enter)",
                     null, getName(), trackingCount, serviceReference );
-            refPair.markDeleted();
             boolean deactivate = false;
             boolean untracked = true;
             RefPair<S, T> oldRefPair = null;
@@ -925,29 +1065,28 @@
             {
                 synchronized (monitor)
                 {
-                    if (refPair == this.refPair && isActive())
+                    refPair.markDeleted();
+                    if (refPair == this.currentRefPair && isActive())
                     {
                         if (!getTracker().isEmpty())
                         {
-                            AtomicInteger trackingCount2 = new AtomicInteger();
                             SortedMap<ServiceReference<T>, RefPair<S, T>> tracked = getTracker().getTracked(
-                                true, //TODO true here looks odd.
-                                trackingCount2);
+                                null,
+                                new AtomicInteger());
                             nextRefPair = tracked.values().iterator().next();
                         }
 
                         //n.b. we cannot use cardinalitySatisfied( serviceCount ) here as the call may come from an old tracker during target change.
                         if (isEffectivelyOptional() || nextRefPair != null)
                         {
-                            oldRefPair = this.refPair;
-                            this.refPair = null;
+                            oldRefPair = this.currentRefPair;
                         }
                         else
                         {
                             deactivate = true; //required and no replacement service, deactivate
                         }
                     }
-                    else if (!cardinalitySatisfied() && this.refPair == null)
+                    else if (!cardinalitySatisfied() && this.currentRefPair == null)
                     {
                         deactivate = true;
                     }
@@ -955,31 +1094,47 @@
             }
             if (nextRefPair != null)
             {
-                m_componentManager.invokeBindMethod(DependencyManager.this, nextRefPair, trackingCount);
+                tryInvokeBind(tracker, monitor, nextRefPair, trackingCount);
             }
 
             if (oldRefPair != null)
             {
                 this.trackingCount = trackingCount;
-                m_componentManager.invokeUnbindMethod(DependencyManager.this, oldRefPair, trackingCount);
-                synchronized (getTracker().tracked())
+                synchronized (monitor)
                 {
-                    this.refPair = nextRefPair;
+                    if (oldRefPair != this.currentRefPair)
+                    {
+                        oldRefPair = null;
+                        // make sure the tryInvokeBind found something to bind
+                        if (this.currentRefPair == null)
+                        {
+                            // must deactivate if we didn't find anything to bind
+                            deactivate = !isEffectivelyOptional();
+                        }
+                    }
+                    else
+                    {
+                        this.currentRefPair = null;
+                    }
+                }
+                if (oldRefPair != null)
+                {
+                    m_componentManager.invokeUnbindMethod(DependencyManager.this,
+                        oldRefPair, trackingCount);
+                    ungetService(oldRefPair);
                 }
                 tracked(trackingCount);
                 untracked = false;
             }
-            else if (deactivate)
+            
+            if (deactivate)
             {
                 this.trackingCount = trackingCount;
                 tracked(trackingCount);
                 untracked = false;
                 deactivateComponentManager();
             }
-            if (oldRefPair != null)
-            {
-                ungetService(oldRefPair);
-            }
+
             if (untracked) // not ours
             {
                 this.trackingCount = trackingCount;
@@ -1009,7 +1164,7 @@
                         if (!tracked.isEmpty())
                         {
                             refPair = tracked.values().iterator().next();
-                            this.refPair = refPair;
+                            this.currentRefPair = refPair;
                         }
                     }
                 }
@@ -1035,11 +1190,11 @@
 
         private void closeRefPair()
         {
-            if (refPair != null)
+            if (currentRefPair != null)
             {
-                ungetService(refPair);
+                ungetService(currentRefPair);
             }
-            refPair = null;
+            currentRefPair = null;
         }
 
         @Override
@@ -1052,7 +1207,9 @@
                 synchronized (monitor)
                 {
                     trackingCount.set(this.trackingCount);
-                    return refPair == null ? Collections.<RefPair<S, T>> emptyList() : Collections.singleton(refPair);
+                    return currentRefPair == null
+                        ? Collections.<RefPair<S, T>> emptyList()
+                        : Collections.singleton(currentRefPair);
                 }
             }
             else
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/manager/ScrConfiguration.java b/scr/src/main/java/org/apache/felix/scr/impl/manager/ScrConfiguration.java
index 77881e0..65a5254 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/manager/ScrConfiguration.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/manager/ScrConfiguration.java
@@ -64,12 +64,17 @@
     long DEFAULT_STOP_TIMEOUT_MILLISECONDS = 60000;
 
     String PROP_LOGLEVEL = "ds.loglevel";
+    /**
+     * See {@link #isLogExtension()}
+     */
+    String PROP_LOG_EXTENSION = "ds.log.extension";
 
     String PROP_GLOBAL_EXTENDER="ds.global.extender";
 
     String PROP_SERVICE_CHANGECOUNT_TIMEOUT = "ds.service.changecount.timeout";
 
     String PROP_CACHE_METADATA = "ds.cache.metadata";
+    
 
     /**
      * Returns the current log level.
@@ -102,4 +107,11 @@
 
     boolean cacheMetadata();
 
+
+    /**
+     * If true, use a logging extension. The extension can be incompatible with the OSGi specification.
+     * @return true if extension allowed
+     */
+	boolean isLogExtension();
+
 }
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/manager/ServiceFactoryComponentManager.java b/scr/src/main/java/org/apache/felix/scr/impl/manager/ServiceFactoryComponentManager.java
index a3ba9a1..1520ff9 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/manager/ServiceFactoryComponentManager.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/manager/ServiceFactoryComponentManager.java
@@ -86,7 +86,10 @@
                 "Unset implementation object for component in deleteComponent for reason {0}",
                     null, REASONS[ reason ] );
         }
-        serviceContexts.clear();
+        synchronized (serviceContexts)
+        {
+            serviceContexts.clear();
+        }
         clearServiceProperties();
     }
 
@@ -170,15 +173,19 @@
         {
             serviceContext = serviceContexts.get( service );
         }
-        disposeImplementationObject( serviceContext, ComponentConstants.DEACTIVATION_REASON_DISPOSED );
-        synchronized ( serviceContexts )
-        {
-            serviceContexts.remove( service );
-            // if this was the last use of the component, go back to REGISTERED state
-            State previousState;
-            if ( serviceContexts.isEmpty() && (previousState = getState()) == State.active )
+        if (serviceContext != null) {
+            disposeImplementationObject( serviceContext, ComponentConstants.DEACTIVATION_REASON_DISPOSED );
+            synchronized ( serviceContexts )
             {
-                setState(previousState, State.satisfied);
+                if (serviceContexts.remove( service ) != null) {
+                    // if this was the last use of the component, go back to REGISTERED state
+                    State previousState;
+                    if (serviceContexts.isEmpty()
+                        && (previousState = getState()) == State.active)
+                    {
+                        setState(previousState, State.satisfied);
+                    }
+                }
             }
         }
     }
@@ -240,12 +247,6 @@
         return result;
     }
 
-    @Override
-    boolean hasInstance()
-    {
-        return !serviceContexts.isEmpty();
-    }
-
     //---------- Component interface
 
     @Override
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/manager/SingleComponentManager.java b/scr/src/main/java/org/apache/felix/scr/impl/manager/SingleComponentManager.java
index c86a6c4..7a4ea36 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/manager/SingleComponentManager.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/manager/SingleComponentManager.java
@@ -434,12 +434,6 @@
     }
 
     @Override
-    boolean hasInstance()
-    {
-        return m_componentContext != null;
-    }
-
-    @Override
     <T> void invokeBindMethod( DependencyManager<S, T> dependencyManager, RefPair<S, T> refPair, int trackingCount )
     {
         ComponentContextImpl<S> componentContext = m_componentContext;
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/logger/LogService.java b/scr/src/test/java/org/apache/felix/scr/impl/logger/LogService.java
new file mode 100644
index 0000000..17fb479
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/impl/logger/LogService.java
@@ -0,0 +1,347 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.log.LogLevel;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.LoggerConsumer;
+import org.osgi.service.log.LoggerFactory;
+
+import aQute.lib.collections.MultiMap;
+
+public class LogService implements ServiceFactory<LoggerFactory> {
+	public BundleContext						context;
+	public final List<LogEntry>					entries			= new ArrayList<>();
+	public final Map<String, LogLevel>			levels			= new HashMap<>();
+	public LogLevel								defaultLogLevel	= LogLevel.DEBUG;
+	public List<LoggerFactory>					factories		= new ArrayList<>();
+	public MultiMap<Bundle, Logger>				loggers			= new MultiMap<>();
+
+	public ServiceRegistration<LoggerFactory>	registration;
+	private int ranking;
+
+	public static class LogEntry {
+
+		@Override
+		public String toString() {
+			return "LogEntry [level=" + level + ", format=" + format + ", args=" + Arrays.toString(args) + ", bundle="
+					+ bundle + ", loggername=" + loggername + "]";
+		}
+
+		public LogLevel	level;
+		public String	format;
+		public Object[]	args;
+		public Bundle	bundle;
+		public String	loggername;
+
+		public LogEntry(Bundle bundle, String loggername, LogLevel level, String format, Object[] args) {
+			this.bundle = bundle;
+			this.loggername = loggername;
+			this.level = level;
+			this.format = format;
+			this.args = args;
+		}
+
+	}
+
+	public LogService register() {
+		Hashtable<String,Object> properties = new Hashtable<>();
+		properties.put( Constants.SERVICE_RANKING, ranking);
+		registration = context.registerService(LoggerFactory.class, this, properties);
+		return this;
+	}
+
+	public void unregister() {
+		assert registration != null;
+		registration.unregister();
+		registration = null;
+	}
+
+	public LogService(BundleContext context) {
+		this.context = context;
+	}
+
+	@Override
+	public LoggerFactory getService(final Bundle bundle, ServiceRegistration<LoggerFactory> registration) {
+		LoggerFactory factory = new LoggerFactory() {
+
+			@Override
+			public Logger getLogger(String name) {
+				return getLogger(bundle, name, Logger.class);
+			}
+
+			@Override
+			public Logger getLogger(Class<?> clazz) {
+				return getLogger(bundle, clazz.getName(), Logger.class);
+			}
+
+			@Override
+			public <L extends Logger> L getLogger(String name, Class<L> loggerType) {
+				return getLogger(bundle, name, loggerType);
+			}
+
+			@Override
+			public <L extends Logger> L getLogger(Class<?> clazz, Class<L> loggerType) {
+				return getLogger(bundle, clazz.getName(), loggerType);
+			}
+
+			@Override
+			public <L extends Logger> L getLogger(final Bundle bundle, final String name, Class<L> loggerType) {
+				Logger logger = new Logger() {
+
+					@Override
+					public String getName() {
+						return name;
+					}
+
+					@Override
+					public boolean isTraceEnabled() {
+						return getLevel().implies(LogLevel.TRACE);
+					}
+
+					@Override
+					public void trace(String message) {
+						log(LogLevel.TRACE, message);
+					}
+
+					@Override
+					public void trace(String format, Object arg) {
+						log(LogLevel.TRACE, format, arg);
+					}
+
+					@Override
+					public void trace(String format, Object arg1, Object arg2) {
+						log(LogLevel.TRACE, format, arg1, arg2);
+					}
+
+					@Override
+					public void trace(String format, Object... arguments) {
+						log(LogLevel.TRACE, format, arguments);
+					}
+
+					@Override
+					public <E extends Exception> void trace(LoggerConsumer<E> consumer) throws E {
+						consumer.accept(this);
+					}
+
+					@Override
+					public boolean isDebugEnabled() {
+						return getLevel().implies(LogLevel.DEBUG);
+					}
+
+					@Override
+					public void debug(String message) {
+						log(LogLevel.DEBUG, message);
+					}
+
+					@Override
+					public void debug(String format, Object arg) {
+						log(LogLevel.DEBUG, format, arg);
+					}
+
+					@Override
+					public void debug(String format, Object arg1, Object arg2) {
+						log(LogLevel.DEBUG, format, arg1, arg2);
+					}
+
+					@Override
+					public void debug(String format, Object... arguments) {
+						log(LogLevel.DEBUG, format, arguments);
+					}
+
+					@Override
+					public <E extends Exception> void debug(LoggerConsumer<E> consumer) throws E {
+						consumer.accept(this);
+					}
+
+					@Override
+					public boolean isInfoEnabled() {
+						return getLevel().implies(LogLevel.INFO);
+					}
+
+					@Override
+					public void info(String message) {
+						log(LogLevel.INFO, message);
+					}
+
+					@Override
+					public void info(String format, Object arg) {
+						log(LogLevel.INFO, format, arg);
+					}
+
+					@Override
+					public void info(String format, Object arg1, Object arg2) {
+						log(LogLevel.INFO, format, arg1, arg2);
+					}
+
+					@Override
+					public void info(String format, Object... arguments) {
+						log(LogLevel.INFO, format, arguments);
+					}
+
+					@Override
+					public <E extends Exception> void info(LoggerConsumer<E> consumer) throws E {
+						consumer.accept(this);
+					}
+
+					@Override
+					public boolean isWarnEnabled() {
+						return getLevel().implies(LogLevel.WARN);
+					}
+
+					@Override
+					public void warn(String message) {
+						log(LogLevel.WARN, message);
+					}
+
+					@Override
+					public void warn(String format, Object arg) {
+						log(LogLevel.WARN, format, arg);
+					}
+
+					@Override
+					public void warn(String format, Object arg1, Object arg2) {
+						log(LogLevel.WARN, format, arg1, arg2);
+					}
+
+					@Override
+					public void warn(String format, Object... arguments) {
+						log(LogLevel.WARN, format, arguments);
+					}
+
+					@Override
+					public <E extends Exception> void warn(LoggerConsumer<E> consumer) throws E {
+						consumer.accept(this);
+					}
+
+					@Override
+					public boolean isErrorEnabled() {
+						return getLevel().implies(LogLevel.ERROR);
+					}
+
+					@Override
+					public void error(String message) {
+						log(LogLevel.ERROR, message);
+					}
+
+					@Override
+					public void error(String format, Object arg) {
+						log(LogLevel.ERROR, format, arg);
+					}
+
+					@Override
+					public void error(String format, Object arg1, Object arg2) {
+						log(LogLevel.ERROR, format, arg1, arg2);
+					}
+
+					@Override
+					public void error(String format, Object... arguments) {
+						log(LogLevel.ERROR, format, arguments);
+					}
+
+					@Override
+					public <E extends Exception> void error(LoggerConsumer<E> consumer) throws E {
+						consumer.accept(this);
+					}
+
+					@Override
+					public void audit(String message) {
+						log(LogLevel.AUDIT, message);
+					}
+
+					@Override
+					public void audit(String format, Object arg) {
+						log(LogLevel.AUDIT, format, arg);
+					}
+
+					@Override
+					public void audit(String format, Object arg1, Object arg2) {
+						log(LogLevel.AUDIT, format, arg1, arg2);
+					}
+
+					@Override
+					public void audit(String format, Object... arguments) {
+						log(LogLevel.AUDIT, format, arguments);
+					}
+
+					public void log(LogLevel level, String format, Object... args) {
+						if (getLevel().implies(level)) {
+							entries.add(new LogEntry(bundle, name, level, format, args));
+						}
+					}
+
+					public LogLevel getLevel() {
+						String ancestor = name;
+						while (true) {
+							LogLevel logLevel = levels.get(ancestor);
+							if (logLevel != null) {
+								return logLevel;
+							}
+
+							int n = ancestor.lastIndexOf('.');
+							if (n < 0) {
+								LogLevel logLevel2 = levels.get(Logger.ROOT_LOGGER_NAME);
+								if ( logLevel2 != null)
+									return logLevel2;
+								
+								return defaultLogLevel;
+							}
+
+							ancestor = ancestor.substring(0, n);
+						}
+					}
+
+				};
+				loggers.add(bundle, logger);
+				return loggerType.cast(logger);
+			}
+		};
+		factories.add(factory);
+		return factory;
+	}
+
+	@Override
+	public void ungetService(Bundle bundle, ServiceRegistration<LoggerFactory> registration, LoggerFactory service) {
+		factories.add(service);
+	}
+
+	public void clear() {
+		loggers.clear();
+		entries.clear();
+		factories.clear();
+		levels.clear();
+	}
+
+	public LogService ranking(int ranking) {
+		this.ranking = ranking;
+		return this;
+	}
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/logger/LoggerTest.java b/scr/src/test/java/org/apache/felix/scr/impl/logger/LoggerTest.java
new file mode 100644
index 0000000..e0714da
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/impl/logger/LoggerTest.java
@@ -0,0 +1,504 @@
+/*
+ * 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.scr.impl.logger;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+import org.apache.felix.scr.impl.logger.InternalLogger.Level;
+import org.apache.felix.scr.impl.logger.LogManager.LoggerFacade;
+import org.apache.felix.scr.impl.logger.LogService.LogEntry;
+import org.apache.felix.scr.impl.manager.ScrConfiguration;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.launch.Framework;
+import org.osgi.service.log.LogLevel;
+import org.osgi.service.log.Logger;
+
+import aQute.bnd.osgi.Builder;
+import aQute.bnd.osgi.Jar;
+import aQute.bnd.osgi.JarResource;
+import aQute.lib.io.IO;
+
+public class LoggerTest {
+
+	private Framework	framework;
+	private Bundle		scr;
+	private Bundle		component;
+	private Bundle		log;
+	private File		tmp	= IO.getFile("target/tmp/loggertest");
+
+	@Before
+	public void setup() throws Exception {
+		Map<String, String> configuration = new HashMap<>();
+		configuration.put(Constants.FRAMEWORK_STORAGE, tmp.getAbsolutePath());
+		configuration.put(Constants.FRAMEWORK_STORAGE_CLEAN, Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT);
+		framework = ServiceLoader.load(org.osgi.framework.launch.FrameworkFactory.class).iterator().next()
+				.newFramework(configuration);
+		framework.init();
+		framework.start();
+
+		scr = framework.getBundleContext().installBundle("scr", makeBundle("scr").openInputStream());
+		component = framework.getBundleContext().installBundle("component",
+				makeBundle("component").openInputStream());
+		log = framework.getBundleContext().installBundle("component", makeBundle("log").openInputStream());
+		scr.start();
+		component.start();
+		log.start();
+	}
+
+	@After
+	public void after() throws Exception {
+		framework.stop();
+		framework.waitForStop(100000);
+	}
+
+	static class Buf extends FilterOutputStream {
+		StringWriter		buffer	= new StringWriter();
+		final PrintStream	stream;
+
+		public Buf(PrintStream out) {
+			super(out);
+			this.stream = out;
+		}
+
+		@Override
+		public void write(int b) throws IOException {
+			stream.write(b);
+			buffer.write(b);
+		}
+
+		PrintStream out() {
+			return new PrintStream(this);
+		}
+
+		public void reset() {
+			buffer = new StringWriter();
+		}
+	}
+
+	@Test
+	public void formatTest() {
+		LogService l = new LogService(log.getBundleContext());
+		l.register();
+		ScrConfiguration config = mock(ScrConfiguration.class);
+		when(config.getLogLevel()).thenReturn(Level.DEBUG);
+		ExtLogManager elm = new ExtLogManager(scr.getBundleContext(), config);
+        elm.open();
+
+		ComponentLogger clog = elm.component(component, "i.c", "c");
+
+		clog.log(Level.ERROR, "error {0} {1} {2}", null, "a", 1, scr);
+		LogEntry le = l.entries.get(0);
+		assertThat(le.format).isEqualTo("[c] error a 1 bundle scr:0.0.0 (1)");
+	}
+
+	@Test
+	public void testStandardOutput() throws IOException {
+		ScrConfiguration config = mock(ScrConfiguration.class);
+		when(config.getLogLevel()).thenReturn(Level.DEBUG);
+		ExtLogManager elm = new ExtLogManager(scr.getBundleContext(), config);
+        elm.open();
+		try (Buf out = new Buf(System.out);
+				Buf err = new Buf(System.err);) {
+			try {
+				System.setOut(out.out());
+				System.setErr(err.out());
+				ScrLogger scr = elm.scr();
+				{
+					scr.log(Level.AUDIT, "audit ", null);
+					scr.log(Level.AUDIT, "audit", new Exception("FOOBAR"));
+					scr.log(Level.TRACE, "audit", new Exception("FOOBAR"));
+
+					String logged = (Level.AUDIT.err() ? err : out).buffer.toString();
+					assertThat(logged).startsWith("AUDIT : audit").contains("FOOBAR");
+				}
+
+				{
+					int n = 0;
+					reset(config);
+
+					for (Level configLevel : Level.values()) {
+
+						for (Level requestedLevel : Level.values()) {
+							out.reset();
+							err.reset();
+							when(config.getLogLevel()).thenReturn(configLevel);
+
+							String message = configLevel + ":" + requestedLevel;
+							scr.log(requestedLevel, message, null);
+							verify(config, times(++n)).getLogLevel();
+
+							String logged = (requestedLevel.err() ? err : out).buffer.toString();
+
+							if (configLevel.implies(requestedLevel)) {
+								assertThat(logged).contains(message);
+							} else {
+								assertThat(logged).isEmpty();
+							}
+						}
+					}
+				}
+			} finally {
+				System.setOut(out.stream);
+				System.setErr(err.stream);
+			}
+		}
+	}
+
+	@Test
+	public void testSelectionOfExtensionManager() {
+		LogService l = new LogService(log.getBundleContext());
+		l.register();
+
+		ScrConfiguration config = mock(ScrConfiguration.class);
+		when(config.getLogLevel()).thenReturn(Level.DEBUG);
+		when(config.isLogExtension()).thenReturn(true);
+		ScrLogger logger = ScrLogManager.scr(scr.getBundleContext(), config);
+		BundleLogger bundle = logger.bundle(component);
+		bundle.log(Level.ERROR, "Ext", null);
+		assertThat(l.entries).hasSize(1);
+		LogEntry le = l.entries.get(0);
+        assertThat(le.bundle).isEqualTo(component);
+	}
+
+	@Test
+	public void testSelectionOfBaseManager() {
+		LogService l = new LogService(log.getBundleContext());
+		l.register();
+
+		ScrConfiguration config = mock(ScrConfiguration.class);
+		when(config.isLogExtension()).thenReturn(false);
+		when(config.getLogLevel()).thenReturn(Level.DEBUG);
+
+		ScrLogger logger = ScrLogManager.scr(scr.getBundleContext(), config);
+		BundleLogger bundle = logger.bundle(component);
+		bundle.log(Level.ERROR, "Ext", null);
+		assertThat(l.entries).hasSize(1);
+		LogEntry le = l.entries.get(0);
+		assertThat(le.bundle).isEqualTo(component);
+	}
+
+	@Test
+	public void testExtensionLogLevelNotLoggingWhenRootSetToInfoAndLevelIsDebug() {
+		ScrConfiguration config = mock(ScrConfiguration.class);
+		when(config.isLogExtension()).thenReturn(true);
+		when(config.getLogLevel()).thenReturn(Level.DEBUG);
+
+		LogService l = new LogService(log.getBundleContext());
+		l.register();
+		l.levels.put(Logger.ROOT_LOGGER_NAME, LogLevel.INFO);
+		l.defaultLogLevel = LogLevel.TRACE;
+
+		ScrLogger lscr = ScrLogManager.scr(scr.getBundleContext(), config);
+
+		assertThat(lscr.isLogEnabled(Level.DEBUG)).isFalse();
+		assertThat(lscr.isLogEnabled(Level.INFO)).isTrue();
+
+		lscr.log(Level.DEBUG, "I should not be reported", null);
+
+		assertThat(l.entries).isEmpty();
+	}
+
+	@Test
+	public void testExtensionLogLevelNotLoggingWhenPartialNameSetToInfoAndLevelIsDebug() {
+		ScrConfiguration config = mock(ScrConfiguration.class);
+		when(config.isLogExtension()).thenReturn(true);
+		when(config.getLogLevel()).thenReturn(Level.DEBUG);
+
+		LogService l = new LogService(log.getBundleContext());
+		l.defaultLogLevel = LogLevel.TRACE;
+		l.register();
+		l.levels.put("org.apache.felix.scr", LogLevel.INFO);
+
+		ScrLogger lscr = ScrLogManager.scr(scr.getBundleContext(), config);
+
+		assertThat(lscr.isLogEnabled(Level.DEBUG)).isFalse();
+		assertThat(lscr.isLogEnabled(Level.INFO)).isTrue();
+
+		lscr.log(Level.DEBUG, "I should not be reported", null);
+
+		assertThat(l.entries).isEmpty();
+	}
+
+	@Test
+	public void testExtensionLogManager() {
+		ScrConfiguration config = mock(ScrConfiguration.class);
+		when(config.isLogExtension()).thenReturn(true);
+		when(config.getLogLevel()).thenReturn(Level.DEBUG);
+
+		LogService l = new LogService(log.getBundleContext());
+		l.register();
+
+		ScrLogManager lm = new ExtLogManager(scr.getBundleContext(), config);
+        lm.open();
+
+		{
+			l.entries.clear();
+			ScrLogger scr = lm.scr();
+			scr.log(Level.ERROR, "Scr", null);
+			assertThat(l.entries).hasSize(1);
+			LogEntry le = l.entries.get(0);
+			assertThat(le.format).isEqualTo("Scr");
+			assertThat(le.bundle).isEqualTo(this.scr);
+			assertThat(le.loggername).isEqualTo(ExtLogManager.SCR_LOGGER_NAME);
+		}
+
+		{
+			l.entries.clear();
+			BundleLogger blog = lm.bundle(component);
+			blog.log(Level.ERROR, "Bundle", null);
+			assertThat(l.entries).hasSize(1);
+			LogEntry le = l.entries.get(0);
+			assertThat(le.format).isEqualTo("Bundle");
+            assertThat(le.bundle).isEqualTo(component);
+			assertThat(le.loggername).isEqualTo(ExtLogManager.SCR_LOGGER_PREFIX + "component");
+		}
+
+		{
+			l.entries.clear();
+			ComponentLogger clog = lm.component(component, "implementation.class", "name");
+			clog.log(Level.ERROR, "Component", null);
+			assertThat(l.entries).hasSize(1);
+			LogEntry le = l.entries.get(0);
+			assertThat(le.format).isEqualTo("[name] Component");
+			assertThat(le.bundle).isEqualTo(component);
+			assertThat(le.loggername).isEqualTo(ExtLogManager.SCR_LOGGER_PREFIX + "component.name");
+
+			l.entries.clear();
+			clog.setComponentId(100);
+			clog.log(Level.ERROR, "Component", null);
+			le = l.entries.get(0);
+			assertThat(le.format).isEqualTo("[name(100)] Component");
+		}
+
+		{
+			lm.scr().close();
+		}
+	}
+
+	@Test
+	public void testBackwardCompatibilityOutput() {
+		ScrConfiguration config = mock(ScrConfiguration.class);
+		when(config.isLogExtension()).thenReturn(false);
+		when(config.getLogLevel()).thenReturn(Level.DEBUG);
+
+		LogService l = new LogService(log.getBundleContext());
+		l.register();
+
+		ScrLogManager lm = new ScrLogManager(scr.getBundleContext(), config);
+        lm.open();
+
+		{
+			l.entries.clear();
+			ScrLogger scr = lm.scr();
+			scr.log(Level.ERROR, "Scr", null);
+			assertThat(l.entries).hasSize(1);
+			LogEntry le = l.entries.get(0);
+			assertThat(le.format).isEqualTo("bundle scr:0.0.0 (1) Scr");
+			assertThat(le.bundle).isEqualTo(this.scr);
+			assertThat(le.loggername).isEqualTo(Logger.ROOT_LOGGER_NAME);
+		}
+
+		{
+			l.entries.clear();
+			BundleLogger blog = lm.bundle(component);
+			blog.log(Level.ERROR, "Bundle", null);
+			assertThat(l.entries).hasSize(1);
+			LogEntry le = l.entries.get(0);
+			assertThat(le.format).isEqualTo("bundle component:0.0.0 (2) Bundle");
+			assertThat(le.bundle).isEqualTo(component);
+			assertThat(le.loggername).isEqualTo(Logger.ROOT_LOGGER_NAME);
+		}
+
+		{
+			l.entries.clear();
+			ComponentLogger clog = lm.component(component, "implementation.class", "name");
+			clog.log(Level.ERROR, "Component", null);
+			assertThat(l.entries).hasSize(1);
+			LogEntry le = l.entries.get(0);
+			assertThat(le.format).isEqualTo("bundle component:0.0.0 (2)[implementation.class] : Component");
+			assertThat(le.bundle).isEqualTo(component);
+			assertThat(le.loggername).isEqualTo("implementation.class");
+
+			l.entries.clear();
+			clog.setComponentId(100);
+			clog.log(Level.ERROR, "Component", null);
+			le = l.entries.get(0);
+			assertThat(le.format).isEqualTo("bundle component:0.0.0 (2)[implementation.class(100)] : Component");
+		}
+	}
+
+	@Test
+	public void testLifeCycle() {
+
+		LogManager lm = new LogManager(scr.getBundleContext());
+        lm.open();
+		LoggerFacade facade = lm.getLogger(scr, "lifecycle", LoggerFacade.class);
+		assertThat(facade.logger).isNull();
+
+		LogService l = new LogService(log.getBundleContext());
+		l.register();
+		assertThat(l.loggers.get(scr)).isNull();
+
+        Logger logger = (Logger) facade.getLogger();
+
+		assertThat(logger).isNotNull();
+		assertThat(facade.logger).isEqualTo(logger);
+		assertThat(l.loggers.get(scr)).hasSize(1);
+
+		assertThat(logger.getName()).isEqualTo("lifecycle");
+
+        Logger logger2 = (Logger) facade.getLogger();
+		assertThat(logger2).isEqualTo(logger);
+		assertThat(l.loggers.get(scr)).hasSize(1);
+
+		l.unregister();
+		l.register();
+
+		assertThat(facade.logger).isNull();
+        logger = (Logger) facade.getLogger();
+		assertThat(l.loggers.get(scr)).hasSize(2);
+
+		assertThat(facade.logger).isNotNull();
+		lm.close();
+		assertThat(facade.logger).isNull();
+	}
+
+	@Test
+	public void testPrioritiesLogService() {
+
+		LogManager lm = new LogManager(scr.getBundleContext());
+        lm.open();
+		LoggerFacade facade = lm.getLogger(scr, "lifecycle", LoggerFacade.class);
+		assertThat(facade.logger).isNull();
+
+		LogService la = new LogService(log.getBundleContext());
+		la.register();
+        Logger loggera = (Logger) facade.getLogger();
+		assertThat(loggera).isNotNull();
+		assertThat(facade.logger).isEqualTo(loggera);
+		assertThat(la.loggers.get(scr)).hasSize(1);
+
+		LogService higherRanking = new LogService(log.getBundleContext()).ranking(10).register();
+
+		assertThat(facade.logger).isNull();
+
+        Logger loggerb = (Logger) facade.getLogger();
+		assertThat(loggerb).isNotNull();
+		assertThat(higherRanking.loggers.get(scr)).hasSize(1);
+
+		LogService lowerRanking = new LogService(log.getBundleContext()).ranking(-10).register();
+		assertThat(facade.logger).isNotNull();
+		assertThat(lowerRanking.loggers.get(scr)).isNull();
+	}
+
+	@Test
+	public void testLifeCycleOfComponentBundle() throws BundleException, InterruptedException {
+
+		ScrConfiguration config = mock(ScrConfiguration.class);
+
+		LogService l = new LogService(log.getBundleContext());
+		l.register();
+
+		ScrLogManager lm = new ScrLogManager(scr.getBundleContext(), config);
+        lm.open();
+		lm.component(component, "implementation.class", "component");
+
+		assertThat(lm.lock.domains).hasSize(1);
+		component.stop();
+
+		for (int i = 0; i < 100; i++) {
+			if (lm.lock.domains.isEmpty())
+				return;
+			Thread.sleep(10);
+		}
+		lm.close();
+		fail("domains not cleared within 1 sec");
+	}
+
+	@Test
+	public void testLogLevels() {
+
+		ScrConfiguration config = mock(ScrConfiguration.class);
+
+		when(config.getLogLevel()).thenReturn(Level.DEBUG);
+		when(config.isLogExtension()).thenReturn(false);
+
+		LogService l = new LogService(log.getBundleContext());
+
+		l.register();
+
+		ScrLogManager lm = new ScrLogManager(scr.getBundleContext(), config);
+        lm.open();
+		ScrLogger facade = lm.scr();
+
+		assert LogLevel.values().length == Level.values().length;
+		for (int i = 0; i < LogLevel.values().length; i++) {
+			assert LogLevel.values()[i].name().equals(Level.values()[i].name());
+		}
+
+		Exception ex = new Exception("exception");
+
+		for (LogLevel logLevel : LogLevel.values()) {
+
+			l.defaultLogLevel = logLevel;
+
+			for (Level level : Level.values()) {
+
+				if (logLevel.ordinal() >= level.ordinal())
+					assertThat(facade.isLogEnabled(level)).isTrue();
+				else
+					assertThat(facade.isLogEnabled(level)).isFalse();
+
+				facade.log(level, "level " + level, null);
+				facade.log(level, "level " + level, ex);
+			}
+		}
+		assertThat(l.entries).hasSize(42);
+	}
+
+	private JarResource makeBundle(String bsn) throws Exception {
+		@SuppressWarnings("resource")
+		Builder b = new Builder();
+		b.setBundleSymbolicName(bsn);
+		Jar jar = b.build();
+		b.removeClose(jar);
+		return new JarResource(jar);
+	}
+
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/logger/MockBundleLogger.java b/scr/src/test/java/org/apache/felix/scr/impl/logger/MockBundleLogger.java
index acbd8fb..3040101 100644
--- a/scr/src/test/java/org/apache/felix/scr/impl/logger/MockBundleLogger.java
+++ b/scr/src/test/java/org/apache/felix/scr/impl/logger/MockBundleLogger.java
@@ -21,18 +21,11 @@
 
 import java.text.MessageFormat;
 
-import org.apache.felix.scr.impl.MockBundle;
-import org.apache.felix.scr.impl.logger.InternalLogger.Level;
+import org.osgi.framework.Bundle;
 
 
-public class MockBundleLogger extends BundleLogger
+public class MockBundleLogger implements BundleLogger
 {
-    public MockBundleLogger()
-    {
-        super(new MockBundle(), new MockScrLogger());
-    }
-
-
     private String lastMessage;
 
 
@@ -43,22 +36,20 @@
     }
 
     @Override
-    public boolean log(final Level level, final String pattern, final Throwable ex,
+    public void log(final Level level, final String pattern, final Throwable ex,
         final Object... arguments)
     {
         if ( isLogEnabled( level ) )
         {
             log( level,  MessageFormat.format( pattern, arguments ), ex );
         }
-        return true;
     }
 
 
     @Override
-    public boolean log(final Level level, final String message, final Throwable ex)
+    public void log(final Level level, final String message, final Throwable ex)
     {
         lastMessage = message;
-        return true;
     }
 
 
@@ -66,4 +57,10 @@
     {
         return lastMessage != null && lastMessage.indexOf( value ) >= 0;
     }
+
+	@Override
+	public ComponentLogger component(Bundle m_bundle, String implementationClassName, String name) {
+		return new MockComponentLogger();
+	}
+
 }
\ No newline at end of file
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/logger/MockComponentLogger.java b/scr/src/test/java/org/apache/felix/scr/impl/logger/MockComponentLogger.java
index 8194ebe..18f7575 100644
--- a/scr/src/test/java/org/apache/felix/scr/impl/logger/MockComponentLogger.java
+++ b/scr/src/test/java/org/apache/felix/scr/impl/logger/MockComponentLogger.java
@@ -18,29 +18,23 @@
  */
 package org.apache.felix.scr.impl.logger;
 
-import org.apache.felix.scr.impl.logger.InternalLogger.Level;
-import org.apache.felix.scr.impl.metadata.ComponentMetadata;
-import org.apache.felix.scr.impl.metadata.DSVersion;
-
-public class MockComponentLogger extends ComponentLogger
+public class MockComponentLogger implements ComponentLogger
 {
-    public MockComponentLogger()
-    {
-        super(new ComponentMetadata(DSVersion.DS14), new MockBundleLogger());
-    }
 
-    @Override
-    public boolean log(final Level level, final String pattern, final Throwable ex,
-        final Object... arguments)
-    {
-        // ignore
-        return true;
-    }
+	@Override
+	public void log(Level level, String message, Throwable ex) {
+	}
 
-    @Override
-    public boolean log(final Level level, final String message, final Throwable ex)
-    {
-        // ignore
-        return true;
-    }
+	@Override
+	public void log(Level level, String message, Throwable ex, Object... args) {
+	}
+
+	@Override
+	public boolean isLogEnabled(Level level) {
+		return true;
+	}
+
+	@Override
+	public void setComponentId(long m_componentId) {
+	}
 }
\ No newline at end of file
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/logger/MockScrLogger.java b/scr/src/test/java/org/apache/felix/scr/impl/logger/MockScrLogger.java
index f2f2f94..3634439 100644
--- a/scr/src/test/java/org/apache/felix/scr/impl/logger/MockScrLogger.java
+++ b/scr/src/test/java/org/apache/felix/scr/impl/logger/MockScrLogger.java
@@ -18,72 +18,31 @@
  */
 package org.apache.felix.scr.impl.logger;
 
+import org.osgi.framework.Bundle;
 
-import org.apache.felix.scr.impl.MockBundle;
-import org.apache.felix.scr.impl.MockBundleContext;
-import org.apache.felix.scr.impl.logger.InternalLogger.Level;
-import org.apache.felix.scr.impl.manager.ScrConfiguration;
-
-
-public class MockScrLogger extends ScrLogger
+public class MockScrLogger implements ScrLogger
 {
-    public MockScrLogger()
-    {
-        super(new ScrConfiguration()
-        {
+	@Override
+	public void log(Level level, String message, Throwable ex) {		
+	}
 
-            @Override
-            public long stopTimeout()
-            {
-                return 0;
-            }
+	@Override
+	public void log(Level level, String message, Throwable ex, Object... args) {
+		
+	}
 
-            @Override
-            public long lockTimeout()
-            {
-                return 0;
-            }
+	@Override
+	public boolean isLogEnabled(Level level) {
+		return true;
+	}
 
-            @Override
-            public boolean keepInstances()
-            {
-                return false;
-            }
+	@Override
+	public BundleLogger bundle(Bundle bundle) {
+		return new MockBundleLogger();
+	}
 
-            @Override
-            public boolean isFactoryEnabled()
-            {
-                return false;
-            }
+	@Override
+	public void close() {
+	}
 
-            @Override
-            public boolean infoAsService()
-            {
-                return false;
-            }
-
-            @Override
-            public long serviceChangecountTimeout()
-            {
-                return 0;
-            }
-
-            @Override
-            public Level getLogLevel()
-            {
-                return Level.ERROR;
-            }
-
-            @Override
-            public boolean globalExtender() {
-                return false;
-            }
-
-            @Override
-            public boolean cacheMetadata()
-            {
-                return false;
-            }
-        }, new MockBundleContext(new MockBundle()));
-    }
 }
\ No newline at end of file
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/ComponentTestBase.java b/scr/src/test/java/org/apache/felix/scr/integration/ComponentTestBase.java
index ce27281..0966c5b 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/ComponentTestBase.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/ComponentTestBase.java
@@ -242,6 +242,10 @@
             scrTracker.close();
             scrTracker = null;
         }
+        catch (IllegalStateException e)
+        {
+            // not sure why this is thrown
+        }
         finally
         {
             log.stop();
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/Felix6274Test.java b/scr/src/test/java/org/apache/felix/scr/integration/Felix6274Test.java
index 08c4117..879d09f 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/Felix6274Test.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/Felix6274Test.java
@@ -50,16 +50,16 @@
         // This test creates its own component bundles
         descriptorFile = null;
         DS_LOGLEVEL = "debug";
-        // paxRunnerVmOption = DEBUG_VM_OPTION;
+        //paxRunnerVmOption = DEBUG_VM_OPTION;
     }
 
     private final List<Bundle> installedBundles = new ArrayList<>();
-    private Bundle log_1_4_bundle;
-    private Bundle log_1_3_bundle;
+    private Bundle log_bundle1;
+    private Bundle log_bundle2;
     
     @Before
     public void installBundles() throws BundleException {
-        String log101 = mavenBundle( "org.apache.felix", "org.apache.felix.log", "1.0.1" ).getURL();
+        String log101 = mavenBundle( "org.apache.felix", "org.apache.felix.log", "1.2.2" ).getURL();
 
         // install and start the resolver hook first
         Bundle hookBundle = bundleContext.installBundle("integration.test.6274_hook", createHookBundle());
@@ -70,13 +70,13 @@
         installedBundles.add( bundleContext.installBundle( log101 ) );
 
         
-        log_1_4_bundle = bundleContext.installBundle( "integration.test.6274", 
-                createStream( "6274", "[1.4,1.5)" ) );
-        installedBundles.add( log_1_4_bundle );
+        log_bundle1 = bundleContext.installBundle("integration.test.6274_1",
+            createStream("6274_1", "[1.4,1.5)"));
+        installedBundles.add( log_bundle1 );
 
-        log_1_3_bundle = bundleContext.installBundle( "integration.test.6274_2", 
-                createStream( "6274_2", "[1.3,1.4)" ) );
-        installedBundles.add( log_1_3_bundle );
+        log_bundle2 = bundleContext.installBundle( "integration.test.6274_2", 
+            createStream("6274_2", "[1.4,1.5)"));
+        installedBundles.add( log_bundle2 );
 
         for( Bundle b : installedBundles ) {
             b.start();
@@ -134,10 +134,10 @@
 
         // Locate the components
         ComponentDescriptionDTO log_1_4_dto = scrTracker.getService().getComponentDescriptionDTO(
-                log_1_4_bundle, "R7LoggerComponent" );
+                log_bundle1, "R7LoggerComponent" );
 
         ComponentDescriptionDTO log_1_3_dto = scrTracker.getService().getComponentDescriptionDTO(
-                log_1_3_bundle, "LogServiceComponent" );
+                log_bundle2, "LogServiceComponent" );
 
         assertNotNull( "No Log 1.4 Component DTO", log_1_4_dto );
         assertNotNull( "No Log 1.3 Component DTO", log_1_3_dto );
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/Felix6325OptionalLogTest.java b/scr/src/test/java/org/apache/felix/scr/integration/Felix6325OptionalLogTest.java
new file mode 100644
index 0000000..b4d0137
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/integration/Felix6325OptionalLogTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.scr.integration;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.hooks.resolver.ResolverHook;
+import org.osgi.framework.hooks.resolver.ResolverHookFactory;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.FrameworkWiring;
+
+@RunWith(PaxExam.class)
+public class Felix6325OptionalLogTest extends ComponentTestBase
+{
+    static
+    {
+        // This test creates its own component bundles
+        descriptorFile = null;
+        DS_LOGLEVEL = "debug";
+        //paxRunnerVmOption = DEBUG_VM_OPTION;
+    }
+
+    final ResolverHookFactory resolverHookFactory = new ResolverHookFactoryImpl();
+
+    class ResolverHookFactoryImpl implements ResolverHookFactory
+    {
+        final ResolverHook resolverHook = new ResolverHookImpl();
+
+        @Override
+        public ResolverHook begin(Collection<BundleRevision> triggers)
+        {
+            return resolverHook;
+        }
+    }
+
+    class ResolverHookImpl implements ResolverHook
+    {
+        @Override
+        public void filterSingletonCollisions(BundleCapability singleton,
+            Collection<BundleCapability> collisionCandidates)
+        {
+            // nothing to do
+        }
+
+        @Override
+        public void filterResolvable(Collection<BundleRevision> candidates)
+        {
+            // nothing to do
+        }
+
+        @Override
+        public void filterMatches(BundleRequirement requirement,
+            Collection<BundleCapability> candidates)
+        {
+            if (!candidates.isEmpty()
+                && PackageNamespace.PACKAGE_NAMESPACE.equals(requirement.getNamespace()))
+            {
+                String packageName = (String) candidates.iterator().next().getAttributes().get(
+                    PackageNamespace.PACKAGE_NAMESPACE);
+                if (packageName != null && packageName.startsWith("org.osgi.service.log"))
+                {
+                    candidates.clear();
+                }
+            }
+            return;
+        }
+
+        @Override
+        public void end()
+        {
+            // nothing to do
+        }
+    }
+
+    private ServiceRegistration<ResolverHookFactory> hookReg;
+    private Bundle scrBundle;
+    private FrameworkWiring fwkWiring;
+
+    @Before
+    public void registerResolverHook() throws BundleException
+    {
+        scrBundle = null;
+        for (Bundle b : bundleContext.getBundles())
+        {
+            if ("org.apache.felix.scr".equals(b.getSymbolicName()))
+            {
+                scrBundle = b;
+                break;
+            }
+        }
+        assertNotNull("No SCR bundle found!", scrBundle);
+
+        Bundle systemBundle = bundleContext.getBundle(Constants.SYSTEM_BUNDLE_LOCATION);
+        BundleContext systemContext = systemBundle.getBundleContext();
+        fwkWiring = systemContext.getBundle().adapt(FrameworkWiring.class);
+
+        // Use a resolver hook to make sure scr does not import the log packages
+        hookReg = systemContext.registerService(ResolverHookFactory.class,
+            resolverHookFactory, null);
+    }
+    
+    @After
+    public void unregisterResolverHook() throws InterruptedException
+    {
+        if (hookReg != null)
+        {
+            try
+            {
+                hookReg.unregister();
+            }
+            catch (IllegalStateException e)
+            {
+                // ignore
+            }
+            hookReg = null;
+        }
+        refresh(scrBundle);
+    }
+
+    private void refresh(Bundle b) throws InterruptedException
+    {
+        final CountDownLatch refreshWait = new CountDownLatch(1);
+        fwkWiring.refreshBundles(Collections.singleton(b), new FrameworkListener()
+        {
+            @Override
+            public void frameworkEvent(FrameworkEvent event)
+            {
+                refreshWait.countDown();
+            }
+        });
+        refreshWait.await(10, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void testNoLogPackage() throws Exception
+    {
+        scrBundle.stop();
+        refresh(scrBundle);
+        scrBundle.start();
+    }
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/ServiceBindGreedyTest.java b/scr/src/test/java/org/apache/felix/scr/integration/ServiceBindGreedyTest.java
index 30bec4f..34d0298 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/ServiceBindGreedyTest.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/ServiceBindGreedyTest.java
@@ -19,7 +19,14 @@
 package org.apache.felix.scr.integration;
 
 
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.Hashtable;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.felix.scr.integration.components.SimpleComponent;
 import org.apache.felix.scr.integration.components.SimpleComponent2;
@@ -46,7 +53,7 @@
     static
     {
         // uncomment to enable debugging of this test class
-        // paxRunnerVmOption = DEBUG_VM_OPTION;
+        //paxRunnerVmOption = DEBUG_VM_OPTION;
 
         descriptorFile = "/integration_test_simple_components_service_binding_greedy.xml";
     }
@@ -165,6 +172,120 @@
         TestCase.assertTrue( comp32.m_multiRef.isEmpty() );
     }
 
+    @Test
+    public void test_optional_single_dynamic_multi_thread1() throws Exception
+    {
+        do_test_optional_single_dynamic_multi_thread(false);
+    }
+
+    @Test
+    public void test_optional_single_dynamic_multi_thread2() throws Throwable
+    {
+        do_test_optional_single_dynamic_multi_thread(true);
+    }
+
+    AtomicBoolean enabledConfig = new AtomicBoolean();
+    private void do_test_optional_single_dynamic_multi_thread(
+        Boolean unregPreExistingFirst)
+        throws Exception
+    {
+        String name = "test_optional_single_dynamic";
+        final int numRanks = 1000;
+        final int ranksPerThread = 100;
+        final SimpleServiceImpl preExistingServ = SimpleServiceImpl.create(bundleContext,
+            "preExistingServ", numRanks / 4);
+
+        if (enabledConfig.compareAndSet(false, true))
+        {
+            getDisabledConfigurationAndEnable(name, ComponentConfigurationDTO.ACTIVE);
+        }
+        final SimpleComponent comp = SimpleComponent.INSTANCE;
+        TestCase.assertNotNull(comp);
+        TestCase.assertEquals(preExistingServ, comp.m_singleRef);
+        TestCase.assertTrue(comp.m_multiRef.isEmpty());
+
+        final List<Integer> ranks = Collections.synchronizedList(
+            new LinkedList<Integer>());
+        for (int i = 0; i < numRanks; i++)
+        {
+            ranks.add(i);
+        }
+        Collections.shuffle(ranks);
+
+        Collection<Thread> threads = new HashSet<>();
+        final List<SimpleServiceImpl> registrations = Collections.synchronizedList(
+            new LinkedList<SimpleServiceImpl>());
+        final AtomicReference<SimpleServiceImpl> highest = new AtomicReference<>();
+        for (int i = 0; i < numRanks; i = i + ranksPerThread)
+        {
+            threads.add(new Thread(new Runnable()
+            {
+                public void run()
+                {
+                    for (int j = 0; j < ranksPerThread; j++)
+                    {
+                        int rank = ranks.remove(0);
+                        SimpleServiceImpl srv = SimpleServiceImpl.create(bundleContext,
+                            "srv" + rank, rank);
+                        registrations.add(srv);
+                        if (rank == numRanks - 1)
+                        {
+                            highest.set(srv);
+                        }
+                    }
+                };
+            }));
+        }
+        for (Thread t : threads)
+        {
+            t.start();
+        }
+        for (Thread t : threads)
+        {
+            t.join();
+        }
+
+        TestCase.assertNotNull(highest.get());
+        TestCase.assertEquals(highest.get(), comp.m_singleRef);
+        TestCase.assertTrue(comp.m_multiRef.isEmpty());
+
+        threads.clear();
+        for (int i = 0; i < numRanks; i = i + ranksPerThread)
+        {
+            threads.add(new Thread(new Runnable()
+            {
+                public void run()
+                {
+                    for (int j = 0; j < ranksPerThread; j++)
+                    {
+                        registrations.remove(0).drop();
+                    }
+                };
+            }));
+        }
+        if (unregPreExistingFirst)
+        {
+            preExistingServ.drop();
+        }
+        for (Thread t : threads)
+        {
+            t.start();
+        }
+        for (Thread t : threads)
+        {
+            t.join();
+        }
+        if (!unregPreExistingFirst)
+        {
+            TestCase.assertEquals(preExistingServ, comp.m_singleRef);
+            TestCase.assertTrue(comp.m_multiRef.isEmpty());
+            preExistingServ.drop();
+        }
+
+        TestCase.assertEquals("Found bound reference: " + comp.m_singleRefBind + '-'
+            + comp.m_singleRefUnbind, null, comp.m_singleRef);
+        TestCase.assertTrue(comp.m_multiRef.isEmpty());
+    }
 
     @Test
     public void test_required_single_dynamic() throws Exception
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleComponent.java b/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleComponent.java
index 886d9c4..4d697fa 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleComponent.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleComponent.java
@@ -41,7 +41,7 @@
 
     public static final Set<SimpleComponent> PREVIOUS_INSTANCES = new HashSet<SimpleComponent>();
 
-    public static SimpleComponent INSTANCE;
+    public volatile static SimpleComponent INSTANCE;
 
     private Map<?, ?> m_config;
 
@@ -49,11 +49,11 @@
 
     public ComponentContext m_activateContext;
 
-    public SimpleService m_singleRef;
+    public volatile SimpleService m_singleRef;
 
-    public int m_singleRefBind = 0;
+    public volatile int m_singleRefBind = 0;
 
-    public int m_singleRefUnbind = 0;
+    public volatile int m_singleRefUnbind = 0;
 
     public final Set<SimpleService> m_multiRef = new HashSet<SimpleService>();
 
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleServiceImpl.java b/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleServiceImpl.java
index 090902b..d365e6e 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleServiceImpl.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleServiceImpl.java
@@ -21,14 +21,13 @@
 
 import java.util.Dictionary;
 import java.util.Hashtable;
-import java.util.Properties;
 
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceRegistration;
 
 
-public class SimpleServiceImpl implements SimpleService
+public class SimpleServiceImpl implements SimpleService, Comparable<SimpleServiceImpl>
 {
 
     private String m_value;
@@ -37,7 +36,7 @@
 
     private String m_filterProp;
 
-    private ServiceRegistration m_registration;
+    private ServiceRegistration<SimpleService> m_registration;
 
 
     public static SimpleServiceImpl create( BundleContext bundleContext, String value )
@@ -50,7 +49,8 @@
     {
         SimpleServiceImpl instance = new SimpleServiceImpl( value, ranking );
         Dictionary<String,?> props = instance.getProperties();
-        instance.setRegistration( bundleContext.registerService( SimpleService.class.getName(), instance, props ) );
+        instance.setRegistration(
+            bundleContext.registerService(SimpleService.class, instance, props));
         return instance;
     }
 
@@ -104,7 +104,7 @@
 
     public SimpleServiceImpl drop()
     {
-        ServiceRegistration sr = getRegistration();
+        ServiceRegistration<SimpleService> sr = getRegistration();
         if ( sr != null )
         {
             setRegistration( null );
@@ -120,14 +120,15 @@
     }
 
 
-    public SimpleServiceImpl setRegistration( ServiceRegistration registration )
+    public SimpleServiceImpl setRegistration(
+        ServiceRegistration<SimpleService> registration)
     {
         m_registration = registration;
         return this;
     }
 
 
-    public ServiceRegistration getRegistration()
+    public ServiceRegistration<SimpleService> getRegistration()
     {
         return m_registration;
     }
@@ -136,6 +137,13 @@
     @Override
     public String toString()
     {
-        return getClass().getSimpleName() + ": value=" + getValue() + ", filterprop=" + m_filterProp;
+        return getClass().getSimpleName() + ": value=" + getValue() + ", filterprop="
+            + m_filterProp + ", m_registration=" + m_registration;
+    }
+
+    @Override
+    public int compareTo(SimpleServiceImpl o)
+    {
+        return Integer.compare(this.m_ranking, o.m_ranking);
     }
 }
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/components/felix3680_2/Main.java b/scr/src/test/java/org/apache/felix/scr/integration/components/felix3680_2/Main.java
index 6de36ee..71f64b4 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/components/felix3680_2/Main.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/components/felix3680_2/Main.java
@@ -29,7 +29,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import org.apache.felix.scr.impl.manager.ThreadDump;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274/Component.java b/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274_1/Component.java
similarity index 93%
rename from scr/src/test/java/org/apache/felix/scr/integration/components/felix6274/Component.java
rename to scr/src/test/java/org/apache/felix/scr/integration/components/felix6274_1/Component.java
index 32eae52..6bef4f4 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274/Component.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274_1/Component.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.felix.scr.integration.components.felix6274;
+package org.apache.felix.scr.integration.components.felix6274_1;
 
 import org.osgi.service.log.Logger;
 
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274_hook/Activator.java b/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274_hook/Activator.java
index fa54fa6..1e8b230 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274_hook/Activator.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274_hook/Activator.java
@@ -68,23 +68,20 @@
     public void filterMatches(BundleRequirement requirement,
         Collection<BundleCapability> candidates)
     {
-        Bundle b = requirement.getResource().getBundle();
-
+        Bundle b = requirement.getRevision().getBundle();
         if (PackageNamespace.PACKAGE_NAMESPACE.equals(requirement.getNamespace())
             && "org.osgi.service.log".equals(
                 candidates.iterator().next().getAttributes().get(
                     PackageNamespace.PACKAGE_NAMESPACE))
-            && "org.apache.felix.log".equals(b.getSymbolicName())
-            && Version.valueOf("1.0.1").equals(b.getVersion()))
+            && (b.getSymbolicName() != null && !b.getSymbolicName().contains("6274_1")))
         {
-            // Felix log 1.0.1 incorrectly allows import of R7 log
+            // force bundles to import felix log package
             Iterator<BundleCapability> iCaps = candidates.iterator();
             while (iCaps.hasNext())
             {
-                if (!iCaps.next().getRevision().getBundle().equals(
-                    requirement.getRevision().getBundle()))
+                if (!"org.apache.felix.log".equals(
+                    iCaps.next().getRevision().getBundle().getSymbolicName()))
                 {
-                    System.out.println("TJW - removed");
                     iCaps.remove();
                 }
             }
diff --git a/scr/src/test/resources/integration_test_FELIX_6274.xml b/scr/src/test/resources/integration_test_FELIX_6274_1.xml
similarity index 96%
rename from scr/src/test/resources/integration_test_FELIX_6274.xml
rename to scr/src/test/resources/integration_test_FELIX_6274_1.xml
index 91d8b63..6999c1d 100644
--- a/scr/src/test/resources/integration_test_FELIX_6274.xml
+++ b/scr/src/test/resources/integration_test_FELIX_6274_1.xml
@@ -20,7 +20,7 @@
 <components xmlns:scr="http://www.osgi.org/xmlns/scr/v1.4.0">
 
     <scr:component name="R7LoggerComponent">
-        <implementation class="org.apache.felix.scr.integration.components.felix6274.Component" />
+        <implementation class="org.apache.felix.scr.integration.components.felix6274_1.Component" />
         <reference name="LOG" interface="org.osgi.service.log.LoggerFactory" field="logger"/>
     </scr:component>
         
diff --git a/webconsole-plugins/event/pom.xml b/webconsole-plugins/event/pom.xml
index 6c2c384..26c9c2a 100644
--- a/webconsole-plugins/event/pom.xml
+++ b/webconsole-plugins/event/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.apache.felix</groupId>
         <artifactId>felix-parent</artifactId>
-        <version>4</version>
+        <version>7</version>
         <relativePath>../../../pom/pom.xml</relativePath>
     </parent>
 
@@ -32,8 +32,7 @@
 
     <name>Apache Felix Web Console Event Plugin</name>
     <description>
-        This is a plugin for the Apache Felix OSGi web console for displaying
-        OSGi events.
+        This is a plugin for the Apache Felix OSGi web console for displaying OSGi events.
     </description>
 
     <scm>
@@ -71,7 +70,7 @@
             <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
-                <version>3.3.0</version>
+                <version>5.1.1</version>
                 <extensions>true</extensions>
                 <configuration>
                     <instructions>
@@ -82,24 +81,42 @@
                             org.apache.felix.webconsole.plugins.event.internal.Activator
                         </Bundle-Activator>
                         <Import-Package>
-                            org.osgi.service.cm;version="[1.2,2)";resolution:=optional,
-                            org.osgi.service.event;;version="[1.2,2)",*
+                            org.osgi.service.cm;version="[1.4,2)";resolution:=optional,*
                         </Import-Package>
                         <Private-Package>
                             org.apache.felix.webconsole.plugins.event.*
                         </Private-Package>
                         <DynamicImport-Package>
-                            org.osgi.service.cm;version="[1.2,2)",
-                            javax.servlet,
-                            javax.servlet.http
+                            org.osgi.service.cm;version="[1.4,2)"
                         </DynamicImport-Package>
-                        <Include-Resource>{maven-resources},OSGI-INF=target/classes/OSGI-INF</Include-Resource>
                         <Embed-Dependency>
                              org.apache.felix.utils;inline=org/apache/felix/utils/json/JSONWriter**
                         </Embed-Dependency>
                     </instructions>
                 </configuration>
             </plugin>
+           <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>3.2.1</version>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <createDependencyReducedPom>false</createDependencyReducedPom>
+                            <relocations>
+                                <relocation>
+                                    <pattern>org.apache.felix.utils</pattern>
+                                    <shadedPattern>org.apache.felix.webconsole.plugins.event.utils</shadedPattern>
+                                </relocation>
+                            </relocations>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
             <plugin>
                 <groupId>org.apache.rat</groupId>
                 <artifactId>apache-rat-plugin</artifactId>
@@ -127,19 +144,25 @@
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>osgi.core</artifactId>
-            <version>4.3.1</version>
+            <version>6.0.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
-            <artifactId>osgi.cmpn</artifactId>
-            <version>4.3.1</version>
+            <artifactId>org.osgi.service.event</artifactId>
+            <version>1.4.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.cm</artifactId>
+            <version>1.5.0</version>
+            <scope>provided</scope>
+        </dependency>  
+        <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.utils</artifactId>
-            <version>1.9.0</version>
+            <version>1.11.4</version>
             <scope>provided</scope>
         </dependency>
     </dependencies>
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/Activator.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/Activator.java
index 06be18b..ca7bbda 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/Activator.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/Activator.java
@@ -32,7 +32,7 @@
 public class Activator implements BundleActivator
 {
     /** Registration for the plugin. */
-    private ServiceRegistration pluginRegistration;
+    private ServiceRegistration<Servlet> pluginRegistration;
 
     /** Listener */
     private EventListener eventListener;
@@ -46,6 +46,7 @@
     /**
      * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
      */
+    @Override
     public void start(final BundleContext context) throws Exception
     {
         // we start with the plugin
@@ -58,14 +59,14 @@
         this.featuresHandler = new OptionalFeaturesHandler(this.plugin, context);
 
         // finally we register the plugin
-        final Dictionary props = new Hashtable();
+        final Dictionary<String, Object> props = new Hashtable<>();
         props.put( Constants.SERVICE_DESCRIPTION, "Event Plugin for the Apache Felix Web Console" );
         props.put( Constants.SERVICE_VENDOR, "The Apache Software Foundation" );
         props.put( "felix.webconsole.label", "events");
         props.put( "felix.webconsole.title", "%plugin.events.title");
         props.put( "felix.webconsole.css", "/events/res/ui/events.css");
         props.put( "felix.webconsole.category", "OSGi");
-        this.pluginRegistration = context.registerService(Servlet.class.getName(),
+        this.pluginRegistration = context.registerService(Servlet.class,
                                 plugin,
                                 props);
     }
@@ -73,6 +74,7 @@
     /**
      * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
      */
+    @Override
     public void stop(final BundleContext context) throws Exception
     {
         if ( this.pluginRegistration != null )
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/ConfigurationListener.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/ConfigurationListener.java
index ecfbf7b..f8e3dfe 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/ConfigurationListener.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/ConfigurationListener.java
@@ -23,7 +23,9 @@
 import java.util.Hashtable;
 
 import org.apache.felix.webconsole.plugins.event.internal.converter.ConfigurationEventConverter;
-import org.osgi.framework.*;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.cm.ConfigurationEvent;
 import org.osgi.service.cm.ManagedService;
 
@@ -34,11 +36,11 @@
 
     private final String pid;
 
-    static ServiceRegistration create( final BundleContext context, final PluginServlet plugin )
+    static ServiceRegistration<?> create( final BundleContext context, final PluginServlet plugin )
     {
         ConfigurationListener cl = new ConfigurationListener( plugin );
 
-        Dictionary props = new Hashtable();
+        Dictionary<String, Object> props = new Hashtable<>();
         props.put( Constants.SERVICE_VENDOR, "The Apache Software Foundation" );
         props.put( Constants.SERVICE_DESCRIPTION, "Event plugin for the Felix Web Console Configuration Receiver" );
         props.put( Constants.SERVICE_PID, cl.pid );
@@ -57,6 +59,7 @@
     /**
      * @see org.osgi.service.cm.ConfigurationListener#configurationEvent(org.osgi.service.cm.ConfigurationEvent)
      */
+    @Override
     public void configurationEvent(ConfigurationEvent event)
     {
         this.plugin.getCollector().add(ConfigurationEventConverter.getInfo(event));
@@ -68,7 +71,8 @@
     /**
      * @see org.osgi.service.cm.ManagedService#updated(java.util.Dictionary)
      */
-    public void updated( Dictionary config )
+    @Override
+    public void updated( Dictionary<String, ?> config )
     {
         plugin.updateConfiguration( config );
     }
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventCollector.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventCollector.java
index 14ae432..769c182 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventCollector.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventCollector.java
@@ -16,7 +16,9 @@
  */
 package org.apache.felix.webconsole.plugins.event.internal;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
 
 /**
  * This class collects events
@@ -27,16 +29,16 @@
     private static final String PROPERTY_MAX_SIZE = "max.size";
     private static final int DEFAULT_MAX_SIZE = 250;
 
-    private List eventInfos;
+    private List<EventInfo> eventInfos;
 
     private long startTime;
 
     private int maxSize;
 
-    public EventCollector(final Dictionary props)
+    public EventCollector()
     {
         this.clear();
-        this.updateConfiguration(props);
+        this.updateConfiguration(null);
     }
 
     public void add(final EventInfo info)
@@ -62,7 +64,7 @@
     {
         synchronized ( this )
         {
-            this.eventInfos = new ArrayList();
+            this.eventInfos = new ArrayList<>();
             this.startTime = System.currentTimeMillis();
         }
     }
@@ -70,15 +72,15 @@
     /**
      * Return a copy of the current event list
      */
-    public List getEvents()
+    public List<EventInfo> getEvents()
     {
         synchronized ( this )
         {
-            return new ArrayList(eventInfos);
+            return new ArrayList<>(eventInfos);
         }
     }
 
-    public void updateConfiguration( final Dictionary props)
+    public void updateConfiguration( final Dictionary<String, ?> props)
     {
         this.maxSize = OsgiUtil.toInteger(props, PROPERTY_MAX_SIZE, DEFAULT_MAX_SIZE);
         synchronized ( this )
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventHandler.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventHandler.java
index 07d9e52..9349889 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventHandler.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventHandler.java
@@ -20,7 +20,9 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import org.apache.felix.webconsole.plugins.event.internal.converter.*;
+import org.apache.felix.webconsole.plugins.event.internal.converter.BundleEventConverter;
+import org.apache.felix.webconsole.plugins.event.internal.converter.FrameworkEventConverter;
+import org.apache.felix.webconsole.plugins.event.internal.converter.ServiceEventConverter;
 import org.osgi.service.event.Event;
 
 /**
@@ -49,6 +51,7 @@
     /**
      * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
      */
+    @Override
     public void handleEvent( Event event )
     {
         boolean include = true;
@@ -62,11 +65,11 @@
         }
         if ( include )
         {
-            Map props = null;
+            Map<String, Object> props = null;
             final String[] names = event.getPropertyNames();
             if ( names != null && names.length > 0 )
             {
-                props = new HashMap();
+                props = new HashMap<>();
                 for ( int i = 0; i < names.length; i++ )
                 {
                     props.put(names[i], event.getProperty(names[i]));
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventInfo.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventInfo.java
index 3e1405a..d2fc33c 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventInfo.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventInfo.java
@@ -37,7 +37,7 @@
     public final long received;
 
     /** Properties. */
-    public final Map properties;
+    public final Map<String, Object> properties;
 
     /** The event class. */
     public final String category;
@@ -51,7 +51,7 @@
         this.category = category;
     }
 
-    public EventInfo( final String topic, final String info, final String category, final Map props )
+    public EventInfo( final String topic, final String info, final String category, final Map<String, Object> props )
     {
         this.topic = topic;
         this.info = info;
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OptionalFeaturesHandler.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OptionalFeaturesHandler.java
index 7559f90..0f98f5a 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OptionalFeaturesHandler.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OptionalFeaturesHandler.java
@@ -20,7 +20,13 @@
 import java.util.Dictionary;
 import java.util.Hashtable;
 
-import org.osgi.framework.*;
+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.framework.ServiceRegistration;
 import org.osgi.service.event.EventAdmin;
 
 /**
@@ -40,16 +46,16 @@
 
     /** Event admin service id */
     //private Long eventAdminServiceId;
-    private ServiceReference eventAdminServiceRef;
+    private ServiceReference<?> eventAdminServiceRef;
 
     /** Registration for the event handler. */
-    private ServiceRegistration eventHandlerRegistration;
+    private ServiceRegistration<?> eventHandlerRegistration;
 
     /** Configuration admin service id */
     private Long configAdminServiceId;
 
     /** Registration for the configuration listener. */
-    private ServiceRegistration configListenerRegistration;
+    private ServiceRegistration<?> configListenerRegistration;
 
     /** Bundle context. */
     private final BundleContext bundleContext;
@@ -62,7 +68,7 @@
         this.plugin = plugin;
         this.bundleContext = context;
         // check if event admin is already available
-        final ServiceReference ref = this.bundleContext.getServiceReference(EVENT_ADMIN_CLASS_NAME);
+        final ServiceReference<?> ref = this.bundleContext.getServiceReference(EVENT_ADMIN_CLASS_NAME);
         if ( ref != null )
         {
             bindEventAdmin(ref);
@@ -70,7 +76,7 @@
 
         // check if config admin is already available
         this.configAdminServiceId = null;
-        final ServiceReference cfaRef = this.bundleContext.getServiceReference(CONFIGURATION_ADMIN_CLASS_NAME);
+        final ServiceReference<?> cfaRef = this.bundleContext.getServiceReference(CONFIGURATION_ADMIN_CLASS_NAME);
         if ( cfaRef != null )
         {
             final Long id = (Long)cfaRef.getProperty(Constants.SERVICE_ID);
@@ -98,9 +104,10 @@
     /**
      * @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent)
      */
+    @Override
     public void serviceChanged(final ServiceEvent event)
     {
-        final ServiceReference ref = event.getServiceReference();
+        final ServiceReference<?> ref = event.getServiceReference();
         final String[] objectClasses =  (String[])ref.getProperty(Constants.OBJECTCLASS);
         if ( objectClasses != null)
         {
@@ -112,6 +119,7 @@
                     {
                         new Thread()
                         {
+                            @Override
                             public void run()
                             {
                                 try {
@@ -125,6 +133,7 @@
                     {
                         new Thread()
                         {
+                            @Override
                             public void run()
                             {
                                 try {
@@ -142,6 +151,7 @@
                     {
                         new Thread()
                         {
+                            @Override
                             public void run()
                             {
                                 try {
@@ -155,6 +165,7 @@
                     {
                         new Thread()
                         {
+                            @Override
                             public void run()
                             {
                                 try {
@@ -169,14 +180,14 @@
         }
     }
 
-    synchronized void bindEventAdmin(ServiceReference ref)
+    synchronized void bindEventAdmin(ServiceReference<?> ref)
     {
         if ( this.eventAdminServiceRef != null)
         {
             this.unbindEventAdmin(this.eventAdminServiceRef);
         }
         this.eventAdminServiceRef = ref;
-        final Dictionary props = new Hashtable();
+        final Dictionary<String, Object> props = new Hashtable<>();
         props.put( Constants.SERVICE_DESCRIPTION, "Event handler for the Apache Felix Web Console" );
         props.put( Constants.SERVICE_VENDOR, "The Apache Software Foundation" );
         props.put( "event.topics", "*"); //$NON-NLS-1$ //$NON-NLS-2$
@@ -186,7 +197,7 @@
                 new EventHandler(this.plugin.getCollector()), props);
     }
 
-    synchronized void unbindEventAdmin(ServiceReference ref)
+    synchronized void unbindEventAdmin(ServiceReference<?> ref)
     {
         if ( this.eventAdminServiceRef != null && this.eventAdminServiceRef.equals(ref) )
         {
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OsgiUtil.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OsgiUtil.java
index 3abac87..b4471f6 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OsgiUtil.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OsgiUtil.java
@@ -18,7 +18,11 @@
  */
 package org.apache.felix.webconsole.plugins.event.internal;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.List;
 
 /**
  * The <code>OsgiUtil</code> is a utility class providing some usefull utility
@@ -26,7 +30,7 @@
  */
 public class OsgiUtil {
 
-    public static boolean toBoolean(Dictionary props, String key, boolean defaultValue)
+    public static boolean toBoolean(Dictionary<String, ?> props, String key, boolean defaultValue)
     {
         Object propValue = toObject( props, key );
         if (propValue instanceof Boolean)
@@ -41,13 +45,13 @@
         return defaultValue;
     }
 
-    public static String toString(Dictionary props, String key, String defaultValue)
+    public static String toString(Dictionary<String, ?> props, String key, String defaultValue)
     {
         Object propValue = toObject( props, key );
         return (propValue != null) ? propValue.toString() : defaultValue;
     }
 
-    public static int toInteger(Dictionary props, String key, int defaultValue)
+    public static int toInteger(Dictionary<String, ?> props, String key, int defaultValue)
     {
         Object propValue = toObject( props, key );
         if (propValue instanceof Integer)
@@ -69,7 +73,7 @@
         return defaultValue;
     }
 
-    public static Object toObject(Dictionary props, String key)
+    public static Object toObject(Dictionary<String, ?> props, String key)
     {
         if (props == null || key == null )
         {
@@ -87,7 +91,7 @@
         }
         else if (propValue instanceof Collection)
         {
-            Collection prop = (Collection) propValue;
+            Collection<?> prop = (Collection<?>) propValue;
             return prop.isEmpty() ? null : prop.iterator().next();
         }
         else
@@ -96,7 +100,7 @@
         }
     }
 
-    public static String[] toStringArray(Dictionary props, String key, String[] defaultArray)
+    public static String[] toStringArray(Dictionary<String, ?> props, String key, String[] defaultArray)
     {
         if (props == null || key == null )
         {
@@ -125,7 +129,7 @@
         {
             // other array
             Object[] valueArray = (Object[]) propValue;
-            List values = new ArrayList(valueArray.length);
+            List<String> values = new ArrayList<>(valueArray.length);
             for(int i=0; i<valueArray.length; i++)
             {
                 final Object value = valueArray[i];
@@ -134,15 +138,15 @@
                     values.add(value.toString());
                 }
             }
-            return (String[]) values.toArray(new String[values.size()]);
+            return values.toArray(new String[values.size()]);
 
         }
         else if (propValue instanceof Collection)
         {
             // collection
-            Collection valueCollection = (Collection) propValue;
-            List valueList = new ArrayList(valueCollection.size());
-            final Iterator i = valueCollection.iterator();
+            Collection<?> valueCollection = (Collection<?>) propValue;
+            List<String> valueList = new ArrayList<>(valueCollection.size());
+            final Iterator<?> i = valueCollection.iterator();
             while ( i.hasNext() )
             {
                 final Object value = i.next();
@@ -151,7 +155,7 @@
                     valueList.add(value.toString());
                 }
             }
-            return (String[]) valueList.toArray(new String[valueList.size()]);
+            return valueList.toArray(new String[valueList.size()]);
         }
 
         return defaultArray;
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PluginServlet.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PluginServlet.java
index 15d7e61..92819a5 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PluginServlet.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PluginServlet.java
@@ -27,7 +27,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -44,6 +43,8 @@
 public class PluginServlet extends HttpServlet
 {
 
+    private static final long serialVersionUID = -8601361741848077124L;
+
     private static final String ACTION_POST = "post"; //$NON-NLS-1$
     private static final String ACTION_SEND = "send"; //$NON-NLS-1$
     private static final String ACTION_CLEAR = "clear"; //$NON-NLS-1$
@@ -62,11 +63,11 @@
 
     public PluginServlet()
     {
-        this.collector = new EventCollector(null);
+        this.collector = new EventCollector();
         TEMPLATE = readTemplateFile(getClass(), "/res/events.html"); //$NON-NLS-1$
     }
 
-    private final String readTemplateFile(final Class clazz, final String templateFile)
+    private final String readTemplateFile(final Class<?> clazz, final String templateFile)
     {
         InputStream templateStream = getClass().getResourceAsStream(templateFile);
         if (templateStream != null)
@@ -119,6 +120,7 @@
     /**
      * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
+    @Override
     protected void doPost( HttpServletRequest req, HttpServletResponse resp )
     throws ServletException, IOException
     {
@@ -141,7 +143,7 @@
 
     private void renderJSON( final PrintWriter pw ) throws IOException
     {
-        List events = this.collector.getEvents();
+        List<EventInfo> events = this.collector.getEvents();
 
         StringBuffer statusLine = new StringBuffer();
         statusLine.append( events.size() );
@@ -155,7 +157,7 @@
         {
             statusLine.append( " since " );
             Date d = new Date();
-            d.setTime( ( ( EventInfo ) events.get( 0 ) ).received );
+            d.setTime( events.get( 0 ).received );
             statusLine.append( d );
         }
         statusLine.append( ". (Event admin: " );
@@ -172,7 +174,7 @@
 
         // Compute scale: startTime is 0, lastTimestamp is 100%
         final long startTime = this.collector.getStartTime();
-        final long endTime = (events.size() == 0 ? startTime : ((EventInfo)events.get(events.size() - 1)).received);
+        final long endTime = (events.size() == 0 ? startTime : events.get(events.size() - 1).received);
         final float scale = (endTime == startTime ? 100.0f : 100.0f / (endTime - startTime));
 
         final JSONWriter writer = new JSONWriter(pw);
@@ -186,7 +188,7 @@
         // display list in reverse order
         for ( int index = events.size() - 1; index >= 0; index-- )
         {
-            eventJson( writer, ( EventInfo ) events.get( index ), index, startTime, scale );
+            eventJson( writer, events.get( index ), index, startTime, scale );
         }
 
         writer.endArray();
@@ -195,6 +197,7 @@
     }
 
 
+    @Override
     protected void doGet( HttpServletRequest request, HttpServletResponse response )
     throws ServletException, IOException
     {
@@ -272,10 +275,10 @@
         jw.object();
         if ( info.properties != null && info.properties.size() > 0 )
         {
-            final Iterator i = info.properties.entrySet().iterator();
+            final Iterator<Map.Entry<String, Object>> i = info.properties.entrySet().iterator();
             while ( i.hasNext() )
             {
-                final Map.Entry current = (Entry) i.next();
+                final Map.Entry<String, Object> current = i.next();
                 jw.key( current.getKey().toString() );
                 jw.value(current.getValue());
             }
@@ -285,7 +288,7 @@
         jw.endObject();
     }
 
-    public void updateConfiguration( Dictionary dict)
+    public void updateConfiguration( Dictionary<String, ?> dict)
     {
         this.collector.updateConfiguration(dict);
     }
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PropertiesEditorSupport.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PropertiesEditorSupport.java
index 1105a7b..55d81fd 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PropertiesEditorSupport.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PropertiesEditorSupport.java
@@ -39,13 +39,13 @@
      * @param request the request to process

      * @return the converted properties

      */

-    public static final Dictionary convertProperties(HttpServletRequest request)

+    public static final Dictionary<String, ?> convertProperties(HttpServletRequest request)

     {

         String keys[] = request.getParameterValues("key"); //$NON-NLS-1$

         String vals[] = request.getParameterValues("val"); //$NON-NLS-1$

         String types[] = request.getParameterValues("type"); //$NON-NLS-1$

 

-        final Hashtable properties = new Hashtable();

+        final Dictionary<String, Object> properties = new Hashtable<>();

         synchronized (properties)

         {

             for (int i = 0; keys != null && i < keys.length; i++)

@@ -85,7 +85,7 @@
         }

         else if ("char".equals(type)) //$NON-NLS-1$

         {

-            return new Character(value.toString().charAt(0));

+            return value.toString().charAt(0);

         }

         else if ("byte array".equals(type)) //$NON-NLS-1$

         {

diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/converter/ServiceEventConverter.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/converter/ServiceEventConverter.java
index 0092cd1..2ddb1e6 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/converter/ServiceEventConverter.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/converter/ServiceEventConverter.java
@@ -17,7 +17,10 @@
 package org.apache.felix.webconsole.plugins.event.internal.converter;
 
 import org.apache.felix.webconsole.plugins.event.internal.EventInfo;
-import org.osgi.framework.*;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceReference;
 
 public class ServiceEventConverter {
 
@@ -29,7 +32,7 @@
             return null;
         }
 
-        final ServiceReference ref = event.getServiceReference();
+        final ServiceReference<?> ref = event.getServiceReference();
 
         final StringBuffer buffer = new StringBuffer( "Service " );
         final Object pid = ref.getProperty( Constants.SERVICE_PID );
diff --git a/webconsole/changelog.txt b/webconsole/changelog.txt
index e2a9788..0d8e7d6 100644
--- a/webconsole/changelog.txt
+++ b/webconsole/changelog.txt
@@ -1,3 +1,9 @@
+Changes in 4.5.6
+----------------
+** Bug
+    * [FELIX-6328] - Web Console (All In One) imports javax.portlet via fileupload
+
+
 Changes in 4.5.4
 ----------------
 ** Bug
diff --git a/webconsole/pom.xml b/webconsole/pom.xml
index 2da6a4e..085b56c 100644
--- a/webconsole/pom.xml
+++ b/webconsole/pom.xml
@@ -93,7 +93,7 @@
             <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
-                <version>4.2.1</version>
+                <version>5.1.1</version>
                 <extensions>true</extensions>
                 <configuration>
                     <instructions>
@@ -136,9 +136,6 @@
                             org.osgi.service.prefs;version="[1.1,2)",
                             org.osgi.service.wireadmin;version="[1.0,2)"
                         </DynamicImport-Package>
-                        <Include-Resource>
-                            {maven-resources},OSGI-INF=target/classes/OSGI-INF
-                        </Include-Resource>
                         <Embed-Dependency>
                             org.apache.felix.utils;inline=org/apache/felix/utils/manifest/**,
                             org.apache.felix.utils;inline=org/apache/felix/utils/json/**
@@ -233,7 +230,7 @@
                                            commons-fileupload;inline=true,
                                            commons-io;inline=true
                                        </Embed-Dependency>
-                                       
+                                       <Import-Package>!javax.portlet,*</Import-Package>                            
                                        <_removeheaders>
                                            Embed-Dependency,Private-Package,Include-Resource
                                        </_removeheaders>