Modifies Felix' internal logger to track and use available log services
(FELIX-77). Also implements a new log level configuration property to
control how much information is logged by the framework.


git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@484660 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/Felix.java b/framework/src/main/java/org/apache/felix/framework/Felix.java
index 07f8c2c..6625b31 100644
--- a/framework/src/main/java/org/apache/felix/framework/Felix.java
+++ b/framework/src/main/java/org/apache/felix/framework/Felix.java
@@ -36,7 +36,7 @@
 public class Felix
 {
     // Logging related member variables.
-    private Logger m_logger = new Logger();
+    private Logger m_logger = null;
     // Config properties.
     private PropertyResolver m_config = new ConfigImpl();
     // Configuration properties passed into constructor.
@@ -131,6 +131,13 @@
      * The following configuration properties can be specified:
      * </p>
      * <ul>
+     *   <li><tt>felix.log.level</tt> - An integer value indicating the degree
+     *       of logging reported by the framework; the higher the value the more
+     *       logging is reported. If zero ('0') is specified, then logging is
+     *       turned off completely. The log levels match those specified in the
+     *       OSGi Log Service (i.e., 1 = error, 2 = warning, 3 = information,
+     *       and 4 = debug). The default value is 1.
+     *   </li>
      *   <li><tt>felix.auto.install.&lt;n&gt;</tt> - Space-delimited list of
      *       bundles to automatically install into start level <tt>n</tt> when
      *       the framework is started. Append a specific start level to this
@@ -223,6 +230,15 @@
         m_factory = null;
         m_configMutable = (configMutable == null)
             ? new MutablePropertyResolverImpl(new StringMap(false)) : configMutable;
+
+        // Create logger with appropriate log level. Even though the
+        // logger needs the system bundle's context for tracking log
+        // services, it is created now because it is needed before
+        // the system bundle is created. The system bundle's context
+        // will be set below after the system bundle is created.
+        m_logger = new Logger(m_configMutable.get(FelixConstants.LOG_LEVEL_PROP));
+
+        // Initialize other member variables.
         m_activeStartLevel = FelixConstants.FRAMEWORK_INACTIVE_STARTLEVEL;
         m_installRequestMap = new HashMap();
         m_installedBundleMap = new HashMap();
@@ -386,7 +402,11 @@
             throw new RuntimeException("Unable to start system bundle.");
         }
 
-        // Reload and cached bundles.
+        // Now that the system bundle is successfully created we can give
+        // its bundle context to the logger so that it can track log services.
+        m_logger.setSystemBundleContext(systembundle.getInfo().getContext());
+
+        // Now reload the cached bundles.
         BundleArchive[] archives = null;
 
         // First get cached bundle identifiers.
diff --git a/framework/src/main/java/org/apache/felix/framework/Logger.java b/framework/src/main/java/org/apache/felix/framework/Logger.java
index a0a8795..55baf22 100644
--- a/framework/src/main/java/org/apache/felix/framework/Logger.java
+++ b/framework/src/main/java/org/apache/felix/framework/Logger.java
@@ -18,128 +18,280 @@
  */
 package org.apache.felix.framework;
 
-import org.osgi.framework.BundleException;
-import org.osgi.framework.ServiceReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import org.osgi.framework.*;
 
 /**
  * <p>
  * This class mimics the standard OSGi <tt>LogService</tt> interface. An
- * instance of this class will be used by the framework for all logging. Currently,
- * the implementation of this class just sends log messages to standard output,
- * but in the future it will be modified to use a log service if one is
- * installed in the framework. To do so, it will need to use reflection to
- * call the log service methods, since it will not have access to the
- * <tt>LogService</tt> class.
+ * instance of this class is used by the framework for all logging. By default
+ * this class logs messages to standard out. The log level can be set to
+ * control the amount of logging performed, where a higher number results in
+ * more logging. A log level of zero turns off logging completely.
+ * </p>
+ * <p>
+ * The log levels match those specified in the OSGi Log Service (i.e., 1 = error,
+ * 2 = warning, 3 = information, and 4 = debug). The default value is 1.
+ * </p>
+ * <p>
+ * This class also uses the System Bundle's context to track log services
+ * and will use the highest ranking log service, if present, as a back end
+ * instead of printing to standard out. The class uses reflection to invoking
+ * the log service's method to avoid a dependency on the log interface.
  * </p>
 **/
