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 ??
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}