FELIX-2264 Implement for lazy service registration and dynamic API binding

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@931858 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole-plugins/memoryusage/pom.xml b/webconsole-plugins/memoryusage/pom.xml
index c99ddac..921af12 100644
--- a/webconsole-plugins/memoryusage/pom.xml
+++ b/webconsole-plugins/memoryusage/pom.xml
@@ -71,20 +71,24 @@
                         </Bundle-SymbolicName>
                         <Import-Package>
                             javax.management.*,
-                            org.osgi.framework,
-                            org.slf4j.*,
+                            org.osgi.framework
+                        </Import-Package>
+                        <DynamicImport-Package>
+                            <!-- logging -->
+                            org.osgi.service.log;version="[1.3,2)",
                             
-                            <!-- plug into the traditional Felix shell -->
-                            org.apache.felix.shell;
+                            <!-- configuration -->
+                            org.osgi.service.cm;version="[1.2,2)",
+                            org.osgi.service.metatype;version="[1.1,2)",
                             
                             <!-- plug into the web console -->
-                            javax.servlet.*;
-                            org.apache.felix.webconsole;
+                            javax.servlet;
+                            javax.servlet.http;version="[2.3,3)",
+                            org.apache.felix.webconsole;version="[3.0,3.1)",
 
-                            <!-- support configuration -->
-                            org.osgi.service.cm;
-                            org.osgi.service.metatype;resolution:=optional
-                        </Import-Package>
+                            <!-- plug into the traditional Felix shell -->
+                            org.apache.felix.shell;version="[1.0,1.1)"
+                        </DynamicImport-Package>
                         <Private-Package>
                             org.apache.felix.webconsole.plugins.memoryusage.*
                         </Private-Package>
@@ -127,16 +131,5 @@
             <version>2.3</version>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-            <version>1.5.2</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <version>3.8.1</version>
-        </dependency>
     </dependencies>
 </project>
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/Activator.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/Activator.java
index 4b18d68..1b4ee5d 100644
--- a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/Activator.java
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/Activator.java
@@ -21,59 +21,56 @@
 import java.util.Dictionary;
 import java.util.Hashtable;
 
+import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
 
 public class Activator implements BundleActivator
 {
 
     private MemoryUsageSupport support;
 
-    public void start(BundleContext bundleContext)
+    public void start(final BundleContext bundleContext)
     {
-
         support = new MemoryUsageSupport(bundleContext);
 
         // install thread handler shell command
-        try
+        new AbstractServiceFactory(bundleContext, null, "org.apache.felix.shell.Command")
         {
-            register(bundleContext, new String[]
-                { "org.apache.felix.shell.Command" }, new MemoryUsageCommand(support), null);
-        }
-        catch (Throwable t)
-        {
-            // shell service might not be available, don't care
-        }
+            @Override
+            protected Object createObject()
+            {
+                return new MemoryUsageCommand(support);
+            }
+        };
 
         // install Web Console plugin
-        try
+        Dictionary<String, Object> pluginProps = new Hashtable<String, Object>();
+        pluginProps.put("felix.webconsole.label", MemoryUsageConstants.LABEL);
+        new AbstractServiceFactory(bundleContext, pluginProps, "javax.servlet.Servlet",
+            "org.apache.felix.webconsole.ConfigurationPrinter")
         {
-            Dictionary<String, Object> properties = new Hashtable<String, Object>();
-            properties.put("felix.webconsole.label", MemoryUsageConstants.LABEL);
-
-            register(bundleContext, new String[]
-                { "javax.servlet.Servlet", "org.apache.felix.webconsole.ConfigurationPrinter" }, new MemoryUsagePanel(
-                bundleContext, support), properties);
-        }
-        catch (Throwable t)
-        {
-            // web console might not be available, don't care
-        }
+            @Override
+            public Object createObject()
+            {
+                return new MemoryUsagePanel(support);
+            }
+        };
 
         // register for configuration