-// TODO: Modify LogWrapper to get LogService service object and invoke with reflection.
-public class Logger
+public class Logger implements ServiceListener
 {
     public static final int LOG_ERROR = 1;
     public static final int LOG_WARNING = 2;
     public static final int LOG_INFO = 3;
     public static final int LOG_DEBUG = 4;
 
-    private Object m_logObj = null;
+    private int m_logLevel = 1;
+    private BundleContext m_context = null;
 
-    public Logger()
+    private final static int LOGGER_OBJECT_IDX = 0;
+    private final static int LOGGER_METHOD_IDX = 1;    
+    private ServiceReference m_logRef = null;
+    private Object[] m_logger = null;
+
+    public Logger(int logLevel)
     {
+        m_logLevel = logLevel;
+    }
+
+    public Logger(String s)
+    {
+        s = (s == null) ? "1" : s;
+        try
+        {
+            m_logLevel = Integer.parseInt(s);
+        }
+        catch (NumberFormatException ex)
+        {
+            // Default to 1.
+        }
+    }
+
+    protected void setSystemBundleContext(BundleContext context)
+    {
+        m_context = context;
+        startListeningForLogService();
     }
 
     public void log(int level, String msg)
     {
-        synchronized (this)
-        {
-            if (m_logObj != null)
-            {
-// Will use reflection.
-//                m_logObj.log(level, msg);
-            }
-            else
-            {
-                _log(null, level, msg, null);
-            }
-        }
+        _log(null, level, msg, null);
     }
 
     public void log(int level, String msg, Throwable ex)
     {
-        synchronized (this)
-        {
-            if (m_logObj != null)
-            {
-// Will use reflection.
-//                m_logObj.log(level, msg);
-            }
-            else
-            {
-                _log(null, level, msg, ex);
-            }
-        }
+        _log(null, level, msg, ex);
     }
 
     public void log(ServiceReference sr, int level, String msg)
     {
-        synchronized (this)
-        {
-            if (m_logObj != null)
-            {
-// Will use reflection.
-//                m_logObj.log(level, msg);
-            }
-            else
-            {
-                _log(sr, level, msg, null);
-            }
-        }
+        _log(sr, level, msg, null);
     }
 
     public void log(ServiceReference sr, int level, String msg, Throwable ex)
     {
-        synchronized (this)
+        _log(sr, level, msg, ex);
+    }
+
+    private void _log(ServiceReference sr, int level, String msg, Throwable throwable)
+    {
+        // Save our own copy just in case it changes. We could try to do
+        // more conservative locking here, but let's be optimistic.
+        Object[] logger = m_logger;
+
+        if (logger != null)
         {
-            if (m_logObj != null)
+            _logReflectively(logger, sr, level, msg, throwable);
+        }
+        else
+        {
+            String s = (sr == null) ? null : "SvcRef " + sr;
+            s = (s == null) ? msg : s + " " + msg;
+            s = (throwable == null) ? s : s + " (" + throwable + ")";
+            switch (level)
             {
-// Will use reflection.
-//                m_logObj.log(level, msg);
-            }
-            else
-            {
-                _log(sr, level, msg, ex);
+                case LOG_DEBUG:
+                    if (m_logLevel >= LOG_DEBUG)
+                    {
+                        System.out.println("DEBUG: " + s);
+                    }
+                    break;
+                case LOG_ERROR:
+                    if (m_logLevel >= LOG_ERROR)
+                    {
+                        System.out.println("ERROR: " + s);
+                        if (throwable != null)
+                        {
+                            if ((throwable instanceof BundleException) &&
+                                (((BundleException) throwable).getNestedException() != null))
+                            {
+                                throwable = ((BundleException) throwable).getNestedException();
+                            }
+                            throwable.printStackTrace();
+                        }
+                    }
+                    break;
+                case LOG_INFO:
+                    if (m_logLevel >= LOG_INFO)
+                    {
+                        System.out.println("INFO: " + s);
+                    }
+                    break;
+                case LOG_WARNING:
+                    if (m_logLevel >= LOG_WARNING)
+                    {
+                        System.out.println("WARNING: " + s);
+                    }
+                    break;
+                default:
+                    System.out.println("UNKNOWN[" + level + "]: " + s);
             }
         }
     }
-    
-    private void _log(ServiceReference sr, int level, String msg, Throwable ex)
+
+    private void _logReflectively(
+        Object[] logger, ServiceReference sr, int level, String msg, Throwable throwable)
     {
-        String s = (sr == null) ? null : "SvcRef " + sr;
-        s = (s == null) ? msg : s + " " + msg;
-        s = (ex == null) ? s : s + " (" + ex + ")";
-        switch (level)
+        if (logger != null)
         {
-            case LOG_DEBUG:
-                System.out.println("DEBUG: " + s);
-                break;
-            case LOG_ERROR:
-                System.out.println("ERROR: " + s);
-                if (ex != null)
-                {
-                    if ((ex instanceof BundleException) &&
-                        (((BundleException) ex).getNestedException() != null))
-                    {
-                        ex = ((BundleException) ex).getNestedException();
-                    }
-                    ex.printStackTrace();
-                }
-                break;
-            case LOG_INFO:
-                System.out.println("INFO: " + s);
-                break;
-            case LOG_WARNING:
-                System.out.println("WARNING: " + s);
-                break;
-            default:
-                System.out.println("UNKNOWN[" + level + "]: " + s);
+            Object[] params = {
+                sr, new Integer(level), msg, throwable
+            };
+            try
+            {
+                ((Method) logger[LOGGER_METHOD_IDX]).invoke(logger[LOGGER_OBJECT_IDX], params);
+            }
+            catch (InvocationTargetException ex)
+            {
+                System.err.println("Logger: " + ex);
+            }
+            catch (IllegalAccessException ex)
+            {
+                System.err.println("Logger: " + ex);
+            }
+        }
+    }
+
+    /**
+     * This method is called when the system bundle context is set;
+     * it simply adds a service listener so that the system bundle can track
+     * log services to be used as the back end of the logging mechanism. It also
+     * attempts to get an existing log service, if present, but in general
+     * there will never be a log service present since the system bundle is
+     * started before every other bundle.
+    **/
+    private synchronized void startListeningForLogService()
+    {
+        // Add a service listener for log services.
+        try
+        {
+            m_context.addServiceListener(
+                this, "(objectClass=org.osgi.service.log.LogService)");
+        }
+        catch (InvalidSyntaxException ex) {
+            // This will never happen since the filter is hard coded.
+        }
+        // Try to get an existing log service.
+        m_logRef = m_context.getServiceReference("org.osgi.service.log.LogService");
+        // Get the service object if available and set it in the logger.
+        if (m_logRef != null)
+        {
+            setLogger(m_context.getService(m_logRef));
+        }
+    }
+
+    /**
+     * This method implements the callback for the ServiceListener interface.
+     * It is public as a byproduct of implementing the interface and should
+     * not be called directly. This method tracks run-time changes to log
+     * service availability. If the log service being used by the framework's
+     * logging mechanism goes away, then this will try to find an alternative.
+     * If a higher ranking log service is registered, then this will switch
+     * to the higher ranking log service.
+    **/
+    public synchronized void serviceChanged(ServiceEvent event)
+    {
+        // If no logger is in use, then grab this one.
+        if ((event.getType() == ServiceEvent.REGISTERED) && (m_logRef == null))
+        {
+            m_logRef = event.getServiceReference();
+            // Get the service object and set it in the logger.
+            setLogger(m_context.getService(m_logRef));
+        }
+        // If a logger is in use, but this one has a higher ranking, then swap
+        // it for the existing logger.
+        else if ((event.getType() == ServiceEvent.REGISTERED) && (m_logRef != null))
+        {
+            ServiceReference ref =
+                m_context.getServiceReference("org.osgi.service.log.LogService");
+            if (!ref.equals(m_logRef))
+            {
+                m_context.ungetService(m_logRef);
+                m_logRef = ref;
+                setLogger(m_context.getService(m_logRef));
+            }
+            
+        }
+        // If the current logger is going away, release it and try to
+        // find another one.
+        else if ((event.getType() == ServiceEvent.UNREGISTERING) &&
+            m_logRef.equals(event.getServiceReference()))
+        {
+            // Unget the service object.
+            m_context.ungetService(m_logRef);
+            // Try to get an existing log service.
+            m_logRef = m_context.getServiceReference(
+                "org.osgi.service.log.LogService");
+            // Get the service object if available and set it in the logger.
+            if (m_logRef != null)
+            {
+                setLogger(m_context.getService(m_logRef));
+            }
+            else
+            {
+                setLogger(null);
+            }
+        }
+    }
+
+    /**
+     * This method sets the new log service object. It also caches the method to
+     * invoke. The service object and method are stored in array to optimistically
+     * eliminate the need to locking when logging.
+    **/
+    private void setLogger(Object logObj)
+    {
+        if (logObj == null)
+        {
+            m_logger = null;
+        }
+        else
+        {
+            Class[] formalParams = {
+                ServiceReference.class,
+                Integer.TYPE,
+                String.class,
+                Throwable.class
+            };
+
+            try
+            {
+                Method logMethod = logObj.getClass().getMethod("log", formalParams);
+                logMethod.setAccessible(true);
+                m_logger = new Object[] { logObj, logMethod };
+            }
+            catch (NoSuchMethodException ex)
+            {
+                System.err.println("Logger: " + ex);
+                m_logger = null;
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/util/FelixConstants.java b/framework/src/main/java/org/apache/felix/framework/util/FelixConstants.java
index a63d1ae..f0a0993 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/FelixConstants.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/FelixConstants.java
@@ -41,6 +41,7 @@
     public static final String BUNDLE_URL_PROTOCOL = "bundle";
 
     // Miscellaneous framework configuration property names.
+    public static final String LOG_LEVEL_PROP = "felix.log.level";
     public static final String AUTO_INSTALL_PROP = "felix.auto.install";
     public static final String AUTO_START_PROP = "felix.auto.start";
     public static final String EMBEDDED_EXECUTION_PROP = "felix.embedded.execution";
diff --git a/main/src/main/resources/config.properties b/main/src/main/resources/config.properties
index c9bae94..784967d 100644
--- a/main/src/main/resources/config.properties
+++ b/main/src/main/resources/config.properties
@@ -30,6 +30,7 @@
  file:bundle/org.apache.felix.shell-${pom.version}.jar \
  file:bundle/org.apache.felix.shell.tui-${pom.version}.jar \
  file:bundle/org.apache.felix.bundlerepository-${pom.version}.jar 
+felix.log.level=4
 felix.startlevel.framework=1
 felix.startlevel.bundle=1
 #framework.service.urlhandlers=false