incubator-sling-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From fmesc...@apache.org
Subject svn commit: r692355 - /incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/LogSupport.java
Date Fri, 05 Sep 2008 07:08:13 GMT
Author: fmeschbe
Date: Fri Sep  5 00:08:13 2008
New Revision: 692355

URL: http://svn.apache.org/viewvc?rev=692355&view=rev
Log:
SLING-643 Implement LogEvent thread and add JavaDoc and some small reformatting

Modified:
    incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/LogSupport.java

Modified: incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/LogSupport.java
URL: http://svn.apache.org/viewvc/incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/LogSupport.java?rev=692355&r1=692354&r2=692355&view=diff
==============================================================================
--- incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/LogSupport.java
(original)
+++ incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/LogSupport.java
Fri Sep  5 00:08:13 2008
@@ -16,10 +16,13 @@
  */
 package org.apache.sling.commons.log;
 
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
 
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleEvent;
@@ -39,12 +42,6 @@
 
 /**
  * The <code>LogReaderServiceFactory</code> TODO
- * <p>
- * <blockquote> When a bundle which registers a LogListener object is stopped or
- * otherwise releases the Log Reader Service, the Log Reader Service must remove
- * all of the bundle's listeners.</blockquote>
- * <p>
- * TODO: To support configuration, we will implement ManagedService here !
  */
 public class LogSupport implements BundleListener, ServiceListener,
         FrameworkListener {
@@ -56,64 +53,103 @@
      */
     private static final String COMPONENT_NAME = "component.name";
 
-    private final Object lock = new Object();
+    /**
+     * The empty enumeration currently returned on the {@link #getLog()} call
+     * because we do not currently record the log events.
+     */
+    private final Enumeration<?> EMPTY = Collections.enumeration(Collections.emptyList());
 
+    // The registered LogListeners
     private LogListenerProxy[] listeners;
 
+    // The lock used to guard concurrent access to the listeners array
+    private final Object listenersLock = new Object();
+
+    // The loggers by bundle id used for logging messages originated from
+    // specific bundles
+    private Map<Long, Logger> loggers = new HashMap<Long, Logger>();
+
+    // the worker thread actually sending LogEvents to LogListeners
+    private LogEntryDispatcher logEntryDispatcher;
+
     /* package */LogSupport() {
+        logEntryDispatcher = new LogEntryDispatcher(this);
+        logEntryDispatcher.start();
     }
 
     /* package */void shutdown() {
-        synchronized (this.lock) {
-            this.listeners = null;
+
+        // terminate the dispatcher and wait for its termination here
+        logEntryDispatcher.terminate();
+        try {
+            logEntryDispatcher.join(1000L);
+        } catch (InterruptedException ie) {
+            // don't care
+        }
+
+        // drop all listeners
+        synchronized (listenersLock) {
+            listeners = null;
         }
     }
 
     // ---------- LogReaderService interface -----------------------------------
 
     /* package */void addLogListener(Bundle bundle, LogListener listener) {
-        synchronized (this.lock) {
+        synchronized (listenersLock) {
             LogListenerProxy llp = new LogListenerProxy(bundle, listener);
-            if (this.listeners == null) {
-                this.listeners = new LogListenerProxy[] { llp };
-            } else if (this.getListener(listener) < 0) {
-                LogListenerProxy[] newListeners = new LogListenerProxy[this.listeners.length
+ 1];
-                System.arraycopy(this.listeners, 0, newListeners, 0,
-                    this.listeners.length);
-                newListeners[this.listeners.length] = llp;
-                this.listeners = newListeners;
+            if (listeners == null) {
+                listeners = new LogListenerProxy[] { llp };
+            } else if (getListener(listener) < 0) {
+                LogListenerProxy[] newListeners = new LogListenerProxy[listeners.length +
1];
+                System.arraycopy(listeners, 0, newListeners, 0,
+                    listeners.length);
+                newListeners[listeners.length] = llp;
+                listeners = newListeners;
             }
         }
     }
 
     /* package */void removeLogListener(LogListener listener) {
-        synchronized (this.lock) {
+        synchronized (listenersLock) {
             // no listeners registered, nothing to do
-            if (this.listeners == null) {
+            if (listeners == null) {
                 return;
             }
 
             // listener is not registered, nothing to do
-            int idx = this.getListener(listener);
+            int idx = getListener(listener);
             if (idx < 0) {
                 return;
             }
 
-            LogListenerProxy[] newListeners = new LogListenerProxy[this.listeners.length
- 1];
+            LogListenerProxy[] newListeners = new LogListenerProxy[listeners.length - 1];
             if (idx > 0) {
-                System.arraycopy(this.listeners, 0, newListeners, 0, idx);
+                System.arraycopy(listeners, 0, newListeners, 0, idx);
             }
-            if (idx < this.listeners.length) {
-                System.arraycopy(this.listeners, idx + 1, newListeners, 0,
+            if (idx < listeners.length) {
+                System.arraycopy(listeners, idx + 1, newListeners, 0,
                     newListeners.length - idx);
             }
-            this.listeners = newListeners;
+            listeners = newListeners;
         }
     }
 
+    /**
+     * Removes all registered LogListeners belonging to the given bundle. This
+     * is the task required by the specification from a Log Service
+     * implemenation:
+     * <p>
+     * <blockquote> When a bundle which registers a LogListener object is
+     * stopped or otherwise releases the Log Reader Service, the Log Reader
+     * Service must remove all of the bundle's listeners.</blockquote>
+     * <p>
+     * 
+     * @param bundle The bundle whose listeners are to be removed.
+     */
     /* package */void removeLogListeners(Bundle bundle) {
         // grab an immediate copy of the array
-        LogListenerProxy[] current = this.getListeners();
+        LogListenerProxy[] current = getListeners();
         if (current == null) {
             return;
         }
@@ -121,15 +157,15 @@
         // check for listeners by bundle
         for (int i = 0; i < current.length; i++) {
             if (current[i].hasBundle(bundle)) {
-                this.removeLogListener(current[i]);
+                removeLogListener(current[i]);
             }
         }
     }
 
     private int getListener(LogListener listener) {
-        if (this.listeners != null) {
-            for (int i = 0; i < this.listeners.length; i++) {
-                if (this.listeners[i].isSame(listener)) {
+        if (listeners != null) {
+            for (int i = 0; i < listeners.length; i++) {
+                if (listeners[i].isSame(listener)) {
                     return i;
                 }
             }
@@ -139,47 +175,45 @@
         return -1;
     }
 
+    /**
+     * Returns the currently registered LogListeners
+     */
     private LogListenerProxy[] getListeners() {
-        synchronized (this.lock) {
-            return this.listeners;
+        synchronized (listenersLock) {
+            return listeners;
         }
     }
 
-    @SuppressWarnings("unchecked")
-    private final Enumeration EMPTY = new Enumeration() {
-        public boolean hasMoreElements() {
-            return false;
-        }
-
-        public Object nextElement() {
-            throw new NoSuchElementException("Empty");
-        }
-    };
-
-    /* package */@SuppressWarnings("unchecked")
-    Enumeration getLog() {
-        return this.EMPTY;
+    /**
+     * Returns an empty enumeration for now because we do not implement log
+     * entry recording for the moment.
+     */
+    Enumeration<?> getLog() {
+        return EMPTY;
     }
 
     // ---------- Firing a log event -------------------------------------------
 
-    /* package */void fireLogEvent(LogEntry entry) {
-        this.logOut(entry);
+    /**
+     * Logs the given log entry to the log file and enqueues for the dispatching
+     * to the registered LogListeners in a separate worker thread.
+     */
+    /* package */void fireLogEvent(LogEntry logEntry) {
 
-        // grab an immediate copy of the array
-        LogListener[] current = this.getListeners();
-        if (current == null) {
-            return;
-        }
+        // actually log it to SLF4J
+        logOut(logEntry);
 
-        // fire the events outside of the lock
-        for (int i = 0; i < current.length; i++) {
-            current[i].logged(entry);
-        }
+        // enqueue for asynchronous delivery
+        logEntryDispatcher.enqueLogEntry(logEntry);
     }
 
     // ---------- BundleListener -----------------------------------------------
 
+    /**
+     * Listens for Bundle events and logs the respective events according to the
+     * Log Service specification. In addition, all LogListener instances
+     * registered for stopped bundles are removed by this method.
+     */
     public void bundleChanged(BundleEvent event) {
         String message;
         switch (event.getType()) {
@@ -192,7 +226,7 @@
             case BundleEvent.STOPPED:
                 // this is special, as we have to fix the listener list for
                 // stopped bundles
-                this.removeLogListeners(event.getBundle());
+                removeLogListeners(event.getBundle());
                 message = "BundleEvent STOPPED";
                 break;
             case BundleEvent.UPDATED:
@@ -213,11 +247,15 @@
 
         LogEntry entry = new LogEntryImpl(event.getBundle(), null,
             LogService.LOG_INFO, message, null);
-        this.fireLogEvent(entry);
+        fireLogEvent(entry);
     }
 
     // ---------- ServiceListener ----------------------------------------------
 
+    /**
+     * Listens for Service events and logs the respective events according to
+     * the Log Service specification.
+     */
     public void serviceChanged(ServiceEvent event) {
         int level = LogService.LOG_INFO;
         String message;
@@ -244,11 +282,21 @@
         LogEntry entry = new LogEntryImpl(
             event.getServiceReference().getBundle(),
             event.getServiceReference(), level, message, null);
-        this.fireLogEvent(entry);
+        fireLogEvent(entry);
     }
 
     // ---------- FrameworkListener --------------------------------------------
 
+    /**
+     * Listens for Framework events and logs the respective events according to
+     * the Log Service specification.
+     * <p>
+     * In the case of a Framework ERROR which is a ClassNotFoundException for an
+     * unresolved bundle, the message is logged at INFO level instead of ERROR
+     * level as prescribed by the spec. This is because such a situation should
+     * not really result in a Framework ERROR but the Apache Felix framework has
+     * no means of controlling this at the moment (framework 1.0.4 release).
+     */
     public void frameworkEvent(FrameworkEvent event) {
         int level = LogService.LOG_INFO;
         String message;
@@ -304,52 +352,61 @@
 
         LogEntry entry = new LogEntryImpl(event.getBundle(), null, level,
             message, exception);
-        this.fireLogEvent(entry);
+        fireLogEvent(entry);
     }
 
     // ---------- Effective logging --------------------------------------------
 
-    private Map<Long, Logger> loggers = new HashMap<Long, Logger>();
-
+    /**
+     * Get a logger for messages orginating from the given bundle. If no bundle
+     * is specified, we use the system bundle logger.
+     * 
+     * @param bundle The bundle for which a logger is to be returned.
+     * @return The Logger for the bundle.
+     */
     private Logger getLogger(Bundle bundle) {
         Long bundleId = new Long((bundle == null) ? 0 : bundle.getBundleId());
-        Logger log = this.loggers.get(bundleId);
+        Logger log = loggers.get(bundleId);
         if (log == null) {
-            
+
             String name;
             if (bundle == null) {
-                
+
                 // if we have no bundle, use the system bundle's name
                 name = Constants.SYSTEM_BUNDLE_SYMBOLICNAME;
-                
+
             } else {
-                
+
                 // otherwise use the bundle symbolic name
                 name = bundle.getSymbolicName();
-                
+
                 // if the bundle has no symbolic name, use the location
                 if (name == null) {
-                    name = bundle.getLocation(); 
+                    name = bundle.getLocation();
                 }
-                
+
                 // if the bundle also has no location, use the bundle Id
                 if (name == null) {
                     name = String.valueOf(bundle.getBundleId());
                 }
             }
-            
+
             log = LoggerFactory.getLogger(name);
-            this.loggers.put(bundleId, log);
+            loggers.put(bundleId, log);
         }
         return log;
     }
 
+    /**
+     * Actually logs the given log entry to the logger for the bundle recorded
+     * in the log entry.
+     */
     private void logOut(LogEntry logEntry) {
         // /* package */ void logOut(Bundle bundle, ServiceReference sr, int
         // level, String message, Throwable exception) {
 
         // get the logger for the bundle
-        Logger log = this.getLogger(logEntry.getBundle());
+        Logger log = getLogger(logEntry.getBundle());
 
         StringBuffer msg = new StringBuffer();
 
@@ -402,6 +459,13 @@
 
     // ---------- internal class -----------------------------------------------
 
+    /**
+     * The <code>LogListenerProxy</code> class is a proxy to the actually
+     * registered <code>LogListener</code> which also records the bundle
+     * registering the listener. This allows for the removal of the log
+     * listeners registered by bundles which have not been removed before the
+     * bundle has been stopped.
+     */
     private static class LogListenerProxy implements LogListener {
 
         private final int runningBundle = Bundle.STARTING | Bundle.ACTIVE
@@ -417,17 +481,106 @@
         }
 
         public void logged(LogEntry entry) {
-            if ((this.bundle.getState() & this.runningBundle) != 0) {
-                this.delegatee.logged(entry);
+            if ((bundle.getState() & runningBundle) != 0) {
+                delegatee.logged(entry);
             }
         }
 
         /* package */boolean isSame(LogListener listener) {
-            return listener == this.delegatee || listener == this;
+            return listener == delegatee || listener == this;
         }
 
         /* package */boolean hasBundle(Bundle bundle) {
-            return this.bundle == bundle;
+            return bundle == bundle;
+        }
+    }
+
+    /**
+     * The <code>LogEntryDispatcher</code> implements the worker thread
+     * responsible for delivering log events to the log listeners.
+     */
+    private static class LogEntryDispatcher extends Thread {
+
+        // provides the actual log listeners on demand
+        private final LogSupport logSupport;
+
+        // the queue of log events to be dispatched
+        private final BlockingQueue<LogEntry> dispatchQueue;
+
+        // true as long as the thread is active
+        private boolean active;
+
+        LogEntryDispatcher(LogSupport logSupport) {
+            super("LogEntry Dispatcher");
+
+            this.logSupport = logSupport;
+            this.dispatchQueue = new LinkedBlockingQueue<LogEntry>();
+            this.active = true;
+        }
+
+        /**
+         * Add a log entry for dispatching.
+         */
+        void enqueLogEntry(LogEntry logEntry) {
+            dispatchQueue.offer(logEntry);
+        }
+
+        /**
+         * Get the next log entry for dispatching. This method blocks until an
+         * event is available or the thread is interrupted.
+         * 
+         * @return The next event to dispatch
+         * @throws InterruptedException If the thread has been interrupted while
+         *             waiting for a log event to dispatch.
+         */
+        LogEntry dequeueLogEntry() throws InterruptedException {
+            return dispatchQueue.take();
+        }
+
+        /**
+         * Terminates this work thread by resetting the active flag and
+         * interrupting itself such that the {@link #dequeueLogEntry()} is
+         * aborted for the thread to terminate.
+         */
+        void terminate() {
+            active = false;
+            interrupt();
+        }
+
+        /**
+         * Runs the actual log event dispatching. This method continues to get
+         * log events from the {@link #dequeueLogEntry()} method until the
+         * active flag is reset.
+         */
+        @Override
+        public void run() {
+            while (active) {
+
+                LogEntry logEntry = null;
+                try {
+                    logEntry = dequeueLogEntry();
+                } catch (InterruptedException ie) {
+                    // don't care, this is expected
+                }
+
+                // dispatch the log entry
+                if (logEntry != null) {
+
+                    // grab an immediate copy of the array
+                    LogListener[] logListeners = logSupport.getListeners();
+
+                    // fire the events outside of the listenersLock
+                    if (logListeners != null) {
+                        for (LogListener logListener : logListeners) {
+                            try {
+                                logListener.logged(logEntry);
+                            } catch (Throwable t) {
+                                // should we really care ??
+                            }
+                        }
+                    }
+                }
+            }
         }
     }
 }



Mime
View raw message