Initial commit of the Log service implementation from Dale Peakall.
git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@467635 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/log/pom.xml b/log/pom.xml
new file mode 100644
index 0000000..0283c9e
--- /dev/null
+++ b/log/pom.xml
@@ -0,0 +1,53 @@
+<project>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix</artifactId>
+ <version>0.8.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>osgi-bundle</packaging>
+ <name>Apache Felix Log Service</name>
+ <artifactId>org.apache.felix.log</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>${pom.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>${pom.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix.plugins</groupId>
+ <artifactId>maven-osgi-plugin</artifactId>
+ <version>${pom.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <osgiManifest>
+ <bundleName>LogService</bundleName>
+ <bundleVendor>Apache Software Foundation</bundleVendor>
+ <bundleDescription>
+ This bundle provides an implementation of the OSGi R4 Log service.
+ </bundleDescription>
+ <bundleActivator>
+ org.apache.felix.log.impl.Activator
+ </bundleActivator>
+ <importPackage>
+ org.osgi.framework, org.osgi.service.log; version=1.3
+ </importPackage>
+ <exportService>
+ org.osgi.service.log.LogService, org.osgi.service.log.LogReaderService
+ </exportService>
+ </osgiManifest>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/log/src/main/java/org/apache/felix/log/impl/Activator.java b/log/src/main/java/org/apache/felix/log/impl/Activator.java
new file mode 100644
index 0000000..040c5da
--- /dev/null
+++ b/log/src/main/java/org/apache/felix/log/impl/Activator.java
@@ -0,0 +1,132 @@
+/*
+ * 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.felix.log.impl;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogReaderService;
+import org.osgi.service.log.LogService;
+
+/**
+ * The bundle activator for the OSGi log service (see section 101 of the service
+ * compendium).
+ * <p>
+ * The log service provides a general purpose message logger for the OSGi service
+ * platform. It consists of two services, one for logging information and another
+ * for retrieving current or previously recorded log information.
+ * <p>
+ * The service knows about the following properties which are read at bundle
+ * startup:
+ * <dl>
+ * <dt>org.apache.felix.log.maxSize</dt>
+ * <dd>Determines the maximum size of the log used to maintain historic
+ * log information. A value of -1 means the log has no maximum size;
+ * a value of 0 means that no historic log information will be maintained.
+ * The default value is 100.</dd>
+ *
+ * <dt>org.apache.felix.log.storeDebug</dt>
+ * <dd>Determines whether or not debug messages will be stored as part of
+ * the historic log information. The default value is false.</dd>
+ * </dl>
+ */
+public final class Activator implements BundleActivator {
+ /** The name of the property that defines the maximum size of the log. */
+ private static final String MAX_SIZE_PROPERTY = "org.apache.felix.log.maxSize";
+
+ /** The default value for the maximum size property. */
+ private static final int DEFAULT_MAX_SIZE = 100;
+
+ /** The name of the property that defines whether debug messages are stored. */
+ private static final String STORE_DEBUG_PROPERTY = "org.apache.felix.log.storeDebug";
+
+ /** The default value for the store debug property. */
+ private static final boolean DEFAULT_STORE_DEBUG = false;
+
+ /** The log. */
+ private Log m_log;
+
+ /**
+ * Returns the maximum size for the log.
+ * @param context the bundle context (used to look up a property)
+ * @return the maximum size for the log
+ */
+ private static int getMaxSize(final BundleContext context) {
+ int maxSize = DEFAULT_MAX_SIZE;
+
+ String maxSizePropValue = context.getProperty(MAX_SIZE_PROPERTY);
+ if (maxSizePropValue != null) {
+ try {
+ maxSize = Integer.parseInt(maxSizePropValue);
+ } catch (NumberFormatException e) {
+ // the property value is invalid - ignore
+ }
+ }
+
+ return maxSize;
+ }
+
+ /**
+ * Returns whether or not to store debug messages.
+ * @param context the bundle context (used to look up a property)
+ * @return whether or not to store debug messages
+ */
+ private static boolean getStoreDebug(final BundleContext context) {
+ boolean storeDebug = DEFAULT_STORE_DEBUG;
+
+ String storeDebugPropValue = context.getProperty(STORE_DEBUG_PROPERTY);
+ if (storeDebugPropValue != null) {
+ storeDebug = Boolean.valueOf(storeDebugPropValue).booleanValue();
+ }
+
+ return storeDebug;
+ }
+
+ /**
+ * Called by the OSGi framework when the bundle is started.
+ * Used to register the service implementations with the framework.
+ * @param context the bundle context
+ * @throws Exception if an error occurs
+ */
+ public void start(final BundleContext context) throws Exception {
+ // create the log instance
+ m_log = new Log(getMaxSize(context), getStoreDebug(context));
+
+ // register the listeners
+ context.addBundleListener(m_log);
+ context.addFrameworkListener(m_log);
+ context.addServiceListener(m_log);
+
+ // register the services with the framework
+ context.registerService(LogService.class.getName(),
+ new LogServiceFactory(m_log), null);
+
+ context.registerService(LogReaderService.class.getName(),
+ new LogReaderServiceFactory(m_log), null);
+ }
+
+ /**
+ * Called by the OSGi framework when the bundle is stopped.
+ * @param context the bundle context
+ * @throws Exception if an error occurs
+ */
+ public void stop(final BundleContext context) throws Exception {
+ // close the log
+ m_log.close();
+ }
+}
diff --git a/log/src/main/java/org/apache/felix/log/impl/Log.java b/log/src/main/java/org/apache/felix/log/impl/Log.java
new file mode 100644
index 0000000..7015c74
--- /dev/null
+++ b/log/src/main/java/org/apache/felix/log/impl/Log.java
@@ -0,0 +1,265 @@
+/*
+ * 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.felix.log.impl;
+
+import java.util.Enumeration;
+
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogListener;
+import org.osgi.service.log.LogService;
+
+/**
+ * Class used to represent the log. This class is used by the implementations
+ * of both the {@link org.osgi.service.log.LogService} interface and the
+ * {@link org.osgi.service.log.LogReaderService} to access the log.
+ * @see org.osgi.service.log.LogService
+ * @see org.osgi.service.log.LogReaderService
+ */
+final class Log implements BundleListener, FrameworkListener, ServiceListener {
+ /** The first log entry. */
+ private LogNode m_head;
+
+ /** The last log entry. */
+ private LogNode m_tail;
+
+ /** The log size. */
+ private int m_size;
+
+ /** The log listener thread. */
+ private LogListenerThread listenerThread;
+
+ /** The maximum size for the log. */
+ private final int m_maxSize;
+
+ /** Whether or not to store debug messages. */
+ private final boolean m_storeDebug;
+
+ /**
+ * Create a new instance.
+ * @param maxSize the maximum size for the log
+ * @param storeDebug whether or not to store debug messages
+ */
+ Log(final int maxSize, final boolean storeDebug) {
+ this.m_maxSize = maxSize;
+ this.m_storeDebug = storeDebug;
+ }
+
+ /**
+ * Close the log.
+ */
+ void close() {
+ if (listenerThread != null) {
+ listenerThread.shutdown();
+ listenerThread = null;
+ }
+
+ m_head = null;
+ m_tail = null;
+ m_size = 0;
+ }
+
+ /**
+ * Adds the entry to the log.
+ * @param entry the entry to add to the log
+ */
+ synchronized void addEntry(final LogEntry entry) {
+ if (m_maxSize != 0) {
+ // add the entry to the historic log
+ if (m_storeDebug || entry.getLevel() != LogService.LOG_DEBUG) {
+ // create a new node for the entry
+ LogNode node = new LogNode(entry);
+
+ // add to the front of the linked list
+ node.setNextNode(m_head);
+ if (m_head != null) {
+ m_head.setPreviousNode(node);
+ }
+
+ // and store the node
+ m_head = node;
+
+ // bump the size of the list
+ ++m_size;
+
+ // if no tail node - add the node to the tail
+ if (m_tail == null) {
+ m_tail = node;
+ }
+ }
+
+ // ensure the historic log doesn't grow beyond a certain size
+ if (m_maxSize != -1) {
+ if (m_size > m_maxSize) {
+ LogNode last = m_tail.getPreviousNode();
+ last.setNextNode(null);
+ m_tail = last;
+ --m_size;
+ }
+ }
+ }
+
+ // notify any listeners
+ if (listenerThread != null) {
+ listenerThread.addEntry(entry);
+ }
+ }
+
+ /**
+ * Add a listener to the log.
+ * @param listener the log listener to subscribe
+ */
+ synchronized void addListener(final LogListener listener) {
+ if (listenerThread == null) {
+ // create a new listener thread if necessary:
+ // the listener thread only runs if there are any registered listeners
+ listenerThread = new LogListenerThread();
+ listenerThread.start();
+ }
+ listenerThread.addListener(listener);
+ }
+
+ /**
+ * Remove a listener from the log.
+ * @param listener the log listener to unsubscribe
+ */
+ synchronized void removeListener(final LogListener listener) {
+ if (listenerThread != null) {
+ listenerThread.removeListener(listener);
+
+ // shutdown the thread if there are no listeners
+ if (listenerThread.getListenerCount() == 0) {
+ listenerThread.shutdown();
+ listenerThread = null;
+ }
+ }
+ }
+
+ /**
+ * Returns an enumeration of all the entries in the log most recent first.
+ * @return an enumeration of all the entries in the log most recent first
+ */
+ synchronized Enumeration getEntries() {
+ return new LogNodeEnumeration(m_head, m_tail);
+ }
+
+ /** The messages returned for the framework events. */
+ private static final String[] FRAMEWORK_EVENT_MESSAGES = {
+ "FrameworkEvent STARTED",
+ "FrameworkEvent ERROR",
+ "FrameworkEvent PACKAGES REFRESHED",
+ "FrameworkEvent STARTLEVEL CHANGED",
+ "FrameworkEvent WARNING",
+ "FrameworkEvent INFO"
+ };
+
+ /**
+ * Called when a framework event occurs.
+ * @param event the event that occured
+ */
+ public void frameworkEvent(final FrameworkEvent event) {
+ int eventType = event.getType();
+ String message = null;
+
+ for (int i = 0; message == null && i < FRAMEWORK_EVENT_MESSAGES.length; ++i) {
+ if (eventType >> i == 1) {
+ message = FRAMEWORK_EVENT_MESSAGES[i];
+ }
+ }
+
+ LogEntry entry = new LogEntryImpl(event.getBundle(),
+ null,
+ (eventType == FrameworkEvent.ERROR) ? LogService.LOG_ERROR : LogService.LOG_INFO,
+ message,
+ event.getThrowable());
+
+ addEntry(entry);
+ }
+
+ /** The messages returned for the bundle events. */
+ private static final String[] BUNDLE_EVENT_MESSAGES = {
+ "BundleEvent INSTALLED",
+ "BundleEvent STARTED",
+ "BundleEvent STOPPED",
+ "BundleEvent UPDATED",
+ "BundleEvent UNINSTALLED",
+ "BundleEvent RESOLVED",
+ "BundleEvent UNRESOLVED"
+ };
+
+ /**
+ * Called when a bundle event occurs.
+ * @param event the event that occured
+ */
+ public void bundleChanged(final BundleEvent event) {
+ int eventType = event.getType();
+ String message = null;
+
+ for (int i = 0; message == null && i < BUNDLE_EVENT_MESSAGES.length; ++i) {
+ if (eventType >> i == 1) {
+ message = BUNDLE_EVENT_MESSAGES[i];
+ }
+ }
+
+ if (message != null) {
+ LogEntry entry = new LogEntryImpl(event.getBundle(),
+ null,
+ LogService.LOG_INFO,
+ message,
+ null);
+
+ addEntry(entry);
+ }
+ }
+
+ /** The messages returned for the service events. */
+ private static final String[] SERVICE_EVENT_MESSAGES = {
+ "ServiceEvent REGISTERED",
+ "ServiceEvent MODIFIED",
+ "ServiceEvent UNREGISTERING"
+ };
+
+ /**
+ * Called when a service event occurs.
+ * @param event the event that occured
+ */
+ public void serviceChanged(final ServiceEvent event) {
+ int eventType = event.getType();
+ String message = null;
+
+ for (int i = 0; message == null && i < SERVICE_EVENT_MESSAGES.length; ++i) {
+ if (eventType >> i == 1) {
+ message = SERVICE_EVENT_MESSAGES[i];
+ }
+ }
+
+ LogEntry entry = new LogEntryImpl(event.getServiceReference().getBundle(),
+ event.getServiceReference(),
+ (eventType == ServiceEvent.MODIFIED) ? LogService.LOG_DEBUG : LogService.LOG_INFO,
+ message,
+ null);
+
+ addEntry(entry);
+ }
+}
diff --git a/log/src/main/java/org/apache/felix/log/impl/LogEntryImpl.java b/log/src/main/java/org/apache/felix/log/impl/LogEntryImpl.java
new file mode 100644
index 0000000..bdf36c3
--- /dev/null
+++ b/log/src/main/java/org/apache/felix/log/impl/LogEntryImpl.java
@@ -0,0 +1,148 @@
+/*
+ * 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.felix.log.impl;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogEntry;
+
+/**
+ * Implementation of the OSGi {@link LogEntry} interface. See section 101
+ * of the OSGi service compendium.
+ * <p>
+ * Provides methods to access the information contained in an individual Log
+ * Service log entry.
+ * <p>
+ * A LogEntry object may be acquired from the
+ * {@link org.osgi.service.log.LogReaderService#getLog()} method or by
+ * registering a {@link org.osgi.service.log.LogListener} object.
+ * @see org.osgi.service.log.LogReaderService#getLog()
+ * @see org.osgi.service.log.LogListener
+ */
+final class LogEntryImpl implements LogEntry {
+
+ /** The bundle that created the LogEntry object. */
+ private final Bundle m_bundle;
+
+ /** The exception associated with this LogEntry object. */
+ private final Throwable m_exception;
+
+ /** The severity level of this LogEntry object. */
+ private final int m_level;
+
+ /** The message associated with this LogEntry object. */
+ private final String m_message;
+
+ /** The service reference associated with this LogEntry object. */
+ private final ServiceReference m_serviceReference;
+
+ /** The system time in milliseconds when this LogEntry object was created. */
+ private final long m_time;
+
+ /**
+ * Create a new instance.
+ * @param bundle the bundle that created the LogEntry object
+ * @param sr the service reference to associate with this LogEntry object
+ * @param level the severity level for this LogEntry object
+ * @param message the message to associate with this LogEntry object
+ * @param exception the exception to associate with this LogEntry object
+ */
+ LogEntryImpl(final Bundle bundle,
+ final ServiceReference sr,
+ final int level,
+ final String message,
+ final Throwable exception) {
+ this.m_bundle = bundle;
+ this.m_exception = LogException.getException(exception);
+ this.m_level = level;
+ this.m_message = message;
+ this.m_serviceReference = sr;
+ this.m_time = System.currentTimeMillis();
+ }
+
+ /**
+ * Returns the bundle that created this LogEntry object.
+ * @return the bundle that created this LogEntry object;<code>null</code> if no
+ * bundle is associated with this LogEntry object
+ */
+ public Bundle getBundle() {
+ return m_bundle;
+ }
+
+ /**
+ * Returns the {@link ServiceReference} object for the service associated with
+ * this LogEntry object.
+ * @return the {@link ServiceReference} object for the service associated with
+ * this LogEntry object; <code>null</code> if no {@link ServiceReference} object
+ * was provided
+ */
+ public ServiceReference getServiceReference() {
+ return m_serviceReference;
+ }
+
+ /**
+ * Returns the severity level of this LogEntry object.
+ * <p>
+ * This is one of the severity levels defined by the
+ * {@link org.osgi.service.logLogService} interface.
+ * @return severity level of this LogEntry object.
+ * @see org.osgi.service.LogService#LOG_ERROR
+ * @see org.osgi.service.LogService#LOG_WARNING
+ * @see org.osgi.service.LogService#LOG_INFO
+ * @see org.osgi.service.LogService#LOG_DEBUG
+ */
+ public int getLevel() {
+ return m_level;
+ }
+
+ /**
+ * Returns the human readable message associated with this LogEntry object.
+ * @return a string containing the message associated with this LogEntry object
+ */
+ public String getMessage() {
+ return m_message;
+ }
+
+ /**
+ * Returns the exception object associated with this LogEntry object.
+ * <p>
+ * The returned exception may not be the original exception. To avoid
+ * references to a bundle defined exception class, thus preventing an
+ * uninstalled bundle from being garbage collected, this LogService will
+ * return an exception object of an implementation defined
+ * {@link Throwable} sub-class.
+ * This exception will maintain as much information as possible from the
+ * original exception object such as the message and stack trace.
+ * @return throwable object of the exception associated with this LogEntry;
+ * <code>null</code> if no exception is associated with this LogEntry object
+ */
+ public Throwable getException() {
+ return m_exception;
+ }
+
+ /**
+ * Returns the value of {@link System#currentTimeMillis()} at the time this
+ * LogEntry object was created.
+ * @return the system time in milliseconds when this LogEntry object was created
+ * @see System#currentTimeMillis()
+ */
+ public long getTime() {
+ return m_time;
+ }
+}
diff --git a/log/src/main/java/org/apache/felix/log/impl/LogException.java b/log/src/main/java/org/apache/felix/log/impl/LogException.java
new file mode 100644
index 0000000..29422d2
--- /dev/null
+++ b/log/src/main/java/org/apache/felix/log/impl/LogException.java
@@ -0,0 +1,99 @@
+/*
+ * 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.felix.log.impl;
+
+/**
+ * Implementation dependent exception class used to avoid references to any
+ * bundle defined exception class, which might prevent an uninstalled bundle
+ * from being garbage collected. This exception maintains the class of the
+ * original exception (as part of the message), the message (appended to the
+ * class name) and the stack trace of both the exception thrown and any
+ * embedded exceptions.
+ */
+final class LogException extends Exception {
+ /** The class name of the original exception. */
+ private final String m_className;
+
+ /** The message from the original exception. */
+ private final String m_message;
+
+ /** The localized message from the original exception. */
+ private final String m_localizedMessage;
+
+ /**
+ * Create a new instance.
+ * @param exception the original exception.
+ */
+ private LogException(final Throwable exception) {
+ m_className = exception.getClass().getName();
+ m_message = exception.getMessage();
+ m_localizedMessage = exception.getLocalizedMessage();
+ setStackTrace(exception.getStackTrace());
+
+ Throwable cause = exception.getCause();
+ if (cause != null) {
+ cause = getException(cause);
+ initCause(cause);
+ }
+ }
+
+ /**
+ * Returns the message associated with the exception. The message
+ * will be the class name of the original exception followed by the
+ * message of the original exception.
+ * @return the message associated with the exception
+ */
+ public String getMessage() {
+ return m_className + ": " + m_message;
+ }
+
+ /**
+ * Returns the localized message associated with the exception. The
+ * localized message will be the class name of the original exception
+ * followed by the localized message of the original exception.
+ * @return the localized message associated with the exception
+ */
+ public String getLocalizedMessage() {
+ return m_className + ": " + m_localizedMessage;
+ }
+
+ /** The prefix that identifies classes from the "java" namespace. */
+ private static final String JAVA_PACKAGE_PREFIX = "java.";
+
+ /**
+ * Returns the exception to store in the {@link LogEntry}.
+ * @param exception the exception that was originally thrown.
+ * @return the exception to store in the {@link LogEntry}
+ */
+ static Throwable getException(final Throwable exception) {
+ Throwable result = null;
+
+ if (exception != null) {
+ String className = exception.getClass().getName();
+ if (exception.getCause() == null
+ && className.startsWith(JAVA_PACKAGE_PREFIX)) {
+ result = exception;
+ } else {
+ result = new LogException(exception);
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/log/src/main/java/org/apache/felix/log/impl/LogListenerThread.java b/log/src/main/java/org/apache/felix/log/impl/LogListenerThread.java
new file mode 100644
index 0000000..9b14a07
--- /dev/null
+++ b/log/src/main/java/org/apache/felix/log/impl/LogListenerThread.java
@@ -0,0 +1,135 @@
+/*
+ * 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.felix.log.impl;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+import java.util.Vector;
+
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogListener;
+
+/**
+ * This class is responsible for asynchronously delivering log messages to
+ * any {@link LogListener} subscribers. A subscriber can be added using the
+ * {@link org.osgi.service.log.LogReaderService#addLogListener(LogListener)}
+ * method.
+ */
+final class LogListenerThread extends Thread {
+
+ /** Whether the thread is stopping or not. */
+ private boolean m_stopping = false;
+
+ /** The stack of entries waiting to be delivered to the log listeners. **/
+ private final Stack m_entriesToDeliver = new Stack();
+
+ /** The list of listeners. */
+ private final List m_listeners = new Vector();
+
+ /**
+ * Add an entry to the list of messages to deliver.
+ * @param entry the log entry to deliver
+ */
+ void addEntry(final LogEntry entry) {
+ synchronized (m_entriesToDeliver) {
+ m_entriesToDeliver.add(entry);
+ m_entriesToDeliver.notifyAll();
+ }
+ }
+
+ /**
+ * Add a listener to the list of listeners that are subscribed.
+ * @param listener the listener to add to the list of subscribed listeners
+ */
+ void addListener(final LogListener listener) {
+ synchronized (m_listeners) {
+ m_listeners.add(listener);
+ }
+ }
+
+ /**
+ * Remove a listener from the list of listeners that are subscribed.
+ * @param listener the listener to remove from the list of subscribed listeners
+ */
+ void removeListener(final LogListener listener) {
+ synchronized (m_listeners) {
+ m_listeners.remove(listener);
+ }
+ }
+
+ /**
+ * Returns the number of listeners that are currently registered.
+ * @return the number of listeners that are currently registered
+ */
+ int getListenerCount() {
+ return m_listeners.size();
+ }
+
+ /**
+ * Stop the thread. This will happen asynchronously.
+ */
+ void shutdown() {
+ m_stopping = true;
+
+ synchronized (m_entriesToDeliver) {
+ m_entriesToDeliver.notifyAll();
+ }
+ }
+
+ /**
+ * The main method of the thread: waits for new messages to be receieved
+ * and then delivers them to any registered log listeners.
+ */
+ public void run() {
+ boolean stop = false;
+
+ for (; !stop;) {
+ synchronized (m_entriesToDeliver) {
+ if (!m_entriesToDeliver.isEmpty()) {
+ LogEntry entry = (LogEntry) m_entriesToDeliver.pop();
+
+ synchronized (m_listeners) {
+ Iterator listenerIt = m_listeners.iterator();
+ while (listenerIt.hasNext()) {
+ try {
+ LogListener listener = (LogListener) listenerIt.next();
+ listener.logged(entry);
+ } catch (Throwable t) {
+ // catch and discard any exceptions thrown by the listener
+ }
+ }
+ }
+ }
+
+ if (m_entriesToDeliver.isEmpty()) {
+ try {
+ m_entriesToDeliver.wait();
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+ }
+
+ if (m_stopping) {
+ stop = true;
+ }
+ }
+ }
+}
diff --git a/log/src/main/java/org/apache/felix/log/impl/LogNode.java b/log/src/main/java/org/apache/felix/log/impl/LogNode.java
new file mode 100644
index 0000000..52176ec
--- /dev/null
+++ b/log/src/main/java/org/apache/felix/log/impl/LogNode.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.felix.log.impl;
+
+import org.osgi.service.log.LogEntry;
+
+/**
+ * The class used as a doubly linked list node in the log.
+ */
+final class LogNode {
+ /** The previous node. */
+ private LogNode m_previous;
+
+ /** The next node. */
+ private LogNode m_next;
+
+ /** The log entry. */
+ private final LogEntry m_entry;
+
+ /**
+ * Create a new instance.
+ * @param entry the log entry.
+ */
+ LogNode(final LogEntry entry) {
+ m_entry = entry;
+ }
+
+ /**
+ * Returns the associated entry.
+ * @return the associated entry
+ */
+ LogEntry getEntry() {
+ return m_entry;
+ }
+
+ /**
+ * Get the next node.
+ * @return the next node
+ */
+ LogNode getNextNode() {
+ return m_next;
+ }
+
+ /**
+ * Set the next node.
+ * @param next the next node
+ */
+ void setNextNode(final LogNode next) {
+ m_next = next;
+ }
+
+ /**
+ * Get the previous node.
+ * @return the previous node
+ */
+ LogNode getPreviousNode() {
+ return m_previous;
+ }
+
+ /**
+ * Set the previous node.
+ * @param previous the previous node
+ */
+ void setPreviousNode(final LogNode previous) {
+ m_previous = previous;
+ }
+}
diff --git a/log/src/main/java/org/apache/felix/log/impl/LogNodeEnumeration.java b/log/src/main/java/org/apache/felix/log/impl/LogNodeEnumeration.java
new file mode 100644
index 0000000..c8412e8
--- /dev/null
+++ b/log/src/main/java/org/apache/felix/log/impl/LogNodeEnumeration.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.felix.log.impl;
+
+import java.util.Enumeration;
+
+import org.osgi.service.log.LogEntry;
+
+/**
+ * Implementation of the {@link Enumeration} interface for a linked list of
+ * {@link LogNode} entries.
+ */
+final class LogNodeEnumeration implements Enumeration {
+ /** The next node. */
+ private LogNode m_next;
+
+ /** The last node. */
+ private final LogNode m_last;
+
+ /**
+ * Creates a new instance.
+ * @param start the first node to return
+ * @param end the last node to return
+ */
+ LogNodeEnumeration(final LogNode start, final LogNode end) {
+ m_next = start;
+ m_last = end;
+ }
+
+ /**
+ * Determines whether there are any more elements to return.
+ * @return <code>true</code> if there are more elements; <code>false</code> otherwise
+ */
+ public boolean hasMoreElements() {
+ return m_next != null;
+ }
+
+ /**
+ * Returns the current element and moves onto the next element.
+ * @return the current element
+ */
+ public Object nextElement() {
+ LogEntry result = null;
+
+ if (m_next == m_last) {
+ result = m_next.getEntry();
+ m_next = null;
+ } else if (m_next != null) {
+ result = m_next.getEntry();
+ m_next = m_next.getNextNode();
+ }
+
+ return result;
+ }
+}
diff --git a/log/src/main/java/org/apache/felix/log/impl/LogReaderServiceFactory.java b/log/src/main/java/org/apache/felix/log/impl/LogReaderServiceFactory.java
new file mode 100644
index 0000000..d05ca40
--- /dev/null
+++ b/log/src/main/java/org/apache/felix/log/impl/LogReaderServiceFactory.java
@@ -0,0 +1,64 @@
+/*
+ * 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.felix.log.impl;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * {@link ServiceFactory} implementation for {@link LogReaderService}. Associates
+ * an individual {@link LogReaderService} with a {@link Bundle}.
+ */
+final class LogReaderServiceFactory implements ServiceFactory {
+ /** The log to associate the service implementations with. */
+ private final Log m_log;
+
+ /**
+ * Create a new instance.
+ * @param log the log to associate the service implementations with.,
+ */
+ LogReaderServiceFactory(final Log log) {
+ m_log = log;
+ }
+
+ /**
+ * Get the service to use for the specified bundle.
+ * @param bundle the bundle requesting the service
+ * @param registration the service registration
+ * @return the log reader service implementation for the specified bundle
+ */
+ public Object getService(final Bundle bundle,
+ final ServiceRegistration registration) {
+ return new LogReaderServiceImpl(m_log);
+ }
+
+ /**
+ * Release the service previously obtained through
+ * {@link #getService(Bundle, ServiceRegistration)}.
+ * @param bundle the bundle that originally requested the service
+ * @param registration the service registration
+ * @param service the service to release
+ */
+ public void ungetService(final Bundle bundle,
+ final ServiceRegistration registration,
+ final Object service) {
+ ((LogReaderServiceImpl) service).removeAllLogListeners();
+ }
+}
diff --git a/log/src/main/java/org/apache/felix/log/impl/LogReaderServiceImpl.java b/log/src/main/java/org/apache/felix/log/impl/LogReaderServiceImpl.java
new file mode 100644
index 0000000..e94dc9b
--- /dev/null
+++ b/log/src/main/java/org/apache/felix/log/impl/LogReaderServiceImpl.java
@@ -0,0 +1,102 @@
+/*
+ * 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.felix.log.impl;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import org.osgi.service.log.LogListener;
+import org.osgi.service.log.LogReaderService;
+
+/**
+ * Implementation of the OSGi {@link LogReaderService} interface. See section 101
+ * of the OSGi service compendium.
+ * <p>
+ * The {@link LogReaderService} maintains a list of {@link org.osgi.service.log.LogEntry}
+ * objects called the <i>log</i>. The {@link LogReaderService} is a service that bundle
+ * developers can use to retrieve information contained in this log, and receive
+ * notifications about {@link org.osgi.service.log.LogEntry} objects when they are created
+ * through the {@link org.osgi.service.log.LogService}.
+ */
+final class LogReaderServiceImpl implements LogReaderService {
+
+ /** The log implementation. */
+ private final Log m_log;
+
+ /** The listeners associated with this service. */
+ private final List m_listeners = new Vector();
+
+ /**
+ * Create a new instance.
+ * @param log the log implementation
+ */
+ LogReaderServiceImpl(final Log log) {
+ this.m_log = log;
+ }
+
+ /**
+ * This method is used to subscribe to the Log Reader Service in order to receive
+ * log messages as they occur. Unlike the previously recorded log entries, all
+ * log messages must be sent to subscribers of the Log Reader Service as they are
+ * recorded.
+ * <p>
+ * A subscriber to the Log Reader Service must implement the {@link LogListener}
+ * interface.
+ * <p>
+ * After a subscription of the Log Reader Service has been started, the subscriber's
+ * {@link LogListener#logged(LogEntry)} method must be called with a {@link LogEntry}
+ * object for the message each time a message is logged.
+ * @param listener the listener object to subscribe
+ */
+ public synchronized void addLogListener(final LogListener listener) {
+ m_listeners.add(listener);
+ m_log.addListener(listener);
+ }
+
+ /**
+ * This method is used to unsubscribe from the Log Reader Service.
+ * @param listener the listener object to unsubscribe
+ */
+ public synchronized void removeLogListener(final LogListener listener) {
+ m_listeners.remove(listener);
+ m_log.removeListener(listener);
+ }
+
+ /**
+ * This method retrieves past log entries as an enumeration with the most recent
+ * entry first.
+ * @return an enumeration of the {@link LogEntry} objects that have been stored
+ */
+ public Enumeration getLog() {
+ return m_log.getEntries();
+ }
+
+ /**
+ * Remove all log listeners registered through this service.
+ */
+ synchronized void removeAllLogListeners() {
+ Iterator listenerIt = m_listeners.iterator();
+ while (listenerIt.hasNext()) {
+ LogListener listener = (LogListener) listenerIt.next();
+ m_log.removeListener(listener);
+ }
+ }
+}
diff --git a/log/src/main/java/org/apache/felix/log/impl/LogServiceFactory.java b/log/src/main/java/org/apache/felix/log/impl/LogServiceFactory.java
new file mode 100644
index 0000000..9e3a783
--- /dev/null
+++ b/log/src/main/java/org/apache/felix/log/impl/LogServiceFactory.java
@@ -0,0 +1,64 @@
+/*
+ * 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.felix.log.impl;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * {@link ServiceFactory} implementation for {@link LogService}. Associates
+ * an individual {@link LogService} with a {@link Bundle}.
+ */
+final class LogServiceFactory implements ServiceFactory {
+ /** The log to associate the service implementations with. */
+ private final Log m_log;
+
+ /**
+ * Create a new instance.
+ * @param log the log to associate the service implementations with.,
+ */
+ LogServiceFactory(final Log log) {
+ m_log = log;
+ }
+
+ /**
+ * Get the service to use for the specified bundle.
+ * @param bundle the bundle requesting the service
+ * @param registration the service registration
+ * @return the log service implementation for the specified bundle
+ */
+ public Object getService(final Bundle bundle,
+ final ServiceRegistration registration) {
+ return new LogServiceImpl(m_log, bundle);
+ }
+
+ /**
+ * Release the service previously obtained through
+ * {@link #getService(Bundle, ServiceRegistration)}.
+ * @param bundle the bundle that originally requested the service
+ * @param registration the service registration
+ * @param service the service to release
+ */
+ public void ungetService(final Bundle bundle,
+ final ServiceRegistration registration,
+ final Object service) {
+ // do nothing
+ }
+}
diff --git a/log/src/main/java/org/apache/felix/log/impl/LogServiceImpl.java b/log/src/main/java/org/apache/felix/log/impl/LogServiceImpl.java
new file mode 100644
index 0000000..d6e3f6b
--- /dev/null
+++ b/log/src/main/java/org/apache/felix/log/impl/LogServiceImpl.java
@@ -0,0 +1,99 @@
+/*
+ * 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.felix.log.impl;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+
+/**
+ * Implementation of the OSGi {@link LogService}.
+ */
+final class LogServiceImpl implements LogService {
+
+ /** The log implementation. */
+ private final Log m_log;
+
+ /** The bundle associated with this implementation. */
+ private final Bundle m_bundle;
+
+ /**
+ * Create a new instance.
+ * @param log the log implementation
+ * @param bundle the bundle associated with this implementation
+ */
+ LogServiceImpl(final Log log, final Bundle bundle) {
+ this.m_log = log;
+ this.m_bundle = bundle;
+ }
+
+ /**
+ * Log the specified message at the specified level.
+ * @param level the level to log the message at
+ * @param message the message to log
+ */
+ public void log(final int level, final String message) {
+ log(null, level, message, null);
+ }
+
+ /**
+ * Log the specified message along with the specified exception at the
+ * specified level.
+ * @param level the level to log the message and exception at
+ * @param message the message to log
+ * @param exception the exception to log
+ */
+ public void log(final int level,
+ final String message,
+ final Throwable exception) {
+ log(null, level, message, exception);
+ }
+
+ /**
+ * Log the specified message along with the speicified service reference
+ * at the specified level.
+ * @param sr the service reference of the service that produced the message
+ * @param level the level to log the message at
+ * @param message the message to log
+ */
+ public void log(final ServiceReference sr,
+ final int level,
+ final String message) {
+ log(sr, level, message, null);
+ }
+
+ /**
+ * Log the specified message along with the specified exception and
+ * service reference at the specified level.
+ * @param sr the service reference of the service that produced the message
+ * @param level the level to log the message at
+ * @param message the message to log
+ * @param exception the exception to log
+ */
+ public void log(final ServiceReference sr,
+ final int level,
+ final String message,
+ final Throwable exception) {
+ m_log.addEntry(new LogEntryImpl((sr != null) ? sr.getBundle() : m_bundle,
+ sr,
+ level,
+ message,
+ exception));
+ }
+}
diff --git a/pom.xml b/pom.xml
index e806d91..b505028 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,6 +77,7 @@
<module>eventadmin.bridge.configuration</module>
<module>eventadmin.bridge.useradmin</module>
<module>eventadmin.bridge.wireadmin</module>
+ <module>log</module>
<module>tools/mangen</module>