-        try
+        Dictionary<String, Object> cmProps = new Hashtable<String, Object>();
+        cmProps.put(Constants.SERVICE_PID, MemoryUsageConstants.PID);
+        new AbstractServiceFactory(bundleContext, cmProps, "org.osgi.service.cm.ManagedService")
         {
-            Dictionary<String, Object> properties = new Hashtable<String, Object>();
-            properties.put(Constants.SERVICE_PID, MemoryUsageConstants.PID);
-            register(bundleContext, new String[]
-                { "org.osgi.service.cm.ManagedService" }, new MemoryUsageConfigurator(support), properties);
-        }
-        catch (Throwable t)
-        {
-            // Configuration Admin and Metatype Service API might not be
-            // available, don't care
-        }
+            @Override
+            public Object createObject()
+            {
+                return new MemoryUsageConfigurator(support);
+            }
+        };
     }
 
     public void stop(BundleContext bundleContext)
@@ -85,21 +82,46 @@
         }
     }
 
-    static void register(BundleContext context, String[] serviceNames, Object service,
-        Dictionary<String, Object> properties)
+    private static abstract class AbstractServiceFactory implements ServiceFactory
     {
+        private int counter;
+        private Object service;
 
-        // ensure properties
-        if (properties == null)
+        public AbstractServiceFactory(BundleContext context, Dictionary<String, Object> properties,
+            String... serviceNames)
         {
-            properties = new Hashtable<String, Object>();
+            // ensure properties
+            if (properties == null)
+            {
+                properties = new Hashtable<String, Object>();
+            }
+
+            // default settings
+            properties.put(Constants.SERVICE_DESCRIPTION, "Memory Usage (" + serviceNames[0] + ")");
+            properties.put(Constants.SERVICE_VENDOR, "Apache Software Foundation");
+
+            context.registerService(serviceNames, this, properties);
         }
 
-        // default settings
-        properties.put(Constants.SERVICE_DESCRIPTION, "Memory Usage (" + serviceNames[0] + ")");
-        properties.put(Constants.SERVICE_VENDOR, "Apache Software Foundation");
+        public synchronized void ungetService(Bundle bundle, ServiceRegistration registration, Object service)
+        {
+            counter--;
+            if (counter <= 0)
+            {
+                service = null;
+            }
+        }
 
-        context.registerService(serviceNames, service, properties);
+        public synchronized Object getService(Bundle bundle, ServiceRegistration registration)
+        {
+            counter++;
+            if (service == null)
+            {
+                service = createObject();
+            }
+            return service;
+        }
+
+        protected abstract Object createObject();
     }
-
 }
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsagePanel.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsagePanel.java
index 88e7258..8af03ca 100644
--- a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsagePanel.java
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsagePanel.java
@@ -41,22 +41,18 @@
 import org.apache.felix.webconsole.DefaultVariableResolver;
 import org.apache.felix.webconsole.WebConsoleUtil;
 import org.osgi.framework.BundleContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.osgi.service.log.LogService;
 
 @SuppressWarnings("serial")
 class MemoryUsagePanel extends AbstractWebConsolePlugin implements ConfigurationPrinter, AttachmentProvider
 {
 
-    /** default log */
-    private final Logger log = LoggerFactory.getLogger(getClass());
-
     private final MemoryUsageSupport support;
 
-    MemoryUsagePanel(final BundleContext bundleContext, final MemoryUsageSupport support)
+    MemoryUsagePanel(final MemoryUsageSupport support)
     {
         this.support = support;
-        activate(bundleContext);
+        activate(support.getBundleContext());
     }
 
     // ---------- AbstractWebConsolePlugin
@@ -198,7 +194,7 @@
                 {
                     resp.getWriter().print(
                         "Failed dumping the heap, JVM does not provide known mechanism to create a Heap Dump");
-                    log.error("Heap Dump creation failed: JVM has no known Heap Dump API");
+                    support.log(LogService.LOG_ERROR, "Heap Dump creation failed: JVM has no known Heap Dump API");
                 }
             }
             else if ("gc".equals(command))
diff --git a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageSupport.java b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageSupport.java
index 705c419..5e65893 100644
--- a/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageSupport.java
+++ b/webconsole-plugins/memoryusage/src/main/java/org/apache/felix/webconsole/plugins/memoryusage/internal/MemoryUsageSupport.java
@@ -19,6 +19,7 @@
 package org.apache.felix.webconsole.plugins.memoryusage.internal;
 
 import java.io.File;
