SLING-2224 Create new LogService bundle

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1178187 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..45fc4a5
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,66 @@
+Apache Sling OSGi LogService Implementation
+
+
+=================================================
+Welcome to Sling - OSGi LogService Implementation
+=================================================
+
+The "logservice" project implements the OSGi LogService imple,entation ontop
+of the SLF4J logging API. This bundle should be installed as one of the first
+modules in the OSGi framework along with the SLF4J API and implementation and
+- provided the framework supports start levels - be set to start at start
+level 1. This ensures the Logging bundle is loaded as early as possible thus
+providing services to the framework and preparing logging.
+
+See the Apache Sling web site (http://sling.apache.org) for
+documentation and other information. You are welcome to join the
+Sling mailing lists (http://sling.apache.org/site/project-information.html)
+to discuss this component and to use the Sling issue tracker
+(http://issues.apache.org/jira/browse/SLING) to report issues or request
+new features.
+
+Apache Sling is a project of the Apache Software Foundation
+(http://www.apache.org).
+
+License (see also LICENSE)
+==========================
+
+Collective work: Copyright 2007 The Apache Software Foundation.
+
+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.
+
+Getting Started
+===============
+
+This component uses a Maven 2 (http://maven.apache.org/) build
+environment. It requires a Java 5 JDK (or higher) and Maven (http://maven.apache.org/)
+2.0.7 or later. We recommend to use the latest Maven version.
+
+If you have Maven 2 installed, you can compile and
+package the jar using the following command:
+
+    mvn package
+
+See the Maven 2 documentation for other build features.
+
+The latest source code for this component is available in the
+Subversion (http://subversion.tigris.org/) source repository of
+the Apache Software Foundation. If you have Subversion installed,
+you can checkout the latest source using the following command:
+
+    svn checkout http://svn.apache.org/repos/asf/sling/trunk/commons/log
+
+See the Subversion documentation for other source control features.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..de63690
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    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</artifactId>
+        <version>12</version>
+        <relativePath>../../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.sling.commons.logservice</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <name>Apache Sling OSGi LogService Implementation</name>
+    <description>
+        Implementation of the OSGi Compendium Log Service using SLF4J
+        as the actual logging backend.
+    </description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/commons/logservice</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/commons/logservice</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/bundles/commons/logservice</url>
+    </scm>
+    
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Activator>
+                            org.apache.sling.commons.logservice.internal.Activator
+                        </Bundle-Activator>
+                        <Bundle-DocURL>
+                            http://sling.apache.org/site/logging.html
+                        </Bundle-DocURL>
+                        <Export-Package>
+                            org.osgi.service.log
+                        </Export-Package>
+                        <Private-Package>
+                            org.apache.sling.commons.logservice.*,
+                        </Private-Package>
+                        <Import-Package>
+                            org.osgi.service.log;version="[$(version;==;$(@)),$(version;=+;$(@)))",
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <reporting>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                    <!-- No javadocs at all -->
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </reporting>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.5.0</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- OSGi Libraries not included here -->
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <version>4.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <version>4.2.0</version>
+        </dependency>
+        
+        <!-- testing -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/commons/logservice/internal/Activator.java b/src/main/java/org/apache/sling/commons/logservice/internal/Activator.java
new file mode 100644
index 0000000..60766b5
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/logservice/internal/Activator.java
@@ -0,0 +1,77 @@
+/*
+ * 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.sling.commons.logservice.internal;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.log.LogReaderService;
+import org.osgi.service.log.LogService;
+
+/**
+ * The <code>Activator</code> class is the <code>BundleActivator</code> for the
+ * log service bundle. This activator registers the <code>LogService</code> and
+ * <code>LogReaderService</code>.
+ */
+public class Activator implements BundleActivator {
+
+    private static final String VENDOR = "The Apache Software Foundation";
+
+    private LogSupport logSupport;
+
+    /**
+     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+     */
+    public void start(final BundleContext context) throws Exception {
+        logSupport = new LogSupport();
+        context.addBundleListener(logSupport);
+        context.addFrameworkListener(logSupport);
+        context.addServiceListener(logSupport);
+
+        LogServiceFactory lsf = new LogServiceFactory(logSupport);
+        Dictionary<String, String> props = new Hashtable<String, String>();
+        props.put(Constants.SERVICE_PID, lsf.getClass().getName());
+        props.put(Constants.SERVICE_DESCRIPTION,
+            "Apache Sling LogService implementation");
+        props.put(Constants.SERVICE_VENDOR, VENDOR);
+        context.registerService(LogService.class.getName(), lsf, props);
+
+        LogReaderServiceFactory lrsf = new LogReaderServiceFactory(logSupport);
+        props = new Hashtable<String, String>();
+        props.put(Constants.SERVICE_PID, lrsf.getClass().getName());
+        props.put(Constants.SERVICE_DESCRIPTION,
+            "Apache Sling LogReaderService implementation");
+        props.put(Constants.SERVICE_VENDOR, VENDOR);
+        context.registerService(LogReaderService.class.getName(), lrsf, props);
+    }
+
+    /**
+     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+     */
+    public void stop(final BundleContext context) throws Exception {
+        if (logSupport != null) {
+            context.removeBundleListener(logSupport);
+            context.removeFrameworkListener(logSupport);
+            context.removeServiceListener(logSupport);
+            logSupport.shutdown();
+            logSupport = null;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/commons/logservice/internal/LogEntryImpl.java b/src/main/java/org/apache/sling/commons/logservice/internal/LogEntryImpl.java
new file mode 100644
index 0000000..a317293
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/logservice/internal/LogEntryImpl.java
@@ -0,0 +1,71 @@
+/*
+ * 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.sling.commons.logservice.internal;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogEntry;
+
+public class LogEntryImpl implements LogEntry {
+
+    private final Bundle bundle;
+
+    private final ServiceReference serviceReference;
+
+    private final int level;
+
+    private final String message;
+
+    private final Throwable exception;
+
+    private final long time;
+
+    /* package */LogEntryImpl(Bundle bundle,
+            ServiceReference serviceReference, int level, String message,
+            Throwable exception) {
+        this.bundle = bundle;
+        this.serviceReference = serviceReference;
+        this.level = level;
+        this.message = message;
+        this.exception = exception;
+        this.time = System.currentTimeMillis();
+    }
+
+    public Bundle getBundle() {
+        return this.bundle;
+    }
+
+    public ServiceReference getServiceReference() {
+        return this.serviceReference;
+    }
+
+    public int getLevel() {
+        return this.level;
+    }
+
+    public String getMessage() {
+        return this.message;
+    }
+
+    public Throwable getException() {
+        return this.exception;
+    }
+
+    public long getTime() {
+        return this.time;
+    }
+}
diff --git a/src/main/java/org/apache/sling/commons/logservice/internal/LogReaderServiceFactory.java b/src/main/java/org/apache/sling/commons/logservice/internal/LogReaderServiceFactory.java
new file mode 100644
index 0000000..79791e4
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/logservice/internal/LogReaderServiceFactory.java
@@ -0,0 +1,83 @@
+/*
+ * 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.sling.commons.logservice.internal;
+
+import java.util.Enumeration;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.log.LogListener;
+import org.osgi.service.log.LogReaderService;
+
+/**
+ * The <code>LogReaderServiceFactory</code> is the service factory for
+ * <code>LogReader</code> service instances supplied to bundles.
+ * <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>
+ */
+public class LogReaderServiceFactory implements ServiceFactory {
+
+    private LogSupport logSupport;
+
+    LogReaderServiceFactory(LogSupport logSupport) {
+        this.logSupport = logSupport;
+    }
+
+    // ---------- ServiceFactory interface ------------------------------------
+
+    public Object getService(Bundle bundle, ServiceRegistration registration) {
+        return new LogReaderServiceImpl(bundle);
+    }
+
+    public void ungetService(Bundle bundle, ServiceRegistration registration,
+            Object service) {
+        ((LogReaderServiceImpl) service).shutdown();
+    }
+
+    // --------- internal LogReaderService implementation ----------------------
+
+    private class LogReaderServiceImpl implements LogReaderService {
+
+        private Bundle bundle;
+
+        /* package */LogReaderServiceImpl(Bundle bundle) {
+            this.bundle = bundle;
+        }
+
+        /* package */void shutdown() {
+            LogReaderServiceFactory.this.logSupport.removeLogListeners(this.bundle);
+        }
+
+        public void addLogListener(LogListener listener) {
+            LogReaderServiceFactory.this.logSupport.addLogListener(this.bundle,
+                listener);
+        }
+
+        public void removeLogListener(LogListener listener) {
+            LogReaderServiceFactory.this.logSupport.removeLogListener(listener);
+        }
+
+        @SuppressWarnings("rawtypes")
+        public Enumeration getLog() {
+            return LogReaderServiceFactory.this.logSupport.getLog();
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/commons/logservice/internal/LogServiceFactory.java b/src/main/java/org/apache/sling/commons/logservice/internal/LogServiceFactory.java
new file mode 100644
index 0000000..e62faf8
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/logservice/internal/LogServiceFactory.java
@@ -0,0 +1,97 @@
+/*
+ * 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.sling.commons.logservice.internal;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogService;
+
+/**
+ * The <code>LogServiceFactory</code> implements the OSGi Log Service
+ * specification and provides the functionality for the logging system. This
+ * service should be one of the first services loaded in the system.
+ */
+public class LogServiceFactory implements ServiceFactory {
+
+    private LogSupport logSupport;
+
+    /**
+     * Initializes the logging system with settings from some startup properties
+     * before the real configuration is read after ContentBus bootstrap.
+     * 
+     * @param properties The startup properties to initialize the logging system
+     *            with.
+     */
+    LogServiceFactory(LogSupport logSupport) {
+        this.logSupport = logSupport;
+
+    }
+
+    // ---------- ServiceFactory
+
+    public Object getService(Bundle bundle, ServiceRegistration registration) {
+        return new LogServiceImpl(bundle);
+    }
+
+    public void ungetService(Bundle bundle, ServiceRegistration registration,
+            Object service) {
+        // nothing to do currently
+    }
+
+    private class LogServiceImpl implements LogService {
+
+        private Bundle bundle;
+
+        /**
+         * Initializes the logging system with settings from some startup
+         * properties before the real configuration is read after ContentBus
+         * bootstrap.
+         * 
+         * @param properties The startup properties to initialize the logging
+         *            system with.
+         */
+        /* package */LogServiceImpl(Bundle bundle) {
+            this.bundle = bundle;
+        }
+
+        // ---------- LogService
+
+        public void log(int level, String message) {
+            this.log(null, level, message, null);
+        }
+
+        public void log(int level, String message, Throwable exception) {
+            this.log(null, level, message, exception);
+        }
+
+        public void log(ServiceReference sr, int level, String message) {
+            this.log(sr, level, message, null);
+        }
+
+        public void log(ServiceReference sr, int level, String message,
+                Throwable exception) {
+            // simply fire a log event
+            LogEntry entry = new LogEntryImpl(this.bundle, sr, level, message,
+                exception);
+            LogServiceFactory.this.logSupport.fireLogEvent(entry);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/commons/logservice/internal/LogSupport.java b/src/main/java/org/apache/sling/commons/logservice/internal/LogSupport.java
new file mode 100644
index 0000000..8a78926
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/logservice/internal/LogSupport.java
@@ -0,0 +1,586 @@
+/*
+ * 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.sling.commons.logservice.internal;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentConstants;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogListener;
+import org.osgi.service.log.LogService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>LogReaderServiceFactory</code> TODO
+ */
+public class LogSupport implements BundleListener, ServiceListener,
+        FrameworkListener {
+
+    /**
+     * The service property name of the component name (value is
+     * "component.name"). Note: We use a private constant here to not create a
+     * unneded dependency on the org.osgi.service.component package.
+     */
+    private static final String COMPONENT_NAME = ComponentConstants.COMPONENT_NAME; // "component.name";
+
+    /**
+     * 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() {
+
+        // 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 (listenersLock) {
+            LogListenerProxy llp = new LogListenerProxy(bundle, listener);
+            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 (listenersLock) {
+            // no listeners registered, nothing to do
+            if (listeners == null) {
+                return;
+            }
+
+            // listener is not registered, nothing to do
+            int idx = getListener(listener);
+            if (idx < 0) {
+                return;
+            }
+
+            LogListenerProxy[] newListeners = new LogListenerProxy[listeners.length - 1];
+            if (idx > 0) {
+                System.arraycopy(listeners, 0, newListeners, 0, idx);
+            }
+            if (idx < listeners.length) {
+                System.arraycopy(listeners, idx + 1, newListeners, 0,
+                    newListeners.length - idx);
+            }
+            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 = getListeners();
+        if (current == null) {
+            return;
+        }
+
+        // check for listeners by bundle
+        for (int i = 0; i < current.length; i++) {
+            if (current[i].hasBundle(bundle)) {
+                removeLogListener(current[i]);
+            }
+        }
+    }
+
+    private int getListener(LogListener listener) {
+        if (listeners != null) {
+            for (int i = 0; i < listeners.length; i++) {
+                if (listeners[i].isSame(listener)) {
+                    return i;
+                }
+            }
+        }
+
+        // fall back to not found
+        return -1;
+    }
+
+    /**
+     * Returns the currently registered LogListeners
+     */
+    private LogListenerProxy[] getListeners() {
+        synchronized (listenersLock) {
+            return listeners;
+        }
+    }
+
+    /**
+     * 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 -------------------------------------------
+
+    /**
+     * 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) {
+
+        // actually log it to SLF4J
+        logOut(logEntry);
+
+        // 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()) {
+            case BundleEvent.INSTALLED:
+                message = "BundleEvent INSTALLED";
+                break;
+            case BundleEvent.STARTED:
+                message = "BundleEvent STARTED";
+                break;
+            case BundleEvent.STOPPED:
+                // this is special, as we have to fix the listener list for
+                // stopped bundles
+                removeLogListeners(event.getBundle());
+                message = "BundleEvent STOPPED";
+                break;
+            case BundleEvent.UPDATED:
+                message = "BundleEvent UPDATED";
+                break;
+            case BundleEvent.UNINSTALLED:
+                message = "BundleEvent UNINSTALLED";
+                break;
+            case BundleEvent.RESOLVED:
+                message = "BundleEvent RESOLVED";
+                break;
+            case BundleEvent.UNRESOLVED:
+                message = "BundleEvent UNRESOLVED";
+                break;
+            default:
+                message = "BundleEvent " + event.getType();
+        }
+
+        LogEntry entry = new LogEntryImpl(event.getBundle(), null,
+            LogService.LOG_INFO, message, null);
+        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;
+        switch (event.getType()) {
+            case ServiceEvent.REGISTERED:
+                message = "ServiceEvent REGISTERED";
+                break;
+            case ServiceEvent.MODIFIED:
+                message = "ServiceEvent MODIFIED";
+                level = LogService.LOG_DEBUG;
+                break;
+            case ServiceEvent.UNREGISTERING:
+                message = "ServiceEvent UNREGISTERING";
+                break;
+            default:
+                message = "ServiceEvent " + event.getType();
+        }
+
+        String s = (event.getServiceReference().getBundle() == null)
+                ? null
+                : "Bundle " + event.getServiceReference().getBundle();
+        s = (s == null) ? message : s + " " + message;
+
+        LogEntry entry = new LogEntryImpl(
+            event.getServiceReference().getBundle(),
+            event.getServiceReference(), level, message, null);
+        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;
+        Throwable exception = event.getThrowable();
+        switch (event.getType()) {
+            case FrameworkEvent.STARTED:
+                message = "FrameworkEvent STARTED";
+                break;
+            case FrameworkEvent.ERROR:
+                message = "FrameworkEvent ERROR";
+
+                // special precaution for Felix.loadBundleClass event overkill
+                // FIXME: actually, the error is ok, if the bundle failed to
+                // resolve
+                if (exception instanceof BundleException) {
+                    StackTraceElement[] ste = exception.getStackTrace();
+                    if (ste != null && ste.length > 0
+                        && "loadBundleClass".equals(ste[0].getMethodName())) {
+                        message += ": Class " + exception.getMessage()
+                            + " not found";
+                        if (event.getBundle() != null) {
+                            message += " in bundle "
+                                + event.getBundle().getSymbolicName() + " ("
+                                + event.getBundle().getBundleId() + ")";
+                        }
+                        level = LogService.LOG_INFO;
+                        exception = null; // don't care for a stack trace here
+                        break;
+                    }
+                }
+
+                level = LogService.LOG_ERROR;
+                break;
+            case FrameworkEvent.PACKAGES_REFRESHED:
+                message = "FrameworkEvent PACKAGES REFRESHED";
+                break;
+            case FrameworkEvent.STARTLEVEL_CHANGED:
+                message = "FrameworkEvent STARTLEVEL CHANGED";
+                break;
+            case FrameworkEvent.WARNING:
+                message = "FrameworkEvent WARNING";
+                break;
+            case FrameworkEvent.INFO:
+                message = "FrameworkEvent INFO";
+                break;
+            default:
+                message = "FrameworkEvent " + event.getType();
+        }
+
+        String s = (event.getBundle() == null) ? null : "Bundle "
+            + event.getBundle();
+        s = (s == null) ? message : s + " " + message;
+
+        LogEntry entry = new LogEntryImpl(event.getBundle(), null, level,
+            message, exception);
+        fireLogEvent(entry);
+    }
+
+    // ---------- Effective logging --------------------------------------------
+
+    /**
+     * 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 = 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();
+                }
+
+                // if the bundle also has no location, use the bundle Id
+                if (name == null) {
+                    name = String.valueOf(bundle.getBundleId());
+                }
+            }
+
+            log = LoggerFactory.getLogger(name);
+            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 = getLogger(logEntry.getBundle());
+
+        StringBuffer msg = new StringBuffer();
+
+        ServiceReference sr = logEntry.getServiceReference();
+        if (sr != null) {
+            msg.append("Service [");
+            if (sr.getProperty(Constants.SERVICE_PID) != null) {
+                msg.append(sr.getProperty(Constants.SERVICE_PID)).append(',');
+            } else if (sr.getProperty(COMPONENT_NAME) != null) {
+                msg.append(sr.getProperty(COMPONENT_NAME)).append(',');
+            } else if (sr.getProperty(Constants.SERVICE_DESCRIPTION) != null) {
+                msg.append(sr.getProperty(Constants.SERVICE_DESCRIPTION)).append(
+                    ',');
+            }
+            msg.append(sr.getProperty(Constants.SERVICE_ID)).append("] ");
+        }
+
+        if (logEntry.getMessage() != null) {
+            msg.append(logEntry.getMessage());
+        }
+
+        Throwable exception = logEntry.getException();
+        if (exception != null) {
+            msg.append(" (").append(exception).append(')');
+        }
+
+        String message = msg.toString();
+        switch (logEntry.getLevel()) {
+            case LogService.LOG_DEBUG:
+                log.debug(message, exception);
+                break;
+            case LogService.LOG_INFO:
+                log.info(message, exception);
+                break;
+            case LogService.LOG_WARNING:
+                log.warn(message, exception);
+                break;
+            case LogService.LOG_ERROR:
+                log.error(message, exception);
+                break;
+            default:
+                if (logEntry.getLevel() > LogService.LOG_DEBUG) {
+                    log.trace(message, exception);
+                } else if (logEntry.getLevel() < LogService.LOG_ERROR) {
+                    log.error(message, exception);
+                }
+                break;
+        }
+    }
+
+    // ---------- 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
+            | Bundle.STOPPING;
+
+        private final Bundle bundle;
+
+        private final LogListener delegatee;
+
+        public LogListenerProxy(Bundle bundle, LogListener delegatee) {
+            this.bundle = bundle;
+            this.delegatee = delegatee;
+        }
+
+        public void logged(LogEntry entry) {
+            if ((bundle.getState() & runningBundle) != 0) {
+                delegatee.logged(entry);
+            }
+        }
+
+        /* package */boolean isSame(LogListener listener) {
+            return listener == delegatee || listener == this;
+        }
+
+        /* package */boolean hasBundle(Bundle bundle) {
+            return this.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 ??
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}