+import java.io.PrintStream;
 import java.lang.management.ManagementFactory;
 import java.lang.management.MemoryMXBean;
 import java.lang.management.MemoryNotificationInfo;
@@ -41,16 +42,20 @@
 import javax.management.ObjectName;
 
 import org.osgi.framework.BundleContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
 
-final class MemoryUsageSupport implements NotificationListener
+final class MemoryUsageSupport implements NotificationListener, ServiceListener
 {
 
     // This is the name of the HotSpot Diagnostic MBean
     private static final String HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic";
 
-    private final Logger log = LoggerFactory.getLogger(getClass());
+    // to get the LogService
+    private final BundleContext context;
 
     // the default dump location: the dumps folder in the bundle private data
     // or the current working directory
@@ -65,8 +70,28 @@
     // the actual threshold (configured or dynamically set in the console UI)
     private int threshold;
 
+    // log service
+    private ServiceReference logServiceReference;
+    private Object logService;
+
     MemoryUsageSupport(final BundleContext context)
     {
+        this.context = context;
+
+        // register for the log service
+        try
+        {
+            context.addServiceListener(this, "(objectclass=org.osgi.service.log.LogService)");
+            logServiceReference = context.getServiceReference("org.osgi.service.log.LogService");
+            if (logServiceReference != null)
+            {
+                logService = context.getService(logServiceReference);
+            }
+        }
+        catch (InvalidSyntaxException ise)
+        {
+            // TODO
+        }
 
         // the default dump location
         String propDumps = context.getProperty(MemoryUsageConstants.PROP_DUMP_LOCATION);
@@ -139,6 +164,19 @@
         {
             // don't care
         }
+
+        context.removeServiceListener(this);
+        if (logServiceReference != null)
+        {
+            context.ungetService(logServiceReference);
+            logServiceReference = null;
+            logService = null;
+        }
+    }
+
+    public BundleContext getBundleContext()
+    {
+        return context;
     }
 
     /**
@@ -180,8 +218,9 @@
             }
             this.threshold = percentage;
 
-            log.info("Setting Automatic Memory Dump Threshold to {}% for pools {}", threshold, thresholdPools);
-            log.info("Automatic Memory Dump cannot be set for pools {}", noThresholdPools);
+            log(LogService.LOG_INFO, "Setting Automatic Memory Dump Threshold to %d%% for pools %s", threshold,
+                thresholdPools);
+            log(LogService.LOG_INFO, "Automatic Memory Dump cannot be set for pools %s", noThresholdPools);
         }
         else
         {
@@ -351,7 +390,7 @@
             this.dumpLocation = new File(dumpLocation).getAbsoluteFile();
         }
 
-        log.info("Storing Memory Dumps in {}", this.dumpLocation);
+        log(LogService.LOG_INFO, "Storing Memory Dumps in %s", this.dumpLocation);
     }
 
     final File getDumpLocation()
@@ -463,15 +502,16 @@
         String notifType = notification.getType();
         if (notifType.equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED))
         {
-            log.warn("Received Memory Threshold Exceed Notification, dumping Heap");
+            log(LogService.LOG_WARNING, "Received Memory Threshold Exceed Notification, dumping Heap");
             try
             {
                 File file = dumpHeap(null, true);
-                log.warn("Heap dumped to " + file);
+                log(LogService.LOG_WARNING, "Heap dumped to " + file);
             }
             catch (NoSuchElementException e)
             {
-                log.error("Failed dumping the heap, JVM does not provide known mechanism to create a Heap Dump");
+                log(LogService.LOG_ERROR,
+                    "Failed dumping the heap, JVM does not provide known mechanism to create a Heap Dump");
             }
         }
     }
@@ -507,12 +547,12 @@
                 { tmpFile.getAbsolutePath(), live }, new String[]
                 { String.class.getName(), boolean.class.getName() });
 
-            log.debug("dumpSunMBean: Dumped Heap to {} using Sun HotSpot MBean", tmpFile);
+            log(LogService.LOG_DEBUG, "dumpSunMBean: Dumped Heap to %s using Sun HotSpot MBean", tmpFile);
             return tmpFile;
         }
         catch (Throwable t)
         {
-            log.debug("dumpSunMBean: Dump by Sun HotSpot MBean not working", t);
+            log(LogService.LOG_DEBUG, "dumpSunMBean: Dump by Sun HotSpot MBean not working", t);
             tmpFile.delete();
         }
 
@@ -522,9 +562,7 @@
     /**
      * @param name
      * @return
-     * @see http
-     *      ://publib.boulder.ibm.com/infocenter/javasdk/v5r0/index.jsp?topic
-     *      =/com.ibm.java.doc.diagnostics.50/diag/tools/heapdump_enable.html
+     * @see <a href="http://publib.boulder.ibm.com/infocenter/javasdk/v5r0/index.jsp?topic=/com.ibm.java.doc.diagnostics.50/diag/tools/heapdump_enable.html">Getting Heapdumps</a>
      */
     private File dumpIbmDump(String name)
     {
@@ -554,27 +592,87 @@
                         File target = new File(dumpLocation, name);
                         file.renameTo(target);
 
-                        log.debug("dumpSunMBean: Dumped Heap to {} using IBM Dump.HeapDump()", target);
+                        log(LogService.LOG_DEBUG, "dumpSunMBean: Dumped Heap to %s using IBM Dump.HeapDump()", target);
                         return target;
                     }
                 }
 
-                log.debug("dumpIbmDump: None of {} files '{}' is younger than {}", new Object[]
-                    { files.length, dir, minFileTime });
+                log(LogService.LOG_DEBUG, "dumpIbmDump: None of %d files '%s' is younger than %d", files.length, dir,
+                    minFileTime);
             }
             else
             {
-                log.debug("dumpIbmDump: Hmm '{}' does not seem to be a directory; isdir={} ??", dir, dir.isDirectory());
+                log(LogService.LOG_DEBUG, "dumpIbmDump: Hmm '%s' does not seem to be a directory; isdir=%b ??", dir,
+                    dir.isDirectory());
             }
 
-            log.warn("dumpIbmDump: Heap Dump has been created but cannot be located");
+            log(LogService.LOG_WARNING, "dumpIbmDump: Heap Dump has been created but cannot be located");
             return dumpLocation;
         }
         catch (Throwable t)
         {
-            log.debug("dumpIbmDump: Dump by IBM Dump class not working", t);
+            log(LogService.LOG_DEBUG, "dumpIbmDump: Dump by IBM Dump class not working", t);
         }
 
         return null;
     }
+
+    // ---------- Logging support
+
+    public void serviceChanged(ServiceEvent event)
+    {
+        if (event.getType() == ServiceEvent.REGISTERED && logServiceReference == null)
+        {
+            logServiceReference = event.getServiceReference();
+            logService = context.getService(event.getServiceReference());
+        }
+        else if (event.getType() == ServiceEvent.UNREGISTERING && logServiceReference == event.getServiceReference())
+        {
+            logServiceReference = null;
+            logService = null;
+            context.ungetService(event.getServiceReference());
+        }
+    }
+
+    void log(int level, String format, Object... args)
+    {
+        log(level, null, format, args);
+    }
+
+    void log(int level, Throwable t, String format, Object... args)
+    {
+        Object logService = this.logService;
+        final String message = String.format(format, args);
+        if (logService != null)
+        {
+            ((LogService) logService).log(level, message, t);
+        }
+        else
+        {
+            PrintStream out = (level <= LogService.LOG_ERROR) ? System.err : System.out;
+            out.printf("%s: %s (%d): %s%n", toLevelString(level), context.getBundle().getSymbolicName(), context
+                .getBundle().getBundleId(), message);
+            if (t != null)
+            {
+                t.printStackTrace(out);
+            }
+        }
+    }
+
+    private String toLevelString(int level)
+    {
+        switch (level)
+        {
+            case LogService.LOG_DEBUG:
+                return "DEBUG";
+            case LogService.LOG_INFO:
+                return "INFO ";
+            case LogService.LOG_WARNING:
+                return "WARN ";
+            case LogService.LOG_ERROR:
+                return "ERROR";
+            default:
+                return "unknown(" + level + ")";
+        }
+    }
 }