Merge pull request #41 from rhernandez35/master
FELIX-6318: Fix tiny thread safety bug in BundleWiringImpl::getClassLoader
diff --git a/README.md b/README.md
index 25c2cc6..b668aa1 100644
--- a/README.md
+++ b/README.md
@@ -49,6 +49,7 @@
- **ipojo** `/ipojo` - A *service component runtime* aiming to simplify OSGi application development.
- **jaas support** `/jaas` - Bundle to simplify JAAS usage within OSGi environment.
- **logback** `/logback` - A simple integration of the OSGi R7 Log (1.4) service to Logback backend.
+- **OSGi metrics** `/metrics/osgi` - Collecting and publishing metrics related to OSGi applications
- **rootcause** `/rootcause` - Finding the root cause of problems with OSGi declarative services components.
- **utils** `/utils` - Utility classes for OSGi (intended for embedding within other bundles.)
- **webconsole** `/webconsole*` - Web Based Management Console for OSGi Frameworks.
diff --git a/eventadmin/impl/changelog.txt b/eventadmin/impl/changelog.txt
index f9241c2..570d04b 100644
--- a/eventadmin/impl/changelog.txt
+++ b/eventadmin/impl/changelog.txt
@@ -1,3 +1,12 @@
+Changes in 1.6.0
+----------------
+** Improvement
+ * [FELIX-4678] - Make list of denied event handlers available via JMX
+** Task
+ * [FELIX-6329] - Update event admin to Java 8
+ * [FELIX-6330] - Use term deny list for event handlers hitting the timeout
+
+
Changes in 1.5.0
----------------
** Task
diff --git a/eventadmin/impl/pom.xml b/eventadmin/impl/pom.xml
index b63f9c9..9e2378f 100644
--- a/eventadmin/impl/pom.xml
+++ b/eventadmin/impl/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.apache.felix</groupId>
<artifactId>felix-parent</artifactId>
- <version>6</version>
+ <version>7</version>
<relativePath>../../pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -29,7 +29,7 @@
<description>
This bundle provides an implementation of the OSGi R7 EventAdmin service.
</description>
- <version>1.5.1-SNAPSHOT</version>
+ <version>1.6.0-SNAPSHOT</version>
<artifactId>org.apache.felix.eventadmin</artifactId>
<scm>
<connection>scm:git:https://github.com/apache/felix-dev.git</connection>
@@ -83,38 +83,38 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
- <version>4.12</version>
+ <version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmock</groupId>
<artifactId>jmock-junit4</artifactId>
- <version>2.5.1</version>
+ <version>2.12.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
- <version>1.5.2</version>
+ <version>1.7.5</version>
<scope>test</scope>
</dependency>
<!-- Integration Testing with Pax Exam -->
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-container-forked</artifactId>
- <version>4.11.0</version>
+ <version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-junit4</artifactId>
- <version>4.11.0</version>
+ <version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-link-mvn</artifactId>
- <version>4.11.0</version>
+ <version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -132,13 +132,13 @@
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-atinject_1.0_spec</artifactId>
- <version>1.0</version>
+ <version>1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.framework</artifactId>
- <version>5.6.10</version>
+ <version>6.0.3</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -152,7 +152,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
- <version>3.5.0</version>
+ <version>5.1.1</version>
<extensions>true</extensions>
<configuration>
<instructions>
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Activator.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Activator.java
index 39378ad..53fdfaa 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Activator.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Activator.java
@@ -25,7 +25,7 @@
/**
* The activator of the EventAdmin bundle. This class registers an implementation of
* the OSGi R4 <tt>EventAdmin</tt> service (see the Compendium 113) with the
- * framework. It features timeout-based blacklisting of event-handlers for both,
+ * framework. It features timeout-based denying of event-handlers for both,
* asynchronous and synchronous event-dispatching (as a spec conform optional
* extension).
*
@@ -51,6 +51,7 @@
*
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
+ @Override
public void start(final BundleContext context)
{
// init the LogWrapper. Subsequently, the static methods of the LogWrapper
@@ -76,6 +77,7 @@
*
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
+ @Override
public void stop(final BundleContext context)
{
if ( m_config != null )
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Configuration.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Configuration.java
index 7ca1dbf..e0faaf9 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Configuration.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Configuration.java
@@ -61,12 +61,12 @@
* </p>
* <p>
* <p>
- * <tt>org.apache.felix.eventadmin.Timeout</tt> - The black-listing timeout in
+ * <tt>org.apache.felix.eventadmin.Timeout</tt> - The deny-listing timeout in
* milliseconds
* </p>
* The default value is 5000. Increase or decrease at own discretion. A value of less
* then 100 turns timeouts off. Any other value is the time in milliseconds granted
- * to each <tt>EventHandler</tt> before it gets blacklisted.
+ * to each <tt>EventHandler</tt> before it gets put on the denylist.
* </p>
* <p>
* <p>
@@ -167,12 +167,15 @@
private volatile EventAdminImpl m_admin;
// The registration of the security decorator factory (i.e., the service)
- private volatile ServiceRegistration m_registration;
+ private volatile ServiceRegistration<EventAdmin> m_registration;
+
+ // The registration of the mbean
+ private volatile ServiceRegistration<Object> m_mbeanreg;
// all adapters
private AbstractAdapter[] m_adapters;
- private ServiceRegistration m_managedServiceReg;
+ private ServiceRegistration<?> m_managedServiceReg;
// the access control context
private final AccessControlContext acc;
@@ -285,7 +288,7 @@
// The timeout in milliseconds - A value of less then 100 turns timeouts off.
// Any other value is the time in milliseconds granted to each EventHandler
- // before it gets blacklisted.
+ // before it gets denied.
m_timeout = getIntProperty(PROP_TIMEOUT,
m_bundleContext.getProperty(PROP_TIMEOUT), 5000, Integer.MIN_VALUE);
@@ -434,8 +437,13 @@
// register the admin wrapped in a service factory (SecureEventAdminFactory)
// that hands-out the m_admin object wrapped in a decorator that checks
// appropriated permissions of each calling bundle
- m_registration = m_bundleContext.registerService(EventAdmin.class.getName(),
+ m_registration = m_bundleContext.registerService(EventAdmin.class,
new SecureEventAdminFactory(m_admin), null);
+
+ final Dictionary<String, Object> mbeanProps = new Hashtable<>();
+ mbeanProps.put("jmx.objectname", "org.apache.felix.eventadmin:type=handlerinfo,name=EventAdmin");
+
+ m_mbeanreg = m_bundleContext.registerService(Object.class, m_admin.getHandlerInfoMBean(), mbeanProps);
}
else
{
@@ -469,6 +477,10 @@
m_managedServiceReg = null;
}
// We need to unregister manually
+ if ( m_mbeanreg != null ) {
+ m_mbeanreg.unregister();
+ m_mbeanreg = null;
+ }
if ( m_registration != null )
{
m_registration.unregister();
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/MetaTypeProviderImpl.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/MetaTypeProviderImpl.java
index 46830fe..0c5e343 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/MetaTypeProviderImpl.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/MetaTypeProviderImpl.java
@@ -111,9 +111,9 @@
m_asyncThreadPoolRatio));
adList.add( new AttributeDefinitionImpl( Configuration.PROP_TIMEOUT, "Timeout",
- "The black-listing timeout in milliseconds. The default value is 5000. Increase or decrease " +
+ "The deny-list timeout in milliseconds. The default value is 5000. Increase or decrease " +
"at own discretion. A value of less then 100 turns timeouts off. Any other value is the time " +
- "in milliseconds granted to each event handler before it gets blacklisted",
+ "in milliseconds granted to each event handler before it gets denied",
m_timeout ) );
adList.add( new AttributeDefinitionImpl( Configuration.PROP_REQUIRE_TOPIC, "Require Topic",
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/BundleEventAdapter.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/BundleEventAdapter.java
index 58106c8..5719999 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/BundleEventAdapter.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/BundleEventAdapter.java
@@ -68,8 +68,7 @@
properties.put(EventConstants.EVENT, event);
- properties.put("bundle.id", new Long(event.getBundle()
- .getBundleId()));
+ properties.put("bundle.id", event.getBundle().getBundleId());
final String symbolicName = event.getBundle().getSymbolicName();
@@ -81,7 +80,7 @@
properties.put("bundle", event.getBundle());
- final StringBuffer topic = new StringBuffer(BundleEvent.class
+ final StringBuilder topic = new StringBuilder(BundleEvent.class
.getName().replace('.', '/')).append('/');
switch (event.getType())
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/FrameworkEventAdapter.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/FrameworkEventAdapter.java
index a5a55f4..9a971d3 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/FrameworkEventAdapter.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/FrameworkEventAdapter.java
@@ -66,7 +66,7 @@
@Override
public void frameworkEvent(final FrameworkEvent event)
{
- final Dictionary<String, Object> properties = new Hashtable<String, Object>();
+ final Dictionary<String, Object> properties = new Hashtable<>();
properties.put(EventConstants.EVENT, event);
@@ -74,7 +74,7 @@
if (null != bundle)
{
- properties.put("bundle.id", new Long(bundle.getBundleId()));
+ properties.put("bundle.id", bundle.getBundleId());
final String symbolicName = bundle.getSymbolicName();
@@ -105,7 +105,7 @@
properties.put(EventConstants.EXCEPTION, thrown);
}
- final StringBuffer topic = new StringBuffer(
+ final StringBuilder topic = new StringBuilder(
FrameworkEvent.class.getName().replace('.', '/'))
.append('/');
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/LogEventAdapter.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/LogEventAdapter.java
index 7cce20e..84dcd70 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/LogEventAdapter.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/LogEventAdapter.java
@@ -21,7 +21,6 @@
import java.util.Dictionary;
import java.util.Hashtable;
-import org.apache.felix.eventadmin.impl.util.LogWrapper;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
@@ -75,7 +74,7 @@
m_context.addServiceListener(this, "(" + Constants.OBJECTCLASS
+ "=org.osgi.service.log.LogReaderService)");
- final ServiceReference[] refs;
+ final ServiceReference<?>[] refs;
refs = m_context.getServiceReferences(
"org.osgi.service.log.LogReaderService", null);
@@ -161,11 +160,9 @@
if (null != bundle)
{
- properties.put("bundle.id", new Long(bundle
- .getBundleId()));
+ properties.put("bundle.id", bundle.getBundleId());
final String symbolicName = bundle.getSymbolicName();
-
if (null != symbolicName)
{
properties.put(EventConstants.BUNDLE_SYMBOLICNAME,
@@ -175,13 +172,12 @@
properties.put("bundle", bundle);
}
- properties.put("log.level", new Integer(entry.getLevel()));
+ properties.put("log.level", entry.getLevel());
- properties.put(EventConstants.MESSAGE,
- (entry.getMessage()) != null ? entry.getMessage() : "" );
+ properties.put(EventConstants.MESSAGE,
+ (entry.getMessage()) != null ? entry.getMessage() : "" );
- properties.put(EventConstants.TIMESTAMP, new Long(
- entry.getTime()));
+ properties.put(EventConstants.TIMESTAMP, entry.getTime());
properties.put("log.entry", entry);
@@ -203,62 +199,25 @@
properties.put(EventConstants.EXCEPTION, exception);
}
- final ServiceReference service = entry
- .getServiceReference();
+ final ServiceReference<?> service = entry.getServiceReference();
if (null != service)
{
properties.put(EventConstants.SERVICE, service);
+ properties.put(EventConstants.SERVICE_ID, service.getProperty(EventConstants.SERVICE_ID));
+ properties.put(
+ EventConstants.SERVICE_OBJECTCLASS,
+ service.getProperty(Constants.OBJECTCLASS));
- final Object id = service
- .getProperty(EventConstants.SERVICE_ID);
-
- if (null != id)
- {
- try
- {
- properties.put(EventConstants.SERVICE_ID,
- new Long(id.toString()));
- } catch (NumberFormatException ne)
- {
- // LOG and IGNORE
- LogWrapper.getLogger().log(
- entry.getServiceReference(),
- LogWrapper.LOG_WARNING, "Exception parsing " +
- EventConstants.SERVICE_ID + "=" + id, ne);
- }
- }
-
- final Object pid = service.getProperty(
- EventConstants.SERVICE_PID);
-
+ final Object pid = service.getProperty(EventConstants.SERVICE_PID);
if (null != pid)
{
- properties.put(EventConstants.SERVICE_PID,
- pid.toString());
+ properties.put(EventConstants.SERVICE_PID, pid);
}
- final Object objectClass = service.getProperty(
- Constants.OBJECTCLASS);
-
- if (null != objectClass)
- {
- if (objectClass instanceof String[])
- {
- properties.put(
- EventConstants.SERVICE_OBJECTCLASS,
- objectClass);
- }
- else
- {
- properties.put(
- EventConstants.SERVICE_OBJECTCLASS,
- new String[] { objectClass.toString() });
- }
- }
}
- final StringBuffer topic = new StringBuffer(
+ final StringBuilder topic = new StringBuilder(
org.osgi.service.log.LogEntry.class.getName().replace(
'.', '/')).append('/');
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/ServiceEventAdapter.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/ServiceEventAdapter.java
index cf55b8b..74ab293 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/ServiceEventAdapter.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/adapter/ServiceEventAdapter.java
@@ -21,7 +21,6 @@
import java.util.Dictionary;
import java.util.Hashtable;
-import org.apache.felix.eventadmin.impl.util.LogWrapper;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceEvent;
@@ -71,53 +70,22 @@
properties.put(EventConstants.EVENT, event);
- properties.put(EventConstants.SERVICE, event
- .getServiceReference());
+ properties.put(EventConstants.SERVICE, event.getServiceReference());
- final Object id = event.getServiceReference().getProperty(
- EventConstants.SERVICE_ID);
+ properties.put(EventConstants.SERVICE_ID,
+ event.getServiceReference().getProperty(EventConstants.SERVICE_ID));
- if (null != id)
- {
- try
- {
- properties.put(EventConstants.SERVICE_ID, new Long(id
- .toString()));
- } catch (NumberFormatException ne)
- {
- // LOG and IGNORE
- LogWrapper.getLogger().log(event.getServiceReference(),
- LogWrapper.LOG_WARNING, "Exception parsing " +
- EventConstants.SERVICE_ID + "=" + id, ne);
- }
- }
+ properties.put(EventConstants.SERVICE_OBJECTCLASS,
+ event.getServiceReference().getProperty(Constants.OBJECTCLASS));
final Object pid = event.getServiceReference().getProperty(
- EventConstants.SERVICE_PID);
-
+ EventConstants.SERVICE_PID);
if (null != pid)
{
- properties.put(EventConstants.SERVICE_PID, pid.toString());
+ properties.put(EventConstants.SERVICE_PID, pid);
}
- final Object objectClass = event.getServiceReference()
- .getProperty(Constants.OBJECTCLASS);
-
- if (null != objectClass)
- {
- if (objectClass instanceof String[])
- {
- properties.put(EventConstants.SERVICE_OBJECTCLASS,
- objectClass);
- }
- else
- {
- properties.put(EventConstants.SERVICE_OBJECTCLASS,
- new String[] { objectClass.toString() });
- }
- }
-
- final StringBuffer topic = new StringBuffer(ServiceEvent.class
+ final StringBuilder topic = new StringBuilder(ServiceEvent.class
.getName().replace('.', '/')).append('/');
switch (event.getType())
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventAdminImpl.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventAdminImpl.java
index 249bbea..05977c0 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventAdminImpl.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventAdminImpl.java
@@ -18,6 +18,9 @@
*/
package org.apache.felix.eventadmin.impl.handler;
+import java.util.ArrayList;
+import java.util.List;
+
import org.apache.felix.eventadmin.impl.tasks.AsyncDeliverTasks;
import org.apache.felix.eventadmin.impl.tasks.DefaultThreadPool;
import org.apache.felix.eventadmin.impl.tasks.SyncDeliverTasks;
@@ -184,4 +187,24 @@
throw new NullPointerException(name + " may not be null");
}
}
+
+ public interface EventHandlerMBean {
+
+ String[] getDeniedEventHandlers();
+ }
+
+ public Object getHandlerInfoMBean() {
+ return new EventHandlerMBean() {
+
+ @Override
+ public String[] getDeniedEventHandlers() {
+ final List<String> names = new ArrayList<>();
+ for(final EventHandlerProxy p : tracker.getDeniedHandlers()) {
+ names.add(p.getInfo());
+ }
+
+ return names.toArray(new String[names.size()]);
+ }
+ };
+ }
}
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerProxy.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerProxy.java
index 10adfc2..5ff9938 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerProxy.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerProxy.java
@@ -36,7 +36,7 @@
* on demand and prepares some information for faster processing.
*
* It checks the timeout handling for the implementation as well as
- * blacklisting the handler.
+ * putting the handler on the deny list.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
@@ -57,8 +57,8 @@
/** Lazy fetched event handler. */
private volatile EventHandler handler;
- /** Is this handler blacklisted? */
- private volatile boolean blacklisted;
+ /** Is this handler denied? */
+ private volatile boolean denied;
/** Use timeout. */
private boolean useTimeout;
@@ -85,7 +85,7 @@
*/
public boolean update()
{
- this.blacklisted = false;
+ this.denied = false;
boolean valid = true;
// First check, topic
final Object topicObj = reference.getProperty(EventConstants.EVENT_TOPIC);
@@ -268,6 +268,13 @@
}
/**
+ * Get some info about the event handler
+ */
+ public String getInfo() {
+ return this.reference.toString() + " [Bundle " + this.reference.getBundle() + "]";
+ }
+
+ /**
* Dispose the proxy and release the handler
*/
public void dispose()
@@ -327,13 +334,13 @@
/**
* Check if this handler is allowed to receive the event
- * - blacklisted
+ * - denied
* - check filter
* - check permission
*/
public boolean canDeliver(final Event event)
{
- if ( this.blacklisted )
+ if ( this.denied )
{
return false;
}
@@ -427,19 +434,23 @@
}
/**
- * Blacklist the handler.
+ * Deny the handler.
*/
- public void blackListHandler()
+ public void denyEventHandler()
{
- if(!this.blacklisted)
+ if(!this.denied)
{
LogWrapper.getLogger().log(
LogWrapper.LOG_WARNING,
- "Blacklisting ServiceReference [" + this.reference + " | Bundle("
+ "Denying event handler from ServiceReference [" + this.reference + " | Bundle("
+ this.reference.getBundle() + ")] due to timeout!");
- this.blacklisted = true;
+ this.denied = true;
// we can free the handler now.
this.release();
}
}
+
+ public boolean isDenied() {
+ return this.denied;
+ }
}
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerTracker.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerTracker.java
index 8979a0e..7c3c5fd 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerTracker.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/handler/EventHandlerTracker.java
@@ -224,6 +224,39 @@
return handlers;
}
+ /**
+ * Get all handlers for this event
+ *
+ * @param event The event topic
+ * @return All handlers for the event
+ */
+ public Collection<EventHandlerProxy> getDeniedHandlers() {
+ final Set<EventHandlerProxy> handlers = new HashSet<>();
+
+ for(final EventHandlerProxy p : this.matchingAllEvents) {
+ if ( p.isDenied() ) {
+ handlers.add(p);
+ }
+ }
+
+ for(final List<EventHandlerProxy> l : this.matchingPrefixTopic.values()) {
+ for(final EventHandlerProxy p :l) {
+ if ( p.isDenied() ) {
+ handlers.add(p);
+ }
+ }
+ }
+ for(final List<EventHandlerProxy> l : this.matchingTopic.values()) {
+ for(final EventHandlerProxy p :l) {
+ if ( p.isDenied() ) {
+ handlers.add(p);
+ }
+ }
+ }
+
+ return handlers;
+ }
+
/**
* Checks each handler from the proxy list if it can deliver the event
* If the event can be delivered, the proxy is added to the handlers.
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/BlacklistLatch.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/DenylistLatch.java
similarity index 79%
rename from eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/BlacklistLatch.java
rename to eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/DenylistLatch.java
index 2a844ec..4d5d1b4 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/BlacklistLatch.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/DenylistLatch.java
@@ -28,10 +28,10 @@
/**
*
- * A latch that checks handlers for blacklisting on an interval.
+ * A latch that checks handlers for denying on an interval.
*
*/
-public class BlacklistLatch {
+public class DenylistLatch {
private final Semaphore internalSemaphore;
@@ -43,15 +43,15 @@
/**
* @param count Number of handlers that must call countdown
- * @param timeout Timeout in Milliseconds to check for blacklisting handlers
+ * @param timeout Timeout in Milliseconds to check for denying handlers
*/
- public BlacklistLatch(final int count, final long timeout)
+ public DenylistLatch(final int count, final long timeout)
{
this.handlerTasks = new ArrayList<HandlerTask>(count);
this.count = count;
this.timeout = timeout;
- internalSemaphore = new Semaphore(count);
- internalSemaphore.drainPermits();
+ this.internalSemaphore = new Semaphore(count);
+ this.internalSemaphore.drainPermits();
}
/**
@@ -66,11 +66,11 @@
/**
*
- * Adds a handler task to the timeout based blackout checking.
+ * Adds a handler task to the timeout based deny list checking.
*
* @param task
*/
- public void addToBlacklistCheck(final HandlerTask task)
+ public void addToDenylistCheck(final HandlerTask task)
{
this.handlerTasks.add(task);
}
@@ -78,10 +78,10 @@
/**
*
* Causes current thread to wait until each handler has called countDown.
- * Checks on timeout interval to determine if a handler needs blacklisting.
+ * Checks on timeout interval to determine if a handler needs deny listing.
*
*/
- public void awaitAndBlacklistCheck()
+ public void awaitAndDenylistCheck()
{
try
{
@@ -91,7 +91,7 @@
while(handlerTaskIt.hasNext())
{
HandlerTask currentTask = handlerTaskIt.next();
- currentTask.checkForBlacklist();
+ currentTask.checkForDenylist();
}
}
}
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java
index db39165..bf9b0d4 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java
@@ -33,7 +33,7 @@
private final long timeout;
- private final BlacklistLatch handlerLatch;
+ private final DenylistLatch handlerLatch;
private volatile long startTime;
@@ -44,10 +44,10 @@
*
* @param task Proxy to the event handler
* @param event The event to send to the handler
- * @param timeout Timeout for handler blacklisting
+ * @param timeout Timeout for handler denying
* @param handlerLatch The latch used to ensure events fire in proper order
*/
- public HandlerTask(final EventHandlerProxy task, final Event event, final long timeout, final BlacklistLatch handlerLatch)
+ public HandlerTask(final EventHandlerProxy task, final Event event, final long timeout, final DenylistLatch handlerLatch)
{
this.task = task;
this.event = event;
@@ -69,7 +69,7 @@
// execute the task
task.sendEvent(event);
endTime = System.currentTimeMillis();
- checkForBlacklist();
+ checkForDenylist();
}
finally
{
@@ -77,7 +77,7 @@
}
}
- public void runWithoutBlacklistTiming()
+ public void runWithoutDenylistTiming()
{
task.sendEvent(event);
handlerLatch.countDown();
@@ -98,14 +98,14 @@
}
/**
- * Check to see if we need to blacklist this handler
+ * Check to see if we need to deny this handler
*
*/
- public void checkForBlacklist()
+ public void checkForDenylist()
{
if (useTimeout() && getTaskTime() > this.timeout)
{
- task.blackListHandler();
+ task.denyEventHandler();
}
}
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java
index 120fc23..abec088 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java
@@ -91,7 +91,7 @@
final SyncThread syncThread = sleepingThread instanceof SyncThread ? (SyncThread)sleepingThread : null;
final Iterator<EventHandlerProxy> i = tasks.iterator();
- final BlacklistLatch handlerLatch = new BlacklistLatch(tasks.size(), this.timeout/2);
+ final DenylistLatch handlerLatch = new DenylistLatch(tasks.size(), this.timeout/2);
while ( i.hasNext() )
{
@@ -101,7 +101,7 @@
// {
if( !handlerTask.useTimeout() )
{
- handlerTask.runWithoutBlacklistTiming();
+ handlerTask.runWithoutDenylistTiming();
}
else if ( syncThread != null )
{
@@ -112,7 +112,7 @@
else
{
- handlerLatch.addToBlacklistCheck(handlerTask);
+ handlerLatch.addToDenylistCheck(handlerTask);
if ( !this.pool.executeTask(handlerTask) )
{
// scheduling failed: last resort, call directly
@@ -122,7 +122,7 @@
// }
}
- handlerLatch.awaitAndBlacklistCheck();
+ handlerLatch.awaitAndDenylistCheck();
}
}
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/util/LogWrapper.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/util/LogWrapper.java
index 1a94ae7..9b0b22e 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/util/LogWrapper.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/util/LogWrapper.java
@@ -85,7 +85,7 @@
public static final int LOG_DEBUG = 4;
// A set containing the currently available LogServices. Furthermore used as lock
- private final Set<ServiceReference> m_loggerRefs = new HashSet<ServiceReference>();
+ private final Set<ServiceReference<?>> m_loggerRefs = new HashSet<>();
// Only null while not set and m_loggerRefs is empty hence, only needs to be
// checked in case m_loggerRefs is empty otherwise it will not be null.
@@ -173,7 +173,7 @@
logWrapper.m_logServiceListener = listener;
// Add all available LogService references to the singleton.
- final ServiceReference[] refs = context.getServiceReferences( "org.osgi.service.log.LogService", null );
+ final ServiceReference<?>[] refs = context.getServiceReferences( "org.osgi.service.log.LogService", null );
if ( null != refs )
{
@@ -213,7 +213,7 @@
/*
* Add a reference to a newly available LogService
*/
- void addLoggerRef( final ServiceReference ref )
+ void addLoggerRef( final ServiceReference<?> ref )
{
synchronized (m_loggerRefs)
{
@@ -255,9 +255,9 @@
{
// There is at least one LogService available hence, we can use the
// class as well.
- for (Iterator<ServiceReference> iter = m_loggerRefs.iterator(); iter.hasNext();)
+ for (Iterator<ServiceReference<?>> iter = m_loggerRefs.iterator(); iter.hasNext();)
{
- final ServiceReference next = iter.next();
+ final ServiceReference<?> next = iter.next();
org.osgi.service.log.LogService logger =
(org.osgi.service.log.LogService) m_context.getService(next);
@@ -307,9 +307,9 @@
{
// There is at least one LogService available hence, we can use the
// class as well.
- for (Iterator<ServiceReference> iter = m_loggerRefs.iterator(); iter.hasNext();)
+ for (Iterator<ServiceReference<?>> iter = m_loggerRefs.iterator(); iter.hasNext();)
{
- final ServiceReference next = iter.next();
+ final ServiceReference<?> next = iter.next();
org.osgi.service.log.LogService logger =
(org.osgi.service.log.LogService) m_context.getService(next);
@@ -343,7 +343,7 @@
* @param level The log level with which to log the msg.
* @param msg The message to log.
*/
- public void log(final ServiceReference sr, final int level, final String msg)
+ public void log(final ServiceReference<?> sr, final int level, final String msg)
{
// The method will remove any unregistered service reference as well.
synchronized(m_loggerRefs)
@@ -359,9 +359,9 @@
{
// There is at least one LogService available hence, we can use the
// class as well.
- for (Iterator<ServiceReference> iter = m_loggerRefs.iterator(); iter.hasNext();)
+ for (Iterator<ServiceReference<?>> iter = m_loggerRefs.iterator(); iter.hasNext();)
{
- final ServiceReference next = iter.next();
+ final ServiceReference<?> next = iter.next();
org.osgi.service.log.LogService logger =
(org.osgi.service.log.LogService) m_context.getService(next);
@@ -396,7 +396,7 @@
* @param msg The message to log.
* @param ex The exception associated with the message.
*/
- public void log(final ServiceReference sr, final int level, final String msg,
+ public void log(final ServiceReference<?> sr, final int level, final String msg,
final Throwable ex)
{
// The method will remove any unregistered service reference as well.
@@ -413,9 +413,9 @@
{
// There is at least one LogService available hence, we can use the
// class as well.
- for (Iterator<ServiceReference> iter = m_loggerRefs.iterator(); iter.hasNext();)
+ for (Iterator<ServiceReference<?>> iter = m_loggerRefs.iterator(); iter.hasNext();)
{
- final ServiceReference next = iter.next();
+ final ServiceReference<?> next = iter.next();
org.osgi.service.log.LogService logger =
(org.osgi.service.log.LogService) m_context.getService(next);
@@ -445,7 +445,7 @@
* Log the message to standard output. This appends the level to the message.
* null values are handled appropriate.
*/
- private void _log(final ServiceReference sr, final int level, final String msg,
+ private void _log(final ServiceReference<?> sr, final int level, final String msg,
Throwable ex)
{
String s = (sr == null) ? null : "SvcRef " + sr;
diff --git a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/AbstractTest.java b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/AbstractTest.java
index a952362..325c0da 100644
--- a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/AbstractTest.java
+++ b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/AbstractTest.java
@@ -23,7 +23,6 @@
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.provision;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
-import static org.ops4j.pax.exam.CoreOptions.vmOption;
import java.io.File;
import java.util.ArrayList;
@@ -34,7 +33,6 @@
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
@@ -68,7 +66,7 @@
private EventAdmin eventAdmin;
/** Event admin reference. */
- private ServiceReference eventAdminReference;
+ private ServiceReference<EventAdmin> eventAdminReference;
private volatile boolean running = false;
@@ -81,8 +79,8 @@
private volatile String prefix;
private volatile long startTime;
-
- private final Queue<Event> eventRecievedList = new ConcurrentLinkedQueue();
+
+ private final Queue<Event> eventRecievedList = new ConcurrentLinkedQueue<>();
/** Wait lock for syncing. */
private final Object waitLock = new Object();
@@ -108,10 +106,10 @@
* @param sync
*/
protected void send(String topic, Dictionary<String, Object> properties, int index, boolean sync) {
-
- if(properties == null)
+
+ if (properties == null)
{
- properties = new Hashtable();
+ properties = new Hashtable<>();
}
properties.put("thread", Thread.currentThread().getId());
properties.put("index", index);
@@ -232,11 +230,10 @@
/**
* Helper method to get a service of the given type
*/
- @SuppressWarnings("unchecked")
protected <T> T getService(Class<T> clazz) {
- final ServiceReference ref = bundleContext.getServiceReference(clazz.getName());
+ final ServiceReference<T> ref = bundleContext.getServiceReference(clazz);
assertNotNull("getService(" + clazz.getName() + ") must find ServiceReference", ref);
- final T result = (T)(bundleContext.getService(ref));
+ final T result = (bundleContext.getService(ref));
assertNotNull("getService(" + clazz.getName() + ") must find service", result);
return result;
}
@@ -244,10 +241,10 @@
protected EventAdmin getEventAdmin() {
if ( eventAdminReference == null || eventAdminReference.getBundle() == null ) {
eventAdmin = null;
- eventAdminReference = bundleContext.getServiceReference(EventAdmin.class.getName());
+ eventAdminReference = bundleContext.getServiceReference(EventAdmin.class);
}
if ( eventAdmin == null && eventAdminReference != null ) {
- eventAdmin = (EventAdmin) bundleContext.getService(eventAdminReference);
+ eventAdmin = bundleContext.getService(eventAdminReference);
}
return eventAdmin;
}
@@ -274,7 +271,7 @@
// with build server issue
new DirectURLJUnitBundlesOption(),
systemProperty("pax.exam.invoker").value("junit"),
- //vmOption("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"),
+ //vmOption("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"),
bundle("link:classpath:META-INF/links/org.ops4j.pax.exam.invoker.junit.link")
);
}
@@ -293,7 +290,7 @@
{
Integer index = (Integer)currentEvent.getProperty("index");
Long threadId = (Long)currentEvent.getProperty("thread");
-
+
if(index != null && threadId != null){
Integer previousIndex = orderVerifyMap.get(threadId);
if(previousIndex == null)
diff --git a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/Listener.java b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/Listener.java
index b06e6e3..b92d239 100644
--- a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/Listener.java
+++ b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/ittests/Listener.java
@@ -26,7 +26,7 @@
public class Listener implements EventHandler {
- private final ServiceRegistration reg;
+ private final ServiceRegistration<EventHandler> reg;
private final Object payload;
@@ -44,7 +44,7 @@
props.put("event.topics", "*");
}
this.test = test;
- this.reg = ctx.registerService(EventHandler.class.getName(), this, props);
+ this.reg = ctx.registerService(EventHandler.class, this, props);
this.payload = payload;
}
diff --git a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/perftests/PerformanceTestIT.java b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/perftests/PerformanceTestIT.java
index 7c19b4c..3613a5b 100644
--- a/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/perftests/PerformanceTestIT.java
+++ b/eventadmin/impl/src/test/java/org/apache/felix/eventadmin/perftests/PerformanceTestIT.java
@@ -16,6 +16,27 @@
*/
package org.apache.felix.eventadmin.perftests;
+import static org.ops4j.pax.exam.Constants.START_LEVEL_SYSTEM_BUNDLES;
+import static org.ops4j.pax.exam.CoreOptions.bundle;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.provision;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+import static org.ops4j.pax.exam.CoreOptions.vmOption;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.inject.Inject;
+
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,21 +54,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.inject.Inject;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-
-import static org.ops4j.pax.exam.Constants.START_LEVEL_SYSTEM_BUNDLES;
-import static org.ops4j.pax.exam.CoreOptions.*;
-
@RunWith(PaxExam.class)
public class PerformanceTestIT {
// the name of the system property providing the bundle file to be installed and tested
@@ -62,7 +68,7 @@
protected BundleContext bundleContext;
/** Event admin reference. */
- private ServiceReference eventAdminReference;
+ private ServiceReference<EventAdmin> eventAdminReference;
/** Event admin. */
private EventAdmin eventAdmin;
@@ -102,10 +108,10 @@
protected EventAdmin loadEventAdmin() {
if ( eventAdminReference == null || eventAdminReference.getBundle() == null ) {
eventAdmin = null;
- eventAdminReference = bundleContext.getServiceReference(EventAdmin.class.getName());
+ eventAdminReference = bundleContext.getServiceReference(EventAdmin.class);
}
if ( eventAdmin == null && eventAdminReference != null ) {
- eventAdmin = (EventAdmin) bundleContext.getService(eventAdminReference);
+ eventAdmin = bundleContext.getService(eventAdminReference);
}
return eventAdmin;
}
@@ -278,7 +284,7 @@
private static abstract class Listener implements EventHandler {
- private ServiceRegistration registration;
+ private ServiceRegistration<EventHandler> registration;
protected Listener() {
}
@@ -290,7 +296,7 @@
} else {
props.put("event.topics", "*");
}
- this.registration = bundleContext.registerService(EventHandler.class.getName(), this, props);
+ this.registration = bundleContext.registerService(EventHandler.class, this, props);
}
public void unregister() {
diff --git a/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java b/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java
index a6d47ab..036732b 100644
--- a/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java
@@ -18,6 +18,16 @@
*/
package org.apache.felix.framework;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+
import org.apache.felix.framework.cache.Content;
import org.apache.felix.framework.util.FelixConstants;
import org.apache.felix.framework.util.MultiReleaseContent;
@@ -36,17 +46,6 @@
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.security.ProtectionDomain;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Map;
-
public class BundleRevisionImpl implements BundleRevision, Resource
{
public final static int EAGER_ACTIVATION = 0;
@@ -647,7 +646,7 @@
m_bundle.getFramework()._getProperty(Constants.FRAMEWORK_UUID) + "_" + m_id + ":" + port + path,
getBundle().getFramework().getBundleStreamHandler());
}
- catch (MalformedURLException ex)
+ catch (Exception ex)
{
m_bundle.getFramework().getLogger().log(
m_bundle,
diff --git a/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java b/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
index 70eb50a..437d962 100644
--- a/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
@@ -54,7 +54,6 @@
import java.io.IOException;
import java.lang.reflect.Constructor;
-import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
@@ -1081,37 +1080,6 @@
return m_revision.getBundle();
}
- //
- // Class loader implementation methods.
- //
-
- private URL createURL(int port, String path)
- {
- // Add a slash if there is one already, otherwise
- // the is no slash separating the host from the file
- // in the resulting URL.
- if (!path.startsWith("/"))
- {
- path = "/" + path;
- }
-
- try
- {
- return BundleRevisionImpl.getSecureAction().createURL(null,
- FelixConstants.BUNDLE_URL_PROTOCOL + "://" +
- getBundle().getFramework()._getProperty(Constants.FRAMEWORK_UUID) + "_" + m_revision.getId() + ":" + port + path,
- getBundle().getFramework().getBundleStreamHandler());
- }
- catch (MalformedURLException ex)
- {
- m_logger.log(m_revision.getBundle(),
- Logger.LOG_ERROR,
- "Unable to create resource URL.",
- ex);
- }
- return null;
- }
-
public Enumeration getResourcesByDelegation(String name)
{
Set requestSet = (Set) m_cycleCheck.get();
diff --git a/framework/src/main/java/org/apache/felix/framework/URLHandlersBundleURLConnection.java b/framework/src/main/java/org/apache/felix/framework/URLHandlersBundleURLConnection.java
index 2cb7ee7..794af28 100644
--- a/framework/src/main/java/org/apache/felix/framework/URLHandlersBundleURLConnection.java
+++ b/framework/src/main/java/org/apache/felix/framework/URLHandlersBundleURLConnection.java
@@ -39,18 +39,22 @@
private long m_contentTime;
private String m_contentType;
private InputStream m_is;
+ private final String m_path;
public URLHandlersBundleURLConnection(URL url, Felix framework)
throws IOException
{
super(url);
+ String urlString = url.toExternalForm();
+
+ m_path = urlString.substring(urlString.indexOf(url.getPath()));
+
// If this is an attempt to create a connection to the root of
// the bundle, then throw an exception since this isn't possible.
// We only allow "/" as a valid URL so it can be used as context
// for creating other URLs.
- String path = url.getPath();
- if ((path == null) || (path.length() == 0) || path.equals("/"))
+ if ((m_path == null) || (m_path.length() == 0) || m_path.equals("/"))
{
throw new IOException("Resource does not exist: " + url);
}
@@ -121,11 +125,11 @@
m_classPathIdx = 0;
}
if (!((BundleRevisionImpl) m_targetRevision)
- .hasInputStream(m_classPathIdx, url.getPath()))
+ .hasInputStream(m_classPathIdx, m_path))
{
BundleWiring wiring = m_targetRevision.getWiring();
ClassLoader cl = (wiring != null) ? wiring.getClassLoader() : null;
- URL newurl = (cl != null) ? cl.getResource(url.getPath()) : null;
+ URL newurl = (cl != null) ? cl.getResource(m_path) : null;
if (newurl == null)
{
throw new IOException("Resource does not exist: " + url);
@@ -143,9 +147,9 @@
throw new IOException("Resource does not exist: " + url);
}
m_is = ((BundleRevisionImpl)
- m_targetRevision).getInputStream(m_classPathIdx, url.getPath());
+ m_targetRevision).getInputStream(m_classPathIdx, m_path);
m_contentLength = (m_is == null) ? 0 : m_is.available();
- m_contentType = URLConnection.guessContentTypeFromName(url.getFile());
+ m_contentType = URLConnection.guessContentTypeFromName(m_path);
connected = true;
}
}
@@ -234,6 +238,6 @@
return url;
}
return ((BundleRevisionImpl)
- m_targetRevision).getLocalURL(m_classPathIdx, url.getPath());
+ m_targetRevision).getLocalURL(m_classPathIdx, m_path);
}
}
\ No newline at end of file
diff --git a/framework/src/test/java/org/apache/felix/framework/ResourceLoadingTest.java b/framework/src/test/java/org/apache/felix/framework/ResourceLoadingTest.java
new file mode 100644
index 0000000..ce1167c
--- /dev/null
+++ b/framework/src/test/java/org/apache/felix/framework/ResourceLoadingTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.framework;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import junit.framework.TestCase;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWiring;
+
+public class ResourceLoadingTest extends TestCase
+{
+ private File tempDir;
+ private Framework felix;
+ private File cacheDir;
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ tempDir = File.createTempFile("felix-temp", ".dir");
+ assertTrue("precondition", tempDir.delete());
+ assertTrue("precondition", tempDir.mkdirs());
+
+ cacheDir = new File(tempDir, "felix-cache");
+ assertTrue("precondition", cacheDir.mkdir());
+
+ String cache = cacheDir.getPath();
+
+ Map<String,String> params = new HashMap<String, String>();
+ params.put("felix.cache.profiledir", cache);
+ params.put("felix.cache.dir", cache);
+ params.put(Constants.FRAMEWORK_STORAGE, cache);
+
+ felix = new Felix(params);
+ felix.init();
+ felix.start();
+ }
+
+ @Override
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+
+ felix.stop(); // Note that this method is async
+ felix = null;
+
+ deleteDir(tempDir);
+ tempDir = null;
+ cacheDir = null;
+ }
+
+ public void testResourceLoadingWithHash() throws Exception {
+ String bmf = "Bundle-SymbolicName: cap.bundle\n"
+ + "Bundle-Version: 1.2.3.Blah\n"
+ + "Bundle-ManifestVersion: 2\n"
+ + "Import-Package: org.osgi.framework\n";
+ File bundleFile = File.createTempFile("felix-bundle", ".jar", tempDir);
+
+ Manifest mf = new Manifest(new ByteArrayInputStream(bmf.getBytes("utf-8")));
+ mf.getMainAttributes().putValue("Manifest-Version", "1.0");
+ JarOutputStream os = new JarOutputStream(new FileOutputStream(bundleFile), mf);
+
+ String name = "bla/ bli/@@€ ß&&????ßß &&$$\" \'##&&/ äöüß/ @@ foo#bar#baz ?a=a.txt?d=ä#dlksl";
+ os.putNextEntry(new ZipEntry(name));
+ os.write("This is a Test".getBytes());
+ os.close();
+
+ Bundle testBundle = felix.getBundleContext().installBundle(bundleFile.toURI().toASCIIString());
+
+ testBundle.start();
+
+ assertEquals(Bundle.ACTIVE, testBundle.getState());
+ assertNotNull(testBundle.getResource(name));
+ assertNotNull(testBundle.getEntry(name));
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(testBundle.getResource(name).openStream()));
+ assertEquals("This is a Test", reader.readLine());
+
+ reader = new BufferedReader(new InputStreamReader(testBundle.getEntry(name).openStream()));
+ assertEquals("This is a Test", reader.readLine());
+
+ reader = new BufferedReader(new InputStreamReader(testBundle.adapt(BundleWiring.class).getClassLoader().getResourceAsStream(name)));
+ assertEquals("This is a Test", reader.readLine());
+
+ reader = new BufferedReader(new InputStreamReader(testBundle.adapt(BundleWiring.class).getClassLoader().getResource(name).openStream()));
+ assertEquals("This is a Test", reader.readLine());
+
+ URL url = testBundle.adapt(BundleWiring.class).getClassLoader().getResource(name);
+
+ URL testURL = new URL(url.getProtocol() + "://" + url.getHost() + ":" + url.getPort() + "/" + name);
+
+ reader = new BufferedReader(new InputStreamReader(testURL.openStream()));
+ assertEquals("This is a Test", reader.readLine());
+ }
+
+ private static void deleteDir(File root) throws IOException
+ {
+ if (root.isDirectory())
+ {
+ for (File file : root.listFiles())
+ {
+ deleteDir(file);
+ }
+ }
+ assertTrue(root.delete());
+ }
+}
diff --git a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/DsComponentsCheck.java b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/DsComponentsCheck.java
index 9fcbbd2..cc63898 100644
--- a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/DsComponentsCheck.java
+++ b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/DsComponentsCheck.java
@@ -29,7 +29,6 @@
import org.apache.felix.hc.api.HealthCheck;
import org.apache.felix.hc.api.Result;
import org.apache.felix.hc.api.ResultLog.Entry;
-import org.apache.felix.hc.core.impl.util.lang.StringUtils;
import org.apache.felix.hc.generalchecks.scrutil.DsRootCauseAnalyzer;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
@@ -94,10 +93,10 @@
@Override
public Result execute() {
+ FormattingResultLog log = new FormattingResultLog();
Collection<ComponentDescriptionDTO> componentDescriptionDTOs = scr.getComponentDescriptionDTOs();
List<ComponentDescriptionDTO> watchedComps = new LinkedList<ComponentDescriptionDTO>();
- FormattingResultLog log = new FormattingResultLog();
List<String> missingComponents = new LinkedList<String>(componentsList);
for (ComponentDescriptionDTO desc : componentDescriptionDTOs) {
if (componentsList.contains(desc.name)) {
@@ -106,7 +105,7 @@
}
}
for (String missingComp : missingComponents) {
- log.temporarilyUnavailable("Not found {}", missingComp);
+ log.info("No component with name {} is registered in SCR runtime", missingComp);
}
int countEnabled = 0;
@@ -143,17 +142,15 @@
}
if (!isActive) {
- if (analyzer != null) {
- analyzer.logNotEnabledComponent(log, dsComp, statusForMissing);
- } else {
- log.add(new Entry(statusForMissing, "Not active: " + dsComp.name));
- }
+ analyzer.logNotEnabledComponent(log, dsComp);
}
-
}
+ if (!missingComponents.isEmpty()) {
+ log.add(new Entry(statusForMissing, missingComponents.size() + " required components are missing in SCR runtime"));
+ }
if (countDisabled > 0) {
- log.temporarilyUnavailable("{} required components are not active", countDisabled);
+ log.add(new Entry(statusForMissing, countDisabled + " required components are not active"));
}
log.info("{} required components are active", countEnabled);
diff --git a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ServicesCheck.java b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ServicesCheck.java
index 01e88c6..fefe8c7 100644
--- a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ServicesCheck.java
+++ b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ServicesCheck.java
@@ -116,7 +116,7 @@
for (String missingServiceName : missingServiceNames) {
if (!missingServiceName.startsWith("(")) {
- analyzer.logMissingService(log, missingServiceName, statusForMissing);
+ analyzer.logMissingService(log, missingServiceName);
} else {
log.info("Service '{}' is missing", missingServiceName);
}
diff --git a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAdapter.java b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAdapter.java
index 75a80db..c81d373 100644
--- a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAdapter.java
+++ b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAdapter.java
@@ -39,28 +39,25 @@
this.analyzer = new DSRootCause(scr);
}
- public void logMissingService(FormattingResultLog log, String missingServiceName, Status status) {
+ public void logMissingService(FormattingResultLog log, String missingServiceName) {
Optional<DSComp> rootCauseOptional = analyzer.getRootCause(missingServiceName);
if (rootCauseOptional.isPresent()) {
- logRootCause(log, rootCauseOptional.get(), status);
+ logRootCause(log, rootCauseOptional.get());
} else {
- log.add(new Entry(status, "Missing service without matching DS component: " + missingServiceName));
+ log.info("Missing service without matching DS component: " + missingServiceName);
}
}
- public void logNotEnabledComponent(FormattingResultLog log, ComponentDescriptionDTO desc, Status status) {
+ public void logNotEnabledComponent(FormattingResultLog log, ComponentDescriptionDTO desc) {
DSComp component = analyzer.getRootCause(desc);
- logRootCause(log, component, status);
+ logRootCause(log, component);
}
- private void logRootCause(FormattingResultLog log, DSComp component, Status status) {
+ private void logRootCause(FormattingResultLog log, DSComp component) {
new RootCausePrinter(new Consumer<String>() {
- private boolean firstLineLogged = false;
-
@Override
public void accept(String str) {
- log.add(new Entry(!firstLineLogged ? status : Status.OK, str.replaceFirst(" ", "-- ").replaceFirst(" ", "- ")));
- firstLineLogged = true;
+ log.add(new Entry(Status.OK, str.replaceFirst(" ", "-- ").replaceFirst(" ", "- ")));
}
}).print(component);
}
diff --git a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAnalyzer.java b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAnalyzer.java
index c130945..51a2839 100644
--- a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAnalyzer.java
+++ b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/scrutil/DsRootCauseAnalyzer.java
@@ -53,15 +53,19 @@
}
}
- public void logMissingService(FormattingResultLog log, String missingServiceName, Status status) {
+ public void logMissingService(FormattingResultLog log, String missingServiceName) {
if (dsRootCauseAdapter != null) {
- dsRootCauseAdapter.logMissingService(log, missingServiceName, status);
+ dsRootCauseAdapter.logMissingService(log, missingServiceName);
+ } else {
+ log.info("Service '{}' is missing", missingServiceName);
}
}
- public void logNotEnabledComponent(FormattingResultLog log, ComponentDescriptionDTO desc, Status status) {
+ public void logNotEnabledComponent(FormattingResultLog log, ComponentDescriptionDTO desc) {
if (dsRootCauseAdapter != null) {
- dsRootCauseAdapter.logNotEnabledComponent(log, desc, status);
+ dsRootCauseAdapter.logNotEnabledComponent(log, desc);
+ } else {
+ log.info("Component '{}' is missing", desc.name);
}
}
}
diff --git a/http/README.md b/http/README.md
index b716e1c..a49290a 100644
--- a/http/README.md
+++ b/http/README.md
@@ -19,10 +19,41 @@
* `org.apache.felix.http.cometd` - Adds Comet/Ajax Push functionality to the HTTP Service implementation.
* `org.apache.felix.http.proxy` - Proxy that is needed inside WAR when deployed inside an application server.
-Note that as of version **3.x**, the Serlvet APIs are **no longer** packaged with the implementation bundles! If you are migrating from lower versions, be sure to add the
+Note that as of version **3.x**, the Servlet APIs are **no longer** packaged with the implementation bundles! If you are migrating from lower versions, be sure to add the
`org.apache.felix.http.servlet-api` (or any other compatible Serlvet API bundle) to your
classpath and deployment!
+## Installing additional bundles for the optional HTTP/2 support with jetty
+
+The jetty implementation uses the OSGi ServiceLoader mediator technique to find certain pluggable components that are required for HTTP/2 support. Your OSGi runtime must first have the bundles needed for OSGi ServiceLoader support deployed. Background information about the OSGi ServiceLoader integration is discussed [here](https://blog.osgi.org/2013/02/javautilserviceloader-in-osgi.html)
+
+Deploying the following set of bundles would be one way to enable the ServiceLoader mediator support:
+
+ * `org.apache.aries.spifly:org.apache.aries.spifly.dynamic.bundle:1.3.0`
+ * `org.apache.aries:org.apache.aries.util:1.1.3`
+ * `org.ow2.asm:asm:8.0.1`
+ * `org.ow2.asm:asm-analysis:8.0.1`
+ * `org.ow2.asm:asm-commons:8.0.1`
+ * `org.ow2.asm:asm-tree:8.0.1`
+ * `org.ow2.asm:asm-util:8.0.1`
+
+Next, depending on your server environment you must choose only one of the following sets
+of additional bundles to deploy [as described in the jetty documentation](https://www.eclipse.org/jetty/documentation/current/alpn-chapter.html)
+
+1. For java 9 or later:
+ * `org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715`
+ * `org.eclipse.jetty:jetty-alpn-java-server:${jetty.version}`
+
+2. For java 8:
+ * `org.eclipse.jetty:jetty-alpn-openjdk8-server:${jetty.version}`
+
+ * For java 8 versions earlier than 1.8.0_252 download and add -Xbootclasspath/p:/path_to_file_here/alpn-boot-8.1.13.v20181017.jar as an argument to the java process and then deploy the following bundle:
+ * `org.eclipse.jetty.osgi:jetty-osgi-alpn:${jetty.version}`
+
+ * For java 8 version 1.8.0_252 or later skip the bootclasspath argument and deploy the following bundle instead:
+ * `org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715`
+
+
## Using the OSGi Http Whiteboard
The OSGi whiteboard implementation simplifies the task of registering servlets, filters, resources, listeners, and servlet contexts. For a complete introduction, please refer to the OSGi R7 Compendium or Enterprise specification.
@@ -342,6 +373,12 @@
| `org.apache.felix.jetty.gzip.excludedPaths` | The additional path specs to exclude. Inclusion takes precedence over exclusion. Default is none. |
| `org.apache.felix.jetty.gzip.includedMimeTypes` | The included mime types. Inclusion takes precedence over exclusion. Default is none. |
| `org.apache.felix.jetty.gzip.excludedMimeTypes` | The excluded mime types. Inclusion takes precedence over exclusion. Default is none. |
+| `org.apache.felix.http2.enable` | Whether to enable HTTP/2. Default is false. |
+| `org.apache.felix.jetty.http2.maxConcurrentStreams` | The max number of concurrent streams per connection. Default is 128. |
+| `org.apache.felix.jetty.http2.initialStreamRecvWindow` | The initial stream receive window (client to server). Default is 524288. |
+| `org.apache.felix.jetty.http2.initialSessionRecvWindow` | The initial session receive window (client to server). Default is 1048576. |
+| `org.apache.felix.jetty.alpn.protocols` | The ALPN protocols to consider. Default is h2, http/1.1. |
+| `org.apache.felix.jetty.alpn.defaultProtocol` | The default protocol when negotiation fails. Default is http/1.1. |
### All-in-one-bundle configuration properties
diff --git a/http/jetty/pom.xml b/http/jetty/pom.xml
index 04477cb..8e317c5 100644
--- a/http/jetty/pom.xml
+++ b/http/jetty/pom.xml
@@ -49,6 +49,98 @@
<build>
<plugins>
+
+ <!-- Use a groovy script to preserve the META-INF/services/* files for the artifacts that are embeded in the uber jar -->
+ <plugin>
+ <groupId>org.codehaus.gmaven</groupId>
+ <artifactId>groovy-maven-plugin</artifactId>
+ <version>2.1.1</version>
+ <executions>
+ <execution>
+ <id>groovy-magic</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>execute</goal>
+ </goals>
+ <configuration>
+ <source><![CDATA[
+ // make an output dir for the merged resource files
+ def slDir = new File(project.build.directory, "serviceloader-resources");
+ slDir.mkdirs();
+
+ // scan each of the artifacts to preserve the information found in any META-INF/services/* files
+ project.artifacts.each() { artifact ->
+
+ if (artifact.getArtifactHandler().isAddedToClasspath() && !org.apache.maven.artifact.Artifact.SCOPE_TEST.equals( artifact.getScope() )) {
+ def jar;
+ try {
+ jar = new java.util.jar.JarFile(artifact.file)
+ jar.stream().each() { entry ->
+ if (!entry.isDirectory() && entry.name.startsWith("META-INF/services/")) {
+
+ // check if we already have a file with this name
+ def svcFile = new File(slDir, entry.name)
+ def svcSet = new LinkedHashSet();
+ if (svcFile.exists()) {
+ // found existing file, so load the items from the existing file so we can merge
+ svcFile.eachLine { className ->
+ className = className.trim();
+ if (!className.isEmpty()) {
+ svcSet.add(className);
+ }
+ }
+ }
+
+ // read the content of the found entry
+ def lineReader;
+ try {
+ lineReader = new BufferedReader(new InputStreamReader(jar.getInputStream(entry), java.nio.charset.StandardCharsets.UTF_8));
+ def className;
+ while ( ( className = lineReader.readLine() ) != null ) {
+ className = className.trim();
+ if (!className.isEmpty()) {
+ svcSet.add(className);
+ }
+ }
+ } finally {
+ // cleanup
+ if (lineReader != null) {
+ lineReader.close()
+ }
+ }
+
+ // write the merged data to the output file
+ if (!svcSet.isEmpty()) {
+ // make any missing folders
+ svcFile.getParentFile().mkdirs();
+
+ svcFile.withWriter('utf-8') { writer ->
+ svcSet.each() { item ->
+ writer.writeLine item;
+ }
+
+ // finish up with a blank line
+ writer.println();
+ }
+ }
+
+ }
+ }
+ } finally {
+ // cleanup
+ if (jar != null) {
+ jar.close();
+ }
+ }
+ }
+
+ }
+ ]]></source>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
@@ -112,11 +204,19 @@
osgi.service;objectClass:List<String>="org.osgi.service.http.runtime.HttpServiceRuntime";
uses:="org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto",
osgi.service;objectClass:List<String>="org.osgi.service.http.HttpService";
- uses:="org.osgi.service.http"
+ uses:="org.osgi.service.http",
+ osgi.serviceloader;osgi.serviceloader="org.eclipse.jetty.http.HttpFieldPreEncoder"
</Provide-Capability>
<Require-Capability>
- osgi.contract;filter:="(&(osgi.contract=JavaServlet)(version=3.1))"
+ osgi.contract;filter:="(&(osgi.contract=JavaServlet)(version=3.1))",
+ osgi.extender;filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional,
+ osgi.extender;filter:="(osgi.extender=osgi.serviceloader.processor)";resolution:=optional,
+ osgi.serviceloader;filter:="(osgi.serviceloader=org.eclipse.jetty.http.HttpFieldPreEncoder)";resolution:=optional;cardinality:=multiple,
+ osgi.serviceloader;filter:="(osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Server)";resolution:=optional;cardinality:=multiple
</Require-Capability>
+ <Include-Resource>
+ {maven-resources},${project.build.directory}/serviceloader-resources
+ </Include-Resource>
<_removeheaders>
Private-Package,Conditional-Package
</_removeheaders>
@@ -164,6 +264,23 @@
org.eclipse.jetty.webapp;resolution:=optional,
*
</Import-Package>
+ <!-- We need to override this from the base configuration to exclude the ServiceLoader capabilities -->
+ <Provide-Capability>
+ osgi.implementation;osgi.implementation="osgi.http";version:Version="1.1";
+ uses:="javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard",
+ osgi.service;objectClass:List<String>="org.osgi.service.http.runtime.HttpServiceRuntime";
+ uses:="org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto",
+ osgi.service;objectClass:List<String>="org.osgi.service.http.HttpService";
+ uses:="org.osgi.service.http"
+ </Provide-Capability>
+ <!-- We need to override this from the base configuration to exclude the ServiceLoader capabilities -->
+ <Require-Capability>
+ osgi.contract;filter:="(&(osgi.contract=JavaServlet)(version=3.1))"
+ </Require-Capability>
+ <!-- We need to override this from the base configuration to exclude the ServiceLoader resources -->
+ <Include-Resource>
+ {maven-resources}
+ </Include-Resource>
<_removeheaders>
X-Jetty-Version,Private-Package,Conditional-Package
</_removeheaders>
@@ -249,6 +366,26 @@
<version>${jetty.version}</version>
</dependency>
<dependency>
+ <groupId>org.eclipse.jetty.http2</groupId>
+ <artifactId>http2-server</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.http2</groupId>
+ <artifactId>http2-common</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.http2</groupId>
+ <artifactId>http2-hpack</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-alpn-server</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.service.http</artifactId>
<version>1.2.1</version>
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java
index 9e48814..db08887 100644
--- a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java
@@ -432,6 +432,41 @@
"If not -1, stop timeout for the server in milliseconds.", -1L,
bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_STOP_TIMEOUT)));
+ adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_HTTP2_ENABLE,
+ "Enable Http/2",
+ "Whether to enable HTTP/2. Default is false.",
+ false,
+ bundle.getBundleContext().getProperty(JettyConfig.FELIX_HTTP2_ENABLE)));
+ adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_HTTP2_MAX_CONCURRENT_STREAMS,
+ "Http/2 Max Concurrent Streams",
+ "The max number of concurrent streams per connection. Default is 128.",
+ 128,
+ bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_HTTP2_MAX_CONCURRENT_STREAMS)));
+ adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_HTTP2_INITIAL_STREAM_RECV_WINDOW,
+ "Http/2 Initial Stream Recieve Window",
+ "The initial stream receive window (client to server). Default is 524288.",
+ 524288,
+ bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_HTTP2_INITIAL_STREAM_RECV_WINDOW)));
+ adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_HTTP2_INITIAL_SESSION_RECV_WINDOW,
+ "Http/2 Initial Session Recieve Window",
+ "The initial session receive window (client to server). Default is 1048576.",
+ 1048576,
+ bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_HTTP2_INITIAL_SESSION_RECV_WINDOW)));
+
+ adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_ALPN_PROTOCOLS,
+ "ALPN Protocols",
+ "The ALPN protocols to consider. Default is h2, http/1.1.",
+ AttributeDefinition.STRING,
+ new String[] {"h2", "http/1.1"},
+ 2147483647,
+ null, null,
+ getStringArray(bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_ALPN_PROTOCOLS))));
+ adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_ALPN_DEFAULT_PROTOCOL,
+ "ALPN Default Protocol",
+ "The default protocol when negotiation fails. Default is http/1.1.",
+ "http/1.1",
+ bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_ALPN_DEFAULT_PROTOCOL)));
+
return new ObjectClassDefinition()
{
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
index 549c7fe..aa340d7 100644
--- a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
@@ -267,6 +267,24 @@
/** Felix specific property to specify the stop timeout of the jetty server */
public static final String FELIX_JETTY_STOP_TIMEOUT = "org.apache.felix.jetty.stopTimeout";
+ /** Felix specific property to control whether to enable HTTP/2. */
+ public static final String FELIX_HTTP2_ENABLE = "org.apache.felix.http2.enable";
+
+ /** Felix specific property to specify the max number of concurrent streams per connection */
+ public static final String FELIX_JETTY_HTTP2_MAX_CONCURRENT_STREAMS = "org.apache.felix.jetty.http2.maxConcurrentStreams";
+
+ /** Felix specific property to specify the initial stream receive window (client to server) */
+ public static final String FELIX_JETTY_HTTP2_INITIAL_STREAM_RECV_WINDOW = "org.apache.felix.jetty.http2.initialStreamRecvWindow";
+
+ /** Felix specific property to specify the initial session receive window (client to server) */
+ public static final String FELIX_JETTY_HTTP2_INITIAL_SESSION_RECV_WINDOW = "org.apache.felix.jetty.http2.initialSessionRecvWindow";
+
+ /** Felix specific property to specify the ALPN protocols to consider */
+ public static final String FELIX_JETTY_ALPN_PROTOCOLS = "org.apache.felix.jetty.alpn.protocols";
+
+ /** Felix specific property to specify the default protocol when negotiation fails */
+ public static final String FELIX_JETTY_ALPN_DEFAULT_PROTOCOL = "org.apache.felix.jetty.alpn.defaultProtocol";
+
private static String validateContextPath(String ctxPath)
{
// undefined, empty, or root context path
@@ -530,6 +548,35 @@
return useHttps && getHttpsPort() > 0;
}
+ /**
+ * Returns <code>true</code> if HTTP/2 is configured to be used (
+ * {@link #FELIX_HTTP2_ENABLE})
+ */
+ public boolean isUseHttp2()
+ {
+ return getBooleanProperty(FELIX_HTTP2_ENABLE, false);
+ }
+
+ public int getHttp2MaxConcurrentStreams() {
+ return getIntProperty(FELIX_JETTY_HTTP2_MAX_CONCURRENT_STREAMS, 128);
+ }
+
+ public int getHttp2InitialStreamRecvWindow() {
+ return getIntProperty(FELIX_JETTY_HTTP2_INITIAL_STREAM_RECV_WINDOW, 524288);
+ }
+
+ public int getHttp2InitialSessionRecvWindow() {
+ return getIntProperty(FELIX_JETTY_HTTP2_INITIAL_SESSION_RECV_WINDOW, 1048576);
+ }
+
+ public String[] getAlpnProtocols() {
+ return getStringArrayProperty(FELIX_JETTY_ALPN_PROTOCOLS, new String[] {"h2", "http/1.1"} );
+ }
+
+ public String getAlpnDefaultProtocol() {
+ return getProperty(FELIX_JETTY_ALPN_DEFAULT_PROTOCOL, "http/1.1");
+ }
+
public boolean isProxyLoadBalancerConnection()
{
return getBooleanProperty(FELIX_PROXY_LOAD_BALANCER_CONNECTION_ENABLE, false);
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
index a4b99c8..a067dde 100644
--- a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
@@ -34,7 +34,9 @@
import org.apache.felix.http.base.internal.HttpServiceController;
import org.apache.felix.http.base.internal.logger.SystemLogger;
import org.apache.felix.http.jetty.internal.webapp.WebAppBundleTracker;
+import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.io.ConnectionStatistics;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.security.UserStore;
@@ -471,6 +473,27 @@
httpConfiguration.addCustomizer(customizerWrapper);
}
+ if (this.config.isUseHttp2()) {
+ //add ALPN factory
+ SslConnectionFactory alpnConnFactory = new SslConnectionFactory(sslContextFactory, "alpn");
+ connector.addConnectionFactory(alpnConnFactory);
+
+ ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(this.config.getAlpnProtocols());
+ alpn.setDefaultProtocol(this.config.getAlpnDefaultProtocol());
+ connector.addConnectionFactory(alpn);
+
+ //Configure a HTTP2 on the ssl connector
+ HTTP2ServerConnectionFactory http2factory = new HTTP2ServerConnectionFactory(httpConfiguration);
+ http2factory.setMaxConcurrentStreams(this.config.getHttp2MaxConcurrentStreams());
+ http2factory.setInitialStreamRecvWindow(this.config.getHttp2InitialStreamRecvWindow());
+ http2factory.setInitialSessionRecvWindow(this.config.getHttp2InitialSessionRecvWindow());
+ connector.addConnectionFactory(http2factory);
+
+ //use http/2 cipher comparator
+ sslContextFactory.setCipherComparator(org.eclipse.jetty.http2.HTTP2Cipher.COMPARATOR);
+ sslContextFactory.setUseCipherSuitesOrder(true);
+ }
+
configureConnector(connector, this.config.getHttpsPort());
return startConnector(connector);
}
diff --git a/log/pom.xml b/log/pom.xml
index 420b7c8..a3e2188 100644
--- a/log/pom.xml
+++ b/log/pom.xml
@@ -29,7 +29,7 @@
<description>
A simple implementation of the OSGi R7 Log service.
</description>
- <version>1.2.3-SNAPSHOT</version>
+ <version>1.2.5-SNAPSHOT</version>
<artifactId>org.apache.felix.log</artifactId>
<properties>
@@ -41,6 +41,7 @@
<connection>scm:git:https://github.com/apache/felix-dev.git</connection>
<developerConnection>scm:git:https://github.com/apache/felix-dev.git</developerConnection>
<url>https://gitbox.apache.org/repos/asf?p=felix-dev.git</url>
+ <tag>HEAD</tag>
</scm>
<dependencies>
diff --git a/metrics/osgi/README.md b/metrics/osgi/README.md
new file mode 100644
index 0000000..c0a2e24
--- /dev/null
+++ b/metrics/osgi/README.md
@@ -0,0 +1,70 @@
+# Apache Felix OSGi Metrics
+
+The OSGi metrics module defines a simple mechanism to gather OSGi-related metrics for application startup.
+
+The module is split into two bundles:
+
+- _collector_ - a zero-dependencies bundle that uses the OSGi APIs to gather various metrics
+- _consumers_ - a single bundle that contains various consumers
+
+## Metric collection
+
+The metrics are collected by the `org.apache.felix.metrics.osgi.collector` bundle. This bundle requires no configuration and imports a minimal set of packages, to allow starting as early as possible.
+
+As soon as startup is completed the metrics are made available to consumers that implement the `StartupMetricsListener` interface. The metrics are published after an optional delay, to prevent on-off bounces in startup completion.
+
+Startup completion is delegated to either the `org.apache.felix.systemready` or the `org.apache.felix.healtchecks.api` bundles, which publish marker services once the system is considered ready.
+
+## Metric publication
+
+The `org.apache.felix.metrics.osgi.consumers` bundle contains three out-of-the-box implementation for publishing the metrics
+
+- DropWizard metrics using a `MetricRegistry`
+- JSON file written in the bundle data directory
+- Log entries using the SLF4j API
+
+### JSON metrics file sample
+
+The following (truncated) JSON file exemplifies how the metrics are written
+
+```json
+{
+ "application": {
+ "startTimeMillis": 1587469534671,
+ "startDurationMillis": 14635
+ },
+ "bundles": [
+ {
+ "symbolicName": "org.osgi.util.pushstream",
+ "startTimeMillis": 1587469535933,
+ "startDurationMillis": 0
+ },
+ {
+ "symbolicName": "org.apache.aries.util",
+ "startTimeMillis": 1587469535935,
+ "startDurationMillis": 0
+ },
+ {
+ "symbolicName": "org.apache.felix.configadmin",
+ "startTimeMillis": 1587469536313,
+ "startDurationMillis": 58
+ }
+ ],
+ "services": [
+ {
+ "identifier": "jmx.objectname=org.apache.sling.classloader:name=FSClassLoader,type=ClassLoader",
+ "restarts": 2
+ }
+ ]
+}
+```
+
+Similar metrics are reported through the other collectors.
+
+## Usage
+
+1. Add the `org.apache.felix/org.apache.felix.metrics.osgi.collector` bundle and ensure that
+ it starts as early as possible
+1. Add the `org.apache.felix/org.apache.felix.metrics.osgi.consumers` bundle.
+1. Add the required bundles, either Apache Felix SystemReady or Apache Felix Health Checks
+1. Start up the application
diff --git a/metrics/osgi/collector/bnd.bnd b/metrics/osgi/collector/bnd.bnd
new file mode 100644
index 0000000..6dee250
--- /dev/null
+++ b/metrics/osgi/collector/bnd.bnd
@@ -0,0 +1,2 @@
+Import-Package: org.slf4j;resolution:=optional, *
+DynamicImport-Package: org.slf4j
\ No newline at end of file
diff --git a/metrics/osgi/collector/pom.xml b/metrics/osgi/collector/pom.xml
new file mode 100644
index 0000000..192f732
--- /dev/null
+++ b/metrics/osgi/collector/pom.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0"?>
+<!-- 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-bundle-parent</artifactId>
+ <version>38</version>
+ <relativePath />
+ </parent>
+
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.metrics.osgi.collector</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+
+ <name>Apache Felix OSGi Metrics Collector</name>
+ <description>
+ Collects metrics related to the OSGi framework and makes them available to consumers.
+ </description>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>biz.aQute.bnd</groupId>
+ <artifactId>bnd-baseline-maven-plugin</artifactId>
+ <configuration>
+ <failOnMissing>false</failOnMissing>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.annotation.versioning</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.annotation.bundle</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
+ </dependency>
+
+ <!-- note this is set to optional in bnd.bnd, to help the bundle start as soon as possible -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <!-- testing dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>3.3.3</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- IT dependencies -->
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-container-native</artifactId>
+ <version>${pax-exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-junit4</artifactId>
+ <version>${pax-exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-link-mvn</artifactId>
+ <version>${pax-exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.url</groupId>
+ <artifactId>pax-url-aether</artifactId>
+ <version>${pax-url.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <version>6.0.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.inject</groupId>
+ <artifactId>javax.inject</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.systemready</artifactId>
+ <version>0.4.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.healthcheck.api</artifactId>
+ <version>2.0.4</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.healthcheck.core</artifactId>
+ <version>2.0.8</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.healthcheck.generalchecks</artifactId>
+ <version>2.0.4</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ </dependency>
+ </dependencies>
+
+ <properties>
+ <pax-exam.version>4.13.2</pax-exam.version>
+ <pax-url.version>2.6.2</pax-url.version>
+ </properties>
+</project>
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/BundleStartDuration.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/BundleStartDuration.java
new file mode 100644
index 0000000..214b47b
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/BundleStartDuration.java
@@ -0,0 +1,45 @@
+/*
+ * 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.metrics.osgi;
+
+import java.time.Duration;
+import java.time.Instant;
+
+public final class BundleStartDuration {
+
+ private final String symbolicName;
+ private final Instant startingAt;
+ private final Duration startedAfter;
+
+ public BundleStartDuration(String symbolicName, Instant startingAt, Duration startedAfter) {
+ this.symbolicName = symbolicName;
+ this.startingAt = startingAt;
+ this.startedAfter = startedAfter;
+ }
+
+ public String getSymbolicName() {
+ return symbolicName;
+ }
+
+ public Instant getStartingAt() {
+ return startingAt;
+ }
+
+ public Duration getStartedAfter() {
+ return startedAfter;
+ }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/ServiceRestartCounter.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/ServiceRestartCounter.java
new file mode 100644
index 0000000..8be29ef
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/ServiceRestartCounter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.metrics.osgi;
+
+public final class ServiceRestartCounter {
+
+ private final String serviceIdentifier;
+ private final int serviceRestarts;
+
+ public ServiceRestartCounter(String serviceIdentifier, int serviceRestarts) {
+ this.serviceIdentifier = serviceIdentifier;
+ this.serviceRestarts = serviceRestarts;
+ }
+
+ /**
+ * @return a opaque service identifier, used for describing the service that has restarted
+ */
+ public String getServiceIdentifier() {
+ return serviceIdentifier;
+ }
+
+ public int getServiceRestarts() {
+ return serviceRestarts;
+ }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/StartupMetrics.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/StartupMetrics.java
new file mode 100644
index 0000000..d878766
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/StartupMetrics.java
@@ -0,0 +1,101 @@
+/*
+ * 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.metrics.osgi;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides metrics about the OSGi framework startup and associated services
+ *
+ * <p>The calculation of the application being "ready" is based on the Apache Felix SystemReady bundle and
+ * requires a proper configuration of all checks.</p>
+ */
+public final class StartupMetrics {
+
+ public static final class Builder {
+
+ private StartupMetrics startupMetrics = new StartupMetrics();
+
+ public static Builder withJvmStartup(Instant jvmStartup) {
+ Builder builder = new Builder();
+ builder.startupMetrics.jvmStartup = jvmStartup;
+ return builder;
+ }
+
+ public Builder withStartupTime(Duration startupTime) {
+ startupMetrics.startupTime = startupTime;
+ return this;
+ }
+
+ public Builder withBundleStartDurations(List<BundleStartDuration> bundleStartDurations) {
+ startupMetrics.bundleStartDurations = Collections.unmodifiableList(bundleStartDurations);
+ return this;
+ }
+
+ public Builder withServiceRestarts(List<ServiceRestartCounter> serviceRestarts) {
+ startupMetrics.serviceRestarts = Collections.unmodifiableList(serviceRestarts);
+ return this;
+ }
+
+ public StartupMetrics build() {
+ return startupMetrics;
+ }
+ }
+
+ private Instant jvmStartup;
+ private Duration startupTime;
+ private List<BundleStartDuration> bundleStartDurations;
+ private List<ServiceRestartCounter> serviceRestarts;
+
+ private StartupMetrics() { }
+
+ /**
+ * Returns the instant when the JVM has started
+ *
+ * <p>Note that this is different from the OSGi startup process, and may lead to unexpected results if the
+ * OSGi framework starts considerably later compared to the JVM.</p>
+ *
+ * @return the instant when the JVM has started
+ */
+ public Instant getJvmStartup() {
+ return jvmStartup;
+ }
+
+ /**
+ * @return the time between the {@link #getJvmStartup()} and the application being ready
+ */
+ public Duration getStartupTime() {
+ return startupTime;
+ }
+
+ /**
+ * @return all bundle start durations
+ */
+ public List<BundleStartDuration> getBundleStartDurations() {
+ return bundleStartDurations;
+ }
+
+ /**
+ * @return tracked services with at least one restart
+ */
+ public List<ServiceRestartCounter> getServiceRestarts() {
+ return serviceRestarts;
+ }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/StartupMetricsListener.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/StartupMetricsListener.java
new file mode 100644
index 0000000..07022d2
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/StartupMetricsListener.java
@@ -0,0 +1,38 @@
+/*
+ * 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.metrics.osgi;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * A listener that is notified of the startup metrics
+ *
+ * <p>The time of the notification can be delayed after the actual application start, as
+ * the implementation may choose to delay it to ensure that the startup is not affected
+ * by e.g. bouncing services.</p>
+ *
+ * <p>Listeners that register after the application startup will receive a notification anyway.</p>
+ *
+ */
+@ConsumerType
+public interface StartupMetricsListener {
+
+ /**
+ * @param metrics the startup metrics
+ */
+ void onStartupComplete(StartupMetrics metrics);
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/Activator.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/Activator.java
new file mode 100644
index 0000000..abce910
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/Activator.java
@@ -0,0 +1,51 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import org.osgi.annotation.bundle.Header;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+
+@Header(name = Constants.BUNDLE_ACTIVATOR, value = "${@class}")
+// avoid dependency to SCR so we can start early on
+public class Activator implements BundleActivator {
+
+ private BundleStartTimeCalculator bstc;
+ private StartupTimeCalculator stc;
+ private ServiceRestartCountCalculator srcc;
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+
+ bstc = new BundleStartTimeCalculator(context.getBundle().getBundleId());
+ context.addBundleListener(bstc);
+
+ srcc = new ServiceRestartCountCalculator();
+ context.addServiceListener(srcc);
+
+ stc = new StartupTimeCalculator(context, bstc, srcc);
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ stc.close();
+ context.removeServiceListener(srcc);
+ context.removeBundleListener(bstc);
+ }
+
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/BundleStartTimeCalculator.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/BundleStartTimeCalculator.java
new file mode 100644
index 0000000..449497f
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/BundleStartTimeCalculator.java
@@ -0,0 +1,112 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.felix.metrics.osgi.BundleStartDuration;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.Constants;
+import org.osgi.framework.SynchronousBundleListener;
+
+public class BundleStartTimeCalculator implements SynchronousBundleListener {
+
+ private Map<Long, StartTime> bundleToStartTime = new HashMap<>();
+ private Clock clock = Clock.systemUTC();
+ private final long ourBundleId;
+
+ public BundleStartTimeCalculator(long ourBundleId) {
+ this.ourBundleId = ourBundleId;
+ }
+
+ @Override
+ public void bundleChanged(BundleEvent event) {
+ Bundle bundle = event.getBundle();
+
+ // this bundle is already starting by the time this is invoked. We also can't get proper timing
+ // from the framework bundle
+
+ if ( bundle.getBundleId() == Constants.SYSTEM_BUNDLE_ID
+ || bundle.getBundleId() == ourBundleId ) {
+ return;
+ }
+
+ synchronized (bundleToStartTime) {
+
+ switch (event.getType()) {
+ case BundleEvent.STARTING:
+ bundleToStartTime.put(bundle.getBundleId(), new StartTime(bundle.getSymbolicName(), clock.millis()));
+ break;
+
+ case BundleEvent.STARTED:
+ StartTime startTime = bundleToStartTime.get(bundle.getBundleId());
+ if ( startTime == null ) {
+ Log.debug(getClass(), "No previous data for started bundle {}/{}", new Object[] { bundle.getBundleId(), bundle.getSymbolicName() });
+ return;
+ }
+ startTime.started(clock.millis());
+ break;
+
+ default: // nothing to do here
+ break;
+ }
+ }
+ }
+
+ public List<BundleStartDuration> getBundleStartDurations() {
+
+ synchronized (bundleToStartTime) {
+ return bundleToStartTime.values().stream()
+ .map( StartTime::toBundleStartDuration )
+ .collect( Collectors.toList() );
+ }
+ }
+
+ class StartTime {
+ private final String bundleSymbolicName;
+ private long startingTimestamp;
+ private long startedTimestamp;
+
+ public StartTime(String bundleSymbolicName, long startingTimestamp) {
+ this.bundleSymbolicName = bundleSymbolicName;
+ this.startingTimestamp = startingTimestamp;
+ }
+
+ public long getDuration() {
+ return startedTimestamp - startingTimestamp;
+ }
+
+ public String getBundleSymbolicName() {
+ return bundleSymbolicName;
+ }
+
+ public void started(long startedTimestamp) {
+ this.startedTimestamp = startedTimestamp;
+ }
+
+ public BundleStartDuration toBundleStartDuration() {
+ return new BundleStartDuration(bundleSymbolicName, Instant.ofEpochMilli(startingTimestamp), Duration.ofMillis(startedTimestamp - startingTimestamp));
+ }
+ }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/Log.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/Log.java
new file mode 100644
index 0000000..2c3e1ad
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/Log.java
@@ -0,0 +1,39 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Delegates to slf4j if available, otherwise silently fails
+ *
+ */
+public abstract class Log {
+ public static void debug(Class<?> caller, String message, Object... args) {
+ try {
+ Logger logger = LoggerFactory.getLogger(caller);
+ logger.debug(message, args);
+ } catch ( NoClassDefFoundError e ) {
+ // not available, just carry on
+ }
+ }
+
+ private Log() {
+
+ }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/ServiceRestartCountCalculator.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/ServiceRestartCountCalculator.java
new file mode 100644
index 0000000..bf6abbd
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/ServiceRestartCountCalculator.java
@@ -0,0 +1,239 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.felix.metrics.osgi.ServiceRestartCounter;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+
+public class ServiceRestartCountCalculator implements ServiceListener {
+
+ private static final String[] GENERAL_IDENTIFIER_PROPERTIES = new String[] { Constants.SERVICE_PID, "component.name", "jmx.objectname" };
+ private static final Map<String, Collection<String>> SPECIFIC_IDENTIFIER_PROPERTIES = new HashMap<>();
+ static {
+ SPECIFIC_IDENTIFIER_PROPERTIES.put("org.apache.sling.commons.metrics.Gauge", Arrays.asList("name"));
+ SPECIFIC_IDENTIFIER_PROPERTIES.put("org.apache.sling.spi.resource.provider.ResourceProvider", Arrays.asList("provider.root"));
+ SPECIFIC_IDENTIFIER_PROPERTIES.put("org.apache.sling.servlets.post.PostOperation", Arrays.asList("sling.post.operation"));
+ SPECIFIC_IDENTIFIER_PROPERTIES.put("javax.servlet.Servlet", Arrays.asList("felix.webconsole.label"));
+ SPECIFIC_IDENTIFIER_PROPERTIES.put("org.apache.felix.inventory.InventoryPrinter", Arrays.asList("felix.inventory.printer.name"));
+ }
+
+ private final Map<ServiceIdentifier, ServiceRegistrationsTracker> registrations = new HashMap<>();
+ private final Map<String, Integer> unidentifiedRegistrationsByClassName = new HashMap<>();
+
+ @Override
+ public void serviceChanged(ServiceEvent event) {
+
+ if ( shouldIgnore(event) )
+ return;
+
+ ServiceIdentifier id = tryFindIdFromGeneralProperties(event);
+ if ( id == null )
+ id = tryFindIdFromSpecificProperties(event);
+
+ if ( id == null ) {
+ logUnknownService(event);
+ if ( event.getType() == ServiceEvent.UNREGISTERING )
+ recordUnknownServiceUnregistration(event);
+ return;
+ }
+
+ ServiceRegistrationsTracker tracker;
+ synchronized (registrations) {
+
+ if ( event.getType() == ServiceEvent.REGISTERED ) {
+ tracker = registrations.computeIfAbsent(id, ServiceRegistrationsTracker::new);
+ tracker.registered();
+ } else if ( event.getType() == ServiceEvent.UNREGISTERING ) {
+
+ tracker = registrations.get(id);
+ if (tracker == null) {
+ Log.debug(getClass(), "Service with identifier {} was unregistered, but no previous registration data was found", id);
+ return;
+ }
+ tracker.unregistered();
+ }
+ }
+ }
+
+ private boolean shouldIgnore(ServiceEvent event) {
+
+ return event.getType() != ServiceEvent.REGISTERED && event.getType() != ServiceEvent.UNREGISTERING;
+ }
+
+ private ServiceIdentifier tryFindIdFromGeneralProperties(ServiceEvent event) {
+ for ( String identifierProp : GENERAL_IDENTIFIER_PROPERTIES ) {
+ Object identifierVal = event.getServiceReference().getProperty(identifierProp);
+ if ( identifierVal != null )
+ return new ServiceIdentifier(identifierProp, identifierVal.toString() );
+ }
+
+ return null;
+ }
+
+ private ServiceIdentifier tryFindIdFromSpecificProperties(ServiceEvent event) {
+ for ( Map.Entry<String, Collection<String>> entry : SPECIFIC_IDENTIFIER_PROPERTIES.entrySet() ) {
+ String[] classNames = (String[]) event.getServiceReference().getProperty(Constants.OBJECTCLASS);
+ for ( String className : classNames ) {
+ if ( entry.getKey().equals(className) ) {
+ StringBuilder propKey = new StringBuilder();
+ StringBuilder propValue = new StringBuilder();
+
+ for ( String idPropName : entry.getValue() ) {
+ Object idPropVal = event.getServiceReference().getProperty(idPropName);
+ if ( idPropVal != null ) {
+ propKey.append(idPropName).append('~');
+ propValue.append(idPropVal).append('~');
+ }
+ }
+
+ if ( propKey.length() != 0 ) {
+ propKey.deleteCharAt(propKey.length() - 1);
+ propValue.deleteCharAt(propValue.length() - 1);
+ ServiceIdentifier id = new ServiceIdentifier(propKey.toString(), propValue.toString());
+ id.setAdditionalInfo(Constants.OBJECTCLASS + "=" + Arrays.toString(classNames));
+ return id;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void logUnknownService(ServiceEvent event) {
+ if ( event.getType() == ServiceEvent.UNREGISTERING ) {
+ Map<String, Object> props = new HashMap<>();
+ for ( String propertyName : event.getServiceReference().getPropertyKeys() ) {
+ Object propVal = event.getServiceReference().getProperty(propertyName);
+ if ( propVal.getClass() == String[].class )
+ propVal = Arrays.toString((String[]) propVal);
+ props.put(propertyName, propVal);
+ }
+
+ Log.debug(getClass(), "Ignoring unregistration of service with props {}, as it has none of identifier properties {}", props, Arrays.toString(GENERAL_IDENTIFIER_PROPERTIES));
+ }
+ }
+
+ private void recordUnknownServiceUnregistration(ServiceEvent event) {
+ String[] classNames = (String[]) event.getServiceReference().getProperty(Constants.OBJECTCLASS);
+ synchronized (unidentifiedRegistrationsByClassName) {
+ for ( String className : classNames )
+ unidentifiedRegistrationsByClassName.compute(className, (k,v) -> v == null ? 1 : ++v);
+ }
+ }
+
+ // visible for testing
+ Map<ServiceIdentifier, ServiceRegistrationsTracker> getRegistrations() {
+ synchronized (registrations) {
+ return new HashMap<>(registrations);
+ }
+ }
+
+ // visible for testing
+ Map<String, Integer> getUnidentifiedRegistrationsByClassName() {
+ synchronized (unidentifiedRegistrationsByClassName) {
+ return unidentifiedRegistrationsByClassName;
+ }
+ }
+
+ public List<ServiceRestartCounter> getServiceRestartCounters() {
+ synchronized (registrations) {
+ return registrations.values().stream()
+ .filter( r -> r.restartCount() > 0)
+ .map( ServiceRegistrationsTracker::toServiceRestartCounter )
+ .collect(Collectors.toList());
+ }
+ }
+
+ static class ServiceIdentifier {
+ private String key;
+ private String value;
+ private String additionalInfo;
+
+ public ServiceIdentifier(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public void setAdditionalInfo(String additionalInfo) {
+ this.additionalInfo = additionalInfo;
+ }
+
+ @Override
+ public int hashCode() {
+
+ return Objects.hash(key, value);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ServiceIdentifier other = (ServiceIdentifier) obj;
+
+ return Objects.equals(key, other.key) && Objects.equals(value, other.value);
+ }
+
+ @Override
+ public String toString() {
+ return this.key + "=" + this.value + ( additionalInfo != null ? "(" + additionalInfo + ")" : "") ;
+ }
+ }
+
+ static class ServiceRegistrationsTracker {
+ private final ServiceIdentifier id;
+ private int registrationCount;
+ private int unregistrationCount;
+
+ public ServiceRegistrationsTracker(ServiceIdentifier id) {
+ this.id = id;
+ }
+
+ public void registered() {
+ this.registrationCount++;
+ }
+
+ public void unregistered() {
+ this.unregistrationCount++;
+ }
+
+ public int restartCount() {
+ if ( unregistrationCount == 0 )
+ return 0;
+
+ return registrationCount - 1;
+ }
+
+ public ServiceRestartCounter toServiceRestartCounter() {
+ return new ServiceRestartCounter(id.toString(), restartCount());
+ }
+ }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/ServiceTrackerCustomizerAdapter.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/ServiceTrackerCustomizerAdapter.java
new file mode 100644
index 0000000..bfa6054
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/ServiceTrackerCustomizerAdapter.java
@@ -0,0 +1,33 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+public abstract class ServiceTrackerCustomizerAdapter<S, T> implements ServiceTrackerCustomizer<S, T> {
+
+ @Override
+ public void modifiedService(ServiceReference<S> reference, T service) {
+ // nothing by default
+ }
+
+ @Override
+ public void removedService(ServiceReference<S> reference, T service) {
+ // nothing by default
+ }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/StartupTimeCalculator.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/StartupTimeCalculator.java
new file mode 100644
index 0000000..3fbeb42
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/impl/StartupTimeCalculator.java
@@ -0,0 +1,136 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import java.lang.management.ManagementFactory;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import org.apache.felix.metrics.osgi.BundleStartDuration;
+import org.apache.felix.metrics.osgi.ServiceRestartCounter;
+import org.apache.felix.metrics.osgi.StartupMetrics;
+import org.apache.felix.metrics.osgi.StartupMetricsListener;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class StartupTimeCalculator {
+
+ // delay activation until the system is marked as ready
+ // don't explicitly import the systemready class as this bundle must be started as early as possible in order
+ // to record bundle starup times
+
+ static final String PROPERTY_READINESS_DELAY = "org.apache.felix.metrics.osgi.additionalReadinessDelayMillis";
+ private ServiceTracker<Object, Object> readyTracker;
+ private ServiceTracker<StartupMetricsListener, StartupMetricsListener> listenersTracker;
+ private BundleStartTimeCalculator bundleCalculator;
+ private ServiceRestartCountCalculator serviceCalculator;
+ private ScheduledExecutorService executor;
+ private Future<Void> future;
+ private Supplier<StartupMetrics> metricsSupplier;
+ private long additionalReadinessDelayMillis = TimeUnit.SECONDS.toMillis(5);
+
+ public StartupTimeCalculator(BundleContext ctx, BundleStartTimeCalculator bundleCalculator, ServiceRestartCountCalculator serviceCalculator) throws InvalidSyntaxException {
+ executor = Executors.newScheduledThreadPool(1);
+ try {
+ String readinessDelay = ctx.getProperty(PROPERTY_READINESS_DELAY);
+ additionalReadinessDelayMillis = Long.parseLong(readinessDelay);
+ } catch ( NumberFormatException e) {
+ Log.debug(getClass(), "Failed parsing readiness delay", e);
+ }
+ this.bundleCalculator = bundleCalculator;
+ this.serviceCalculator = serviceCalculator;
+ this.readyTracker = new ServiceTracker<>(ctx,
+ ctx.createFilter("(|(" + Constants.OBJECTCLASS+"=org.apache.felix.systemready.SystemReady)(&(" + Constants.OBJECTCLASS+ "=org.apache.felix.hc.api.condition.Healthy)(tag=systemalive)))"),
+ new ServiceTrackerCustomizerAdapter<Object, Object>() {
+
+ @Override
+ public Object addingService(ServiceReference<Object> reference) {
+ if ( future == null )
+ future = calculate();
+ return ctx.getService(reference);
+ }
+
+ @Override
+ public void removedService(ServiceReference<Object> reference, Object service) {
+ if ( future != null && !future.isDone() ) {
+ boolean cancelled = future.cancel(false);
+ if ( cancelled ) {
+ metricsSupplier = null;
+ future = null;
+ }
+ }
+ }
+ });
+ this.readyTracker.open();
+
+ this.listenersTracker = new ServiceTracker<>(ctx, StartupMetricsListener.class, new ServiceTrackerCustomizerAdapter<StartupMetricsListener, StartupMetricsListener>() {
+ @Override
+ public StartupMetricsListener addingService(ServiceReference<StartupMetricsListener> reference) {
+ StartupMetricsListener service = ctx.getService(reference);
+ // TODO - there is still a minor race condition, between the supplier being set and the registration of services
+ // which can cause the listener to receive the event twice
+ if ( metricsSupplier != null )
+ service.onStartupComplete(metricsSupplier.get());
+ return service;
+ }
+ });
+ this.listenersTracker.open();
+ }
+
+ public void close() {
+ this.readyTracker.close();
+ }
+
+ private Future<Void> calculate() {
+
+ long currentMillis = Clock.systemUTC().millis();
+
+ return executor.schedule(() -> {
+ long startupMillis = ManagementFactory.getRuntimeMXBean().getStartTime();
+
+ Duration startupDuration = Duration.ofMillis(currentMillis - startupMillis);
+ Instant startupInstant = Instant.ofEpochMilli(startupMillis);
+ List<BundleStartDuration> bundleDurations = bundleCalculator.getBundleStartDurations();
+ List<ServiceRestartCounter> serviceRestarts = serviceCalculator.getServiceRestartCounters();
+
+ metricsSupplier = () -> {
+ return StartupMetrics.Builder.withJvmStartup(startupInstant)
+ .withStartupTime(startupDuration)
+ .withBundleStartDurations(bundleDurations)
+ .withServiceRestarts(serviceRestarts)
+ .build();
+ };
+
+ for ( StartupMetricsListener listener : listenersTracker.getServices(new StartupMetricsListener[0]) )
+ listener.onStartupComplete(metricsSupplier.get());
+
+ return null;
+ }, additionalReadinessDelayMillis, TimeUnit.MILLISECONDS);
+
+
+ }
+}
diff --git a/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/package-info.java b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/package-info.java
new file mode 100644
index 0000000..12d77b2
--- /dev/null
+++ b/metrics/osgi/collector/src/main/java/org/apache/felix/metrics/osgi/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.felix.metrics.osgi;
diff --git a/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/AbstractIT.java b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/AbstractIT.java
new file mode 100644
index 0000000..f7fc59a
--- /dev/null
+++ b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/AbstractIT.java
@@ -0,0 +1,126 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.CoreOptions.bundle;
+import static org.ops4j.pax.exam.CoreOptions.composite;
+import static org.ops4j.pax.exam.CoreOptions.frameworkProperty;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import org.apache.felix.metrics.osgi.StartupMetrics;
+import org.apache.felix.metrics.osgi.StartupMetricsListener;
+import org.apache.felix.metrics.osgi.impl.StartupTimeCalculator;
+import org.junit.Test;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+
+public abstract class AbstractIT {
+
+ private static final String TESTED_BUNDLE_LOCATION = "reference:file:target/classes";
+
+ @Inject
+ protected BundleContext bc;
+
+ @Configuration
+ public Option[] config() {
+ return options(
+ // lower timeout, we don't have bounces
+ frameworkProperty(StartupTimeCalculator.PROPERTY_READINESS_DELAY).value("100"),
+ bundle(TESTED_BUNDLE_LOCATION),
+ junitBundles(),
+ mavenBundle("org.apache.felix", "org.apache.felix.scr", "2.1.16"),
+ mavenBundle("org.osgi", "org.osgi.util.promise", "1.1.1"),
+ mavenBundle("org.osgi", "org.osgi.util.function", "1.1.0"),
+ composite(specificOptions())
+ );
+ }
+
+ protected abstract Option[] specificOptions();
+
+ @Test
+ public void registerListenerAfterSystemIsReady() throws InterruptedException {
+ runBasicTest(false);
+ }
+
+ @Test
+ public void registerListenerBeforeSystemIsReady() throws InterruptedException {
+ runBasicTest(true);
+ }
+
+ private void runBasicTest(boolean registerListenerFirst) throws InterruptedException {
+
+ Set<String> expectedBundleNames = Arrays.stream(bc.getBundles())
+ .filter( b -> b.getBundleId() != Constants.SYSTEM_BUNDLE_ID ) // no framework bundle
+ .filter( b -> !b.getLocation().equals(TESTED_BUNDLE_LOCATION) ) // not the bundle under test
+ .map ( b -> b.getSymbolicName() )
+ .filter( bsn -> ! bsn.startsWith("org.ops4j") ) // no ops4j bundles
+ .filter( bsn -> ! bsn.startsWith("PAXEXAM") ) // no ops4j bundles
+ .filter( bsn -> ! bsn.contains("geronimo-atinject")) // injected early on by Pax-Exam
+ .collect(Collectors.toSet());
+
+ WaitForResultsStartupMetricsListener listener = new WaitForResultsStartupMetricsListener();
+
+ // service that will be tracked as restarting
+ Runnable foo = () -> {};
+ Dictionary<String, Object> props = new Hashtable<>();
+ props.put(Constants.SERVICE_PID, "some.service.pid");
+ ServiceRegistration<Runnable> reg = bc.registerService(Runnable.class, foo, props);
+ reg.unregister();
+ reg = bc.registerService(Runnable.class, foo, props);
+ reg.unregister();
+
+ if ( registerListenerFirst ) {
+ markSystemReady();
+ bc.registerService(StartupMetricsListener.class, listener, null);
+ } else {
+ markSystemReady();
+ bc.registerService(StartupMetricsListener.class, listener, null);
+ }
+
+ StartupMetrics metrics = listener.getMetrics();
+
+ assertThat(metrics, notNullValue());
+ Set<String> trackedBundleNames = metrics.getBundleStartDurations().stream()
+ .map( bsd -> bsd.getSymbolicName())
+ .collect(Collectors.toSet());
+
+ assertTrue("Tracked bundle names " + trackedBundleNames + " did not contain " + expectedBundleNames,
+ trackedBundleNames.containsAll(expectedBundleNames));
+
+ assertThat("Service restarts", metrics.getServiceRestarts().size(), equalTo(1));
+ assertThat("Restarted component service identifier", metrics.getServiceRestarts().get(0).getServiceIdentifier(), equalTo(Constants.SERVICE_PID+"="+props.get(Constants.SERVICE_PID)));
+ }
+
+ protected abstract void markSystemReady();
+}
diff --git a/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/BundleStartTimeCalculatorTest.java b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/BundleStartTimeCalculatorTest.java
new file mode 100644
index 0000000..70d18e7
--- /dev/null
+++ b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/BundleStartTimeCalculatorTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.time.Instant;
+
+import org.apache.felix.metrics.osgi.BundleStartDuration;
+import org.apache.felix.metrics.osgi.impl.BundleStartTimeCalculator;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+
+public class BundleStartTimeCalculatorTest {
+
+ @Test
+ public void bundleStarted() {
+
+ Instant now = Instant.now();
+
+ BundleStartTimeCalculator c = new BundleStartTimeCalculator(1l);
+ Bundle mockBundle = newMockBundle(5l, "foo");
+ c.bundleChanged(new BundleEvent(BundleEvent.STARTING, mockBundle));
+ c.bundleChanged(new BundleEvent(BundleEvent.STARTED, mockBundle));
+
+ assertThat("Expected one entry for bundle durations",c.getBundleStartDurations().size(), CoreMatchers.equalTo(1));
+ BundleStartDuration duration = c.getBundleStartDurations().get(0);
+ assertThat("Bundle duration refers to wrong bundle symbolic name", duration.getSymbolicName(), CoreMatchers.equalTo("foo"));
+
+ assertTrue("Bundle STARTING time (" + duration.getStartingAt() + " must be after test start time(" + now + ")",
+ duration.getStartingAt().isAfter(now));
+ assertFalse("Bundle start duration (" + duration.getStartedAfter() + ") must not be negative",
+ duration.getStartedAfter().isNegative());
+ }
+
+ private Bundle newMockBundle(long id, String symbolicName) {
+
+ Bundle mockBundle = Mockito.mock(Bundle.class);
+ Mockito.when(mockBundle.getBundleId()).thenReturn(id);
+ Mockito.when(mockBundle.getSymbolicName()).thenReturn(symbolicName);
+ return mockBundle;
+ }
+}
diff --git a/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/HealthCheckSmokeIT.java b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/HealthCheckSmokeIT.java
new file mode 100644
index 0000000..f30fcdb
--- /dev/null
+++ b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/HealthCheckSmokeIT.java
@@ -0,0 +1,61 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import static org.ops4j.pax.exam.CoreOptions.frameworkProperty;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.inject.Inject;
+
+import org.apache.felix.hc.api.condition.Healthy;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.BundleContext;
+
+@RunWith(PaxExam.class)
+public class HealthCheckSmokeIT extends AbstractIT {
+
+ @Inject
+ private BundleContext bc;
+
+ @Override
+ protected Option[] specificOptions() {
+ return options(
+ frameworkProperty("org.apache.felix.http.enable").value("false"),
+ mavenBundle("org.apache.felix", "org.apache.felix.healthcheck.api", "2.0.4"),
+ mavenBundle("org.apache.felix", "org.apache.felix.healthcheck.core", "2.0.8"),
+ mavenBundle("org.apache.felix", "org.apache.felix.healthcheck.generalchecks", "2.0.4"),
+ mavenBundle("org.apache.felix", "org.apache.felix.http.servlet-api", "1.1.2"),
+ mavenBundle("org.apache.felix", "org.apache.felix.http.jetty", "4.0.18"),
+ mavenBundle("org.apache.commons", "commons-lang3", "3.9"),
+ mavenBundle("org.apache.felix", "org.apache.felix.eventadmin", "1.5.0"),
+ mavenBundle("org.apache.felix", "org.apache.felix.rootcause", "0.1.0")
+ );
+ }
+
+ @Override
+ protected void markSystemReady() {
+ Dictionary<String, Object> regProps = new Hashtable<>();
+ regProps.put("tag", "systemalive");
+ bc.registerService(Healthy.class, new Healthy() {}, regProps);
+ }
+}
diff --git a/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/ServiceRegistrationsTrackerTest.java b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/ServiceRegistrationsTrackerTest.java
new file mode 100644
index 0000000..e724829
--- /dev/null
+++ b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/ServiceRegistrationsTrackerTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import org.apache.felix.metrics.osgi.impl.ServiceRestartCountCalculator.ServiceIdentifier;
+import org.apache.felix.metrics.osgi.impl.ServiceRestartCountCalculator.ServiceRegistrationsTracker;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Constants;
+
+public class ServiceRegistrationsTrackerTest {
+
+ private ServiceRegistrationsTracker tracker;
+
+ @Before
+ public void prepare() {
+ tracker = new ServiceRegistrationsTracker(new ServiceIdentifier(Constants.SERVICE_PID, "foo"));
+ }
+
+ @Test
+ public void singleRegister() {
+ tracker.registered();
+ assertThat(tracker.restartCount(), equalTo(0));
+ }
+
+ @Test
+ public void registerUnregister() {
+ tracker.registered();
+ tracker.unregistered();
+ assertThat(tracker.restartCount(), equalTo(0));
+ }
+
+ @Test
+ public void singleRestart() {
+ tracker.registered();
+ tracker.unregistered();
+ tracker.registered();
+ assertThat(tracker.restartCount(), equalTo(1));
+ }
+
+ @Test
+ public void singleRestartAndUnregister() {
+ tracker.registered();
+ tracker.unregistered();
+ tracker.registered();
+ tracker.unregistered();
+ assertThat(tracker.restartCount(), equalTo(1));
+ }
+
+ @Test
+ public void twoRestarts() {
+ tracker.registered();
+ tracker.unregistered();
+ tracker.registered();
+ tracker.unregistered();
+ tracker.registered();
+ assertThat(tracker.restartCount(), equalTo(2));
+ }
+}
diff --git a/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/ServiceRestartCountCalculatorTest.java b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/ServiceRestartCountCalculatorTest.java
new file mode 100644
index 0000000..5b89ee8
--- /dev/null
+++ b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/ServiceRestartCountCalculatorTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.felix.metrics.osgi.impl.ServiceRestartCountCalculator;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+public class ServiceRestartCountCalculatorTest {
+
+ @Test
+ public void ignoredEventTypes() {
+
+ ServiceRestartCountCalculator srcc = new ServiceRestartCountCalculator();
+ srcc.serviceChanged(new ServiceEvent(ServiceEvent.MODIFIED, new DummyServiceReference<>(new HashMap<>())));
+ srcc.serviceChanged(new ServiceEvent(ServiceEvent.MODIFIED_ENDMATCH, new DummyServiceReference<>(new HashMap<>())));
+
+ assertThat(srcc.getRegistrations().size(), equalTo(0));
+ }
+
+ @Test
+ public void serviceWithServicePidProperty() {
+
+ assertServiceWithPropertyIsTracked(Constants.SERVICE_PID);
+ }
+
+ @Test
+ public void serviceWithComponentNameProperty() {
+
+ assertServiceWithPropertyIsTracked("component.name");
+ }
+
+ @Test
+ public void serviceWithJmxObjectNameProperty() {
+
+ assertServiceWithPropertyIsTracked("jmx.objectname");
+ }
+
+ @Test
+ public void metricsGaugesAreTracked() {
+ HashMap<String, Object> props = new HashMap<>();
+ props.put(Constants.OBJECTCLASS, new String[] { "org.apache.sling.commons.metrics.Gauge" });
+ props.put("name", "commons.threads.tp.script-cache-thread-pool.Name");
+ DummyServiceReference<Object> dsr = new DummyServiceReference<>(props);
+
+ ServiceRestartCountCalculator srcc = new ServiceRestartCountCalculator();
+ srcc.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, dsr));
+
+ assertThat(srcc.getRegistrations().size(), equalTo(1));
+ }
+
+ @Test
+ public void unknownServiceIsNotTracked() {
+ HashMap<String, Object> props = new HashMap<>();
+ props.put(Constants.OBJECTCLASS, new String[] { "foo" });
+ DummyServiceReference<Object> dsr = new DummyServiceReference<>(props);
+
+ ServiceRestartCountCalculator srcc = new ServiceRestartCountCalculator();
+ srcc.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, dsr));
+
+ assertThat(srcc.getRegistrations().size(), equalTo(0));
+ assertThat(srcc.getUnidentifiedRegistrationsByClassName().size(), equalTo(0));
+ }
+
+ @Test
+ public void unknownServiceUnregistrationsAreTracked() {
+ HashMap<String, Object> props = new HashMap<>();
+ props.put(Constants.OBJECTCLASS, new String[] { "foo", "bar" });
+ DummyServiceReference<Object> sr1 = new DummyServiceReference<>(props);
+
+ HashMap<String, Object> props2 = new HashMap<>();
+ props2.put(Constants.OBJECTCLASS, new String[] { "foo"} );
+ DummyServiceReference<Object> sr2 = new DummyServiceReference<>(props2);
+
+ ServiceRestartCountCalculator srcc = new ServiceRestartCountCalculator();
+ srcc.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, sr1));
+ srcc.serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, sr1));
+
+ srcc.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, sr2));
+ srcc.serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, sr2));
+
+ assertThat(srcc.getRegistrations().size(), equalTo(0));
+ Map<String, Integer> unidentifiedRegistrations = srcc.getUnidentifiedRegistrationsByClassName();
+ assertThat(unidentifiedRegistrations.size(), equalTo(2));
+ assertThat(unidentifiedRegistrations.get("foo"), equalTo(2));
+ assertThat(unidentifiedRegistrations.get("bar"), equalTo(1));
+ }
+
+ private void assertServiceWithPropertyIsTracked(String propertyName) {
+
+ HashMap<String, Object> props = new HashMap<>();
+ props.put(propertyName, new String[] { "foo.bar" });
+ DummyServiceReference<Object> dsr = new DummyServiceReference<>(props);
+
+ ServiceRestartCountCalculator srcc = new ServiceRestartCountCalculator();
+ srcc.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, dsr));
+
+ assertThat(srcc.getRegistrations().size(), CoreMatchers.equalTo(1));
+ }
+
+ static class DummyServiceRegistration<S> implements ServiceRegistration<S> {
+
+ private final DummyServiceReference<S> sr;
+
+ public DummyServiceRegistration(Map<String, Object> props) {
+ this.sr = new DummyServiceReference<>(props);
+ }
+
+ @Override
+ public ServiceReference<S> getReference() {
+ return sr;
+ }
+
+ @Override
+ public void setProperties(Dictionary<String, ?> properties) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public void unregister() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ static class DummyServiceReference<S> implements ServiceReference<S> {
+
+ private final Map<String, Object> props;
+
+ public DummyServiceReference(Map<String, Object> props) {
+ this.props = props;
+ }
+
+ @Override
+ public Object getProperty(String key) {
+ return props.get(key);
+ }
+
+ @Override
+ public String[] getPropertyKeys() {
+ return props.keySet().toArray(new String[0]);
+ }
+
+ @Override
+ public Bundle getBundle() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Bundle[] getUsingBundles() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isAssignableTo(Bundle bundle, String className) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int compareTo(Object reference) {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+
+}
diff --git a/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/SystemReadySmokeIT.java b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/SystemReadySmokeIT.java
new file mode 100644
index 0000000..2d9cfc2
--- /dev/null
+++ b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/SystemReadySmokeIT.java
@@ -0,0 +1,47 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+
+import javax.inject.Inject;
+
+import org.apache.felix.systemready.SystemReady;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.BundleContext;
+
+@RunWith(PaxExam.class)
+public class SystemReadySmokeIT extends AbstractIT {
+
+ @Inject
+ protected BundleContext bc;
+
+ @Override
+ protected Option[] specificOptions() {
+ return options(
+ mavenBundle("org.apache.felix", "org.apache.felix.systemready", "0.4.2"),
+ mavenBundle("org.apache.felix", "org.apache.felix.rootcause", "0.1.0")
+ );
+ }
+ protected void markSystemReady() {
+ bc.registerService(SystemReady.class, new SystemReady() {}, null);
+ }
+
+}
diff --git a/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/WaitForResultsStartupMetricsListener.java b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/WaitForResultsStartupMetricsListener.java
new file mode 100644
index 0000000..42bbba1
--- /dev/null
+++ b/metrics/osgi/collector/src/test/java/org/apache/felix/metrics/osgi/impl/WaitForResultsStartupMetricsListener.java
@@ -0,0 +1,45 @@
+/*
+ * 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.metrics.osgi.impl;
+
+import java.util.concurrent.CountDownLatch;
+
+import org.apache.felix.metrics.osgi.StartupMetrics;
+import org.apache.felix.metrics.osgi.StartupMetricsListener;
+
+class WaitForResultsStartupMetricsListener implements StartupMetricsListener {
+
+ private final CountDownLatch latch = new CountDownLatch(1);
+ private StartupMetrics metrics;
+
+ @Override
+ public void onStartupComplete(StartupMetrics metrics) {
+ this.metrics = metrics;
+ latch.countDown();
+ }
+
+ public StartupMetrics getMetrics() {
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(e);
+ }
+ return metrics;
+ }
+
+}
\ No newline at end of file
diff --git a/metrics/osgi/consumers/bnd.bnd b/metrics/osgi/consumers/bnd.bnd
new file mode 100644
index 0000000..0be6687
--- /dev/null
+++ b/metrics/osgi/consumers/bnd.bnd
@@ -0,0 +1 @@
+Conditional-Package: org.apache.felix.utils.json
\ No newline at end of file
diff --git a/metrics/osgi/consumers/pom.xml b/metrics/osgi/consumers/pom.xml
new file mode 100644
index 0000000..5b02583
--- /dev/null
+++ b/metrics/osgi/consumers/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<!-- 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-bundle-parent</artifactId>
+ <version>38</version>
+ <relativePath />
+ </parent>
+
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.metrics.osgi.consumers</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+
+ <name>Apache Felix OSGi Metrics Consumers</name>
+ <description>
+ Provides various out-of-the-box consumers for OSGi framework metrics.
+ </description>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>biz.aQute.bnd</groupId>
+ <artifactId>bnd-baseline-maven-plugin</artifactId>
+ <configuration>
+ <failOnMissing>false</failOnMissing>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.annotation.versioning</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.annotation.bundle</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.component.annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.metatype.annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ <version>3.2.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.metrics.osgi.collector</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.utils</artifactId>
+ <version>1.11.4</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>3.3.3</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/dropwizard/DropwizardMetricsListener.java b/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/dropwizard/DropwizardMetricsListener.java
new file mode 100644
index 0000000..7333576
--- /dev/null
+++ b/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/dropwizard/DropwizardMetricsListener.java
@@ -0,0 +1,85 @@
+/*
+ * 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.metrics.osgi.consumers.impl.dropwizard;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.felix.metrics.osgi.StartupMetrics;
+import org.apache.felix.metrics.osgi.StartupMetricsListener;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricRegistry;
+
+@Component
+@Designate(ocd = DropwizardMetricsListener.Config.class)
+public class DropwizardMetricsListener implements StartupMetricsListener {
+
+ @ObjectClassDefinition(name = "Apache Felix Dropwizard Startup Metrics Listener")
+ public @interface Config {
+ @AttributeDefinition(name = "Service Restart Threshold", description="Minimum number of service restarts during startup needed to create a metric for the service")
+ int serviceRestartThreshold() default 3;
+ @AttributeDefinition(name = "Slow Bundle Startup Threshold", description="Minimum bundle startup duration in milliseconds needed to create a metric for the bundle")
+ long slowBundleThresholdMillis() default 200;
+ }
+
+ private static final String APPLICATION_STARTUP_GAUGE_NAME = "osgi.application_startup_time_millis";
+ private static final String BUNDLE_STARTUP_GAUGE_NAME_PREFIX = "osgi.slow_bundle_startup_time_millis.";
+ private static final String SERVICE_RESTART_GAUGE_NAME_PREFIX = "osgi.excessive_service_restarts_count.";
+
+ @Reference
+ private MetricRegistry registry;
+
+ private int serviceRestartThreshold;
+ private long slowBundleThresholdMillis;
+ private List<String> registeredMetricNames = new ArrayList<>();
+
+ @Activate
+ protected void activate(Config cfg) {
+ this.serviceRestartThreshold = cfg.serviceRestartThreshold();
+ this.slowBundleThresholdMillis = cfg.slowBundleThresholdMillis();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ registeredMetricNames.forEach( m -> registry.remove(m) );
+ }
+
+ @Override
+ public void onStartupComplete(StartupMetrics event) {
+ register(APPLICATION_STARTUP_GAUGE_NAME, (Gauge<Long>) () -> event.getStartupTime().toMillis() );
+ event.getBundleStartDurations().stream()
+ .filter( bsd -> bsd.getStartedAfter().toMillis() >= slowBundleThresholdMillis )
+ .forEach( bsd -> register(BUNDLE_STARTUP_GAUGE_NAME_PREFIX + bsd.getSymbolicName(), (Gauge<Long>) () -> bsd.getStartedAfter().toMillis()));
+ event.getServiceRestarts().stream()
+ .filter( src -> src.getServiceRestarts() >= serviceRestartThreshold )
+ .forEach( src -> register(SERVICE_RESTART_GAUGE_NAME_PREFIX + src.getServiceIdentifier(), (Gauge<Integer>) src::getServiceRestarts) );
+ }
+
+ private void register(String name, Metric metric) {
+ registry.register(name, metric);
+ registeredMetricNames.add(name);
+ }
+}
diff --git a/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/json/JsonWritingMetricsListener.java b/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/json/JsonWritingMetricsListener.java
new file mode 100644
index 0000000..4174e39
--- /dev/null
+++ b/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/json/JsonWritingMetricsListener.java
@@ -0,0 +1,95 @@
+/*
+ * 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.metrics.osgi.consumers.impl.json;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import org.apache.felix.metrics.osgi.BundleStartDuration;
+import org.apache.felix.metrics.osgi.ServiceRestartCounter;
+import org.apache.felix.metrics.osgi.StartupMetrics;
+import org.apache.felix.metrics.osgi.StartupMetricsListener;
+import org.apache.felix.utils.json.JSONWriter;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+public class JsonWritingMetricsListener implements StartupMetricsListener {
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ private BundleContext ctx;
+
+ @Activate
+ protected void activate(BundleContext ctx) {
+ this.ctx = ctx;
+ }
+
+ @Override
+ public void onStartupComplete(StartupMetrics metrics) {
+
+ File metricsFile = ctx.getDataFile("startup-metrics-" + System.currentTimeMillis() + ".json");
+ if ( metricsFile == null ) {
+ logger.warn("Unable to get data file in the bundle area, startup metrics will not be written");
+ return;
+ }
+
+ try {
+ try ( FileWriter fw = new FileWriter(metricsFile)) {
+ JSONWriter w = new JSONWriter(fw);
+ w.object();
+ // application metrics
+ w.key("application");
+ w.object();
+ w.key("startTimeMillis").value(metrics.getJvmStartup().toEpochMilli());
+ w.key("startDurationMillis").value(metrics.getStartupTime().toMillis());
+ w.endObject();
+
+ // bundle metrics
+ w.key("bundles");
+ w.array();
+ for ( BundleStartDuration bsd : metrics.getBundleStartDurations() ) {
+ w.object();
+ w.key("symbolicName").value(bsd.getSymbolicName());
+ w.key("startTimeMillis").value(bsd.getStartingAt().toEpochMilli());
+ w.key("startDurationMillis").value(bsd.getStartedAfter().toMillis());
+ w.endObject();
+ }
+ w.endArray();
+
+ // service metrics
+ w.key("services");
+ w.array();
+ for ( ServiceRestartCounter src : metrics.getServiceRestarts() ) {
+ w.object();
+ w.key("identifier").value(src.getServiceIdentifier());
+ w.key("restarts").value(src.getServiceRestarts());
+ w.endObject();
+ }
+ w.endArray();
+
+ w.endObject();
+ }
+ } catch (IOException e) {
+ logger.warn("Failed wrting startup metrics", e);
+ }
+ }
+}
diff --git a/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/log/LoggingMetricsListener.java b/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/log/LoggingMetricsListener.java
new file mode 100644
index 0000000..e96619e
--- /dev/null
+++ b/metrics/osgi/consumers/src/main/java/org/apache/felix/metrics/osgi/consumers/impl/log/LoggingMetricsListener.java
@@ -0,0 +1,92 @@
+/*
+ * 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.metrics.osgi.consumers.impl.log;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.felix.metrics.osgi.BundleStartDuration;
+import org.apache.felix.metrics.osgi.ServiceRestartCounter;
+import org.apache.felix.metrics.osgi.StartupMetrics;
+import org.apache.felix.metrics.osgi.StartupMetricsListener;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+@Designate(ocd = LoggingMetricsListener.Config.class)
+public class LoggingMetricsListener implements StartupMetricsListener {
+
+ @ObjectClassDefinition(name = "Apache Felix Logging Startup Metrics Listener")
+ public @interface Config {
+
+ @AttributeDefinition(name = "Service Restart Threshold", description="Minimum number of service restarts during startup needed log the number of service restarts")
+ int serviceRestartThreshold() default 3;
+ @AttributeDefinition(name = "Slow Bundle Startup Threshold", description="Minimum bundle startup duration in milliseconds needed to log the bundle startup time")
+ long slowBundleThresholdMillis() default 200;
+ }
+
+ private int serviceRestartThreshold;
+ private long slowBundleThresholdMillis;
+
+ @Activate
+ protected void activate(Config cfg) {
+ this.serviceRestartThreshold = cfg.serviceRestartThreshold();
+ this.slowBundleThresholdMillis = cfg.slowBundleThresholdMillis();
+ }
+
+ @Override
+ public void onStartupComplete(StartupMetrics event) {
+ Logger log = LoggerFactory.getLogger(getClass());
+ log.info("Application startup completed in {}", event.getStartupTime());
+
+ List<BundleStartDuration> slowStartBundles = event.getBundleStartDurations().stream()
+ .filter( bsd -> bsd.getStartedAfter().toMillis() >= slowBundleThresholdMillis )
+ .collect(Collectors.toList());
+
+ if ( !slowStartBundles.isEmpty() && log.isInfoEnabled() ) {
+ StringBuilder logEntry = new StringBuilder();
+ logEntry.append("The following bundles started in more than ")
+ .append(slowBundleThresholdMillis)
+ .append(" milliseconds: \n");
+ slowStartBundles
+ .forEach( ssb -> logEntry.append("- ").append(ssb.getSymbolicName()).append(" : ").append(ssb.getStartedAfter()).append('\n'));
+
+ log.info(logEntry.toString());
+ }
+
+ List<ServiceRestartCounter> oftenRestartedServices = event.getServiceRestarts().stream()
+ .filter( src -> src.getServiceRestarts() >= serviceRestartThreshold )
+ .collect(Collectors.toList());
+
+ if ( !oftenRestartedServices.isEmpty() && log.isInfoEnabled() ) {
+ StringBuilder logEntry = new StringBuilder();
+ logEntry.append("The following services have restarted more than ")
+ .append(serviceRestartThreshold)
+ .append(" times during startup :\n");
+ oftenRestartedServices
+ .forEach(ors -> logEntry.append("- ").append(ors.getServiceIdentifier()).append(" : ").append(ors.getServiceRestarts()).append(" restarts\n"));
+
+ log.info(logEntry.toString());
+ }
+ }
+
+}
diff --git a/metrics/osgi/consumers/src/test/java/org/apache/felix/metrics/osgi/consumers/impl/json/JsonWritingMetricsListenerTest.java b/metrics/osgi/consumers/src/test/java/org/apache/felix/metrics/osgi/consumers/impl/json/JsonWritingMetricsListenerTest.java
new file mode 100644
index 0000000..bd877f4
--- /dev/null
+++ b/metrics/osgi/consumers/src/test/java/org/apache/felix/metrics/osgi/consumers/impl/json/JsonWritingMetricsListenerTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.metrics.osgi.consumers.impl.json;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+
+import org.apache.felix.metrics.osgi.BundleStartDuration;
+import org.apache.felix.metrics.osgi.ServiceRestartCounter;
+import org.apache.felix.metrics.osgi.StartupMetrics;
+import org.apache.felix.metrics.osgi.consumers.impl.json.JsonWritingMetricsListener;
+import org.apache.felix.utils.json.JSONParser;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mockito;
+import org.osgi.framework.BundleContext;
+
+public class JsonWritingMetricsListenerTest {
+
+ @Rule
+ public TemporaryFolder tmp = new TemporaryFolder();
+
+ @Test
+ public void metricsArePersisted() throws IOException {
+ // bridge the mock bundle context with the temporary folder
+ BundleContext mockBundleContext = mock(BundleContext.class);
+ when(mockBundleContext.getDataFile(Mockito.anyString())).thenAnswer( i -> tmp.newFile(i.getArgument(0, String.class)));
+
+ JsonWritingMetricsListener listener = new JsonWritingMetricsListener();
+ listener.activate(mockBundleContext);
+
+ StartupMetrics metrics = StartupMetrics.Builder
+ .withJvmStartup(Instant.now())
+ .withStartupTime(Duration.ofMillis(50))
+ .withBundleStartDurations(Arrays.asList(new BundleStartDuration("foo", Instant.now(), Duration.ofMillis(5))))
+ .withServiceRestarts(Arrays.asList(new ServiceRestartCounter("some.service", 1)))
+ .build();
+
+ listener.onStartupComplete(metrics);
+
+ File[] files = tmp.getRoot().listFiles();
+
+ assertThat("Bundle data area should hold one file", files.length, equalTo(1));
+
+ File metricsFile = files[0];
+ try ( FileInputStream fis = new FileInputStream(metricsFile)) {
+ JSONParser p = new JSONParser(fis);
+ assertThat(p.getParsed().keySet(), hasItem("application"));
+ assertThat(p.getParsed().keySet(), hasItem("bundles"));
+ assertThat(p.getParsed().keySet(), hasItem("services"));
+ }
+ }
+}
diff --git a/metrics/osgi/pom.xml b/metrics/osgi/pom.xml
new file mode 100644
index 0000000..0e6ac13
--- /dev/null
+++ b/metrics/osgi/pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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</groupId>
+ <artifactId>apache</artifactId>
+ <version>18</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix-metrics-osgi-builder</artifactId>
+ <packaging>pom</packaging>
+ <version>1</version>
+
+ <name>Apache Felix OSGi Metrics (Builder)</name>
+
+ <modules>
+ <module>collector</module>
+ <module>consumers</module>
+ </modules>
+</project>
+
diff --git a/scr/pom.xml b/scr/pom.xml
index 88a1347..a3c4371 100644
--- a/scr/pom.xml
+++ b/scr/pom.xml
@@ -30,7 +30,7 @@
Implementation of the Declarative Services specification 1.4
</description>
<artifactId>org.apache.felix.scr</artifactId>
- <version>2.1.21-SNAPSHOT</version>
+ <version>2.1.23-SNAPSHOT</version>
<scm>
<connection>scm:git:https://github.com/apache/felix-dev.git</connection>
<developerConnection>scm:git:https://github.com/apache/felix-dev.git</developerConnection>
@@ -144,7 +144,18 @@
<version>2.28.2</version>
<scope>test</scope>
</dependency>
-
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>2.9.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>biz.aQute.bnd</groupId>
+ <artifactId>biz.aQute.bndlib</artifactId>
+ <version>5.1.1</version>
+ <scope>test</scope>
+ </dependency>
<!-- Integration Testing with Pax Exam -->
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/Activator.java b/scr/src/main/java/org/apache/felix/scr/impl/Activator.java
index 5235f04..a419f43 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/Activator.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/Activator.java
@@ -43,6 +43,7 @@
import org.apache.felix.scr.impl.config.ScrConfigurationImpl;
import org.apache.felix.scr.impl.inject.internal.ClassUtils;
import org.apache.felix.scr.impl.logger.InternalLogger.Level;
+import org.apache.felix.scr.impl.logger.ScrLogManager;
import org.apache.felix.scr.impl.logger.ScrLogger;
import org.apache.felix.scr.impl.manager.ComponentHolder;
import org.apache.felix.scr.impl.metadata.ComponentMetadata;
@@ -114,14 +115,13 @@
{
m_context = context;
m_bundle = context.getBundle();
- // require the log service
- logger = new ScrLogger(m_configuration, m_context);
// set bundle context for PackageAdmin tracker
ClassUtils.setBundleContext( context );
// get the configuration
m_configuration.start( m_context ); //this will call restart, which calls super.start.
}
+
public void restart(boolean globalExtender)
{
m_componentMetadataStore = load(m_context, logger,
@@ -208,7 +208,7 @@
super.stop( context );
m_configuration.stop();
store(m_componentMetadataStore, context, logger, m_configuration.cacheMetadata());
- logger.closeTracker();
+ logger.close();
}
@Override
@@ -651,4 +651,17 @@
}
}
}
+
+ public void setLogger()
+ {
+ // TODO we only set the logger once
+ // If the need arises to be able to dynamically set the logger type
+ // then more work is needed to do that switch
+ // for now we only can configure ds.log.extension with context properties
+ if (logger == null)
+ {
+ logger = ScrLogManager.scr(m_context, m_configuration);
+ }
+
+ }
}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/BundleComponentActivator.java b/scr/src/main/java/org/apache/felix/scr/impl/BundleComponentActivator.java
index 1e880a3..1bf5108 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/BundleComponentActivator.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/BundleComponentActivator.java
@@ -199,7 +199,7 @@
throws ComponentException
{
// create a logger on behalf of the bundle
- this.logger = new BundleLogger(context.getBundle(), scrLogger);
+ this.logger = scrLogger.bundle(context.getBundle());
// keep the parameters for later
m_componentRegistry = componentRegistry;
m_componentActor = componentActor;
@@ -432,7 +432,7 @@
void validateAndRegister(ComponentMetadata metadata)
{
- final ComponentLogger componentLogger = new ComponentLogger(metadata, logger);
+ final ComponentLogger componentLogger = logger.component(m_bundle, metadata.getImplementationClassName(), metadata.getName());
ComponentRegistryKey key = null;
try
{
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/config/ScrConfigurationImpl.java b/scr/src/main/java/org/apache/felix/scr/impl/config/ScrConfigurationImpl.java
index f504238..6d6fd20 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/config/ScrConfigurationImpl.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/config/ScrConfigurationImpl.java
@@ -82,6 +82,7 @@
private boolean infoAsService;
private boolean cacheMetadata;
+ private boolean logExtension;
private long lockTimeout = DEFAULT_LOCK_TIMEOUT_MILLISECONDS;
@@ -178,6 +179,7 @@
serviceChangecountTimeout = DEFAULT_SERVICE_CHANGECOUNT_TIMEOUT_MILLISECONDS;
newGlobalExtender = false;
cacheMetadata = false;
+ logExtension = false;
}
else
{
@@ -190,6 +192,7 @@
serviceChangecountTimeout = getServiceChangecountTimeout();
newGlobalExtender = getDefaultGlobalExtender();
cacheMetadata = getDefaultCacheMetadata();
+ logExtension = getDefaultLogExtension();
}
}
else
@@ -210,6 +213,7 @@
newGlobalExtender = VALUE_TRUE.equalsIgnoreCase( String.valueOf( config.get( PROP_GLOBAL_EXTENDER) ) );
cacheMetadata = VALUE_TRUE.equalsIgnoreCase(
String.valueOf(config.get(PROP_CACHE_METADATA)));
+ logExtension = VALUE_TRUE.equalsIgnoreCase(String.valueOf(config.get(PROP_LOG_EXTENSION)));
}
if ( scrCommand != null )
{
@@ -218,13 +222,14 @@
oldGlobalExtender = this.globalExtender;
this.globalExtender = newGlobalExtender;
}
+ activator.setLogger();
if ( newGlobalExtender != oldGlobalExtender )
{
activator.restart( newGlobalExtender );
}
}
- /**
+ /**
* Returns the current log level.
* Note that this log level is not used with an R7 LogService implementation.
* @return
@@ -249,7 +254,8 @@
return keepInstances;
}
- @Override
+ @SuppressWarnings("deprecation")
+ @Override
public boolean infoAsService()
{
return infoAsService;
@@ -402,4 +408,14 @@
// default log level (errors only)
return Level.ERROR;
}
+
+ @Override
+ public boolean isLogExtension() {
+ return logExtension;
+ }
+
+ private boolean getDefaultLogExtension() {
+ return VALUE_TRUE.equalsIgnoreCase( bundleContext.getProperty( PROP_LOG_EXTENSION) );
+ }
+
}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/AbstractLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/AbstractLogger.java
deleted file mode 100644
index 87236c6..0000000
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/AbstractLogger.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * 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.scr.impl.logger;
-
-import java.text.MessageFormat;
-
-import org.apache.felix.scr.impl.logger.InternalLogger.Level;
-import org.osgi.framework.Bundle;
-
-/**
- * This is a common base for all loggers
- *
- */
-public abstract class AbstractLogger
-{
-
- /**
- * The prefix put for each log message
- */
- private volatile String prefix;
-
- AbstractLogger(final String prefix)
- {
- this.prefix = prefix;
- }
-
- void setPrefix(final String value)
- {
- this.prefix = value;
- }
-
- String getPrefix()
- {
- return this.prefix;
- }
-
- /**
- * Get the internal logger
- * @return The internal logger
- */
- abstract InternalLogger getLogger();
-
- /**
- * Returns {@code true} if logging for the given level is enabled.
- */
- public boolean isLogEnabled(final Level level)
- {
- final InternalLogger l = getLogger();
- return l.isLogEnabled(level);
- }
-
- /**
- * Method to actually emit the log message. If the LogService is available,
- * the message will be logged through the LogService. Otherwise the message
- * is logged to stdout (or stderr in case of LOG_ERROR level messages),
- *
- * @param level The log level to log the message at
- * @param pattern The {@code java.text.MessageFormat} message format
- * string for preparing the message
- * @param ex An optional <code>Throwable</code> whose stack trace is written,
- * @param arguments The format arguments for the <code>pattern</code>
- * string.
- */
- public boolean log(final Level level, final String pattern, final Throwable ex,
- final Object... arguments)
- {
- if ( isLogEnabled( level ) )
- {
- getLogger().log(level, format(pattern, arguments), ex);
- return true;
- }
- return false;
- }
-
- /**
- * Method to actually emit the log message. If the LogService is available,
- * the message will be logged through the LogService. Otherwise the message
- * is logged to stdout (or stderr in case of LOG_ERROR level messages),
- *
- * @param level The log level of the messages. This corresponds to the log
- * levels defined by the OSGi LogService.
- * @param message The message to print
- * @param ex The <code>Throwable</code> causing the message to be logged.
- */
- public boolean log(final Level level, final String message, final Throwable ex)
- {
- if ( isLogEnabled( level ) )
- {
- getLogger().log(level, prefix.concat(" ").concat(message), ex);
- return true;
- }
- return false;
- }
-
- static String getBundleIdentifier(final Bundle bundle)
- {
- final StringBuilder sb = new StringBuilder("bundle ");
- // symbolic name might be null
- if ( bundle.getSymbolicName() != null )
- {
- sb.append(bundle.getSymbolicName());
- sb.append(':');
- sb.append(bundle.getVersion());
- sb.append( " (" );
- sb.append( bundle.getBundleId() );
- sb.append( ")" );
- }
- else
- {
- sb.append( bundle.getBundleId() );
- }
-
- return sb.toString();
- }
-
- private String format( final String pattern, final Object... arguments )
- {
- final String message;
- if ( arguments == null || arguments.length == 0 )
- {
- message = pattern;
- }
- else
- {
- for(int i=0;i<arguments.length;i++)
- {
- if ( arguments[i] instanceof Bundle )
- {
- arguments[i] = getBundleIdentifier((Bundle)arguments[i]);
- }
- }
- message = MessageFormat.format( pattern, arguments );
- }
- return prefix.concat(message);
- }
-}
\ No newline at end of file
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/BundleLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/BundleLogger.java
index 241b9c8..6f9c6c2 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/BundleLogger.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/logger/BundleLogger.java
@@ -18,83 +18,14 @@
*/
package org.apache.felix.scr.impl.logger;
-import org.apache.felix.scr.impl.logger.InternalLogger.Level;
import org.osgi.framework.Bundle;
-import org.osgi.service.log.Logger;
-import org.osgi.service.log.LoggerFactory;
/**
* The {@code BundleLogger} defines a simple API to enable some logging on behalf of
* an extended bundle. This avoids that all clients doing logging on behalf of
* a component bundle need to pass in things like {@code BundleContext}.
*/
-public class BundleLogger extends LogServiceEnabledLogger
-{
- private final ScrLogger parent;
+public interface BundleLogger extends InternalLogger {
- public BundleLogger(final Bundle bundle, final ScrLogger parent)
- {
- super(bundle, parent.getLoggerFactoryTracker());
- this.parent = parent;
- }
-
- @Override
- InternalLogger getDefaultLogger()
- {
- return new InternalLogger()
- {
-
- @Override
- public void log(final Level level, final String message,
- final Throwable ex)
- {
- parent.getLogger().log(level, message, ex);
- }
-
- @Override
- public boolean isLogEnabled(final Level level)
- {
- return parent.getLogger().isLogEnabled(level);
- }
- };
- }
-
- int getTrackingCount()
- {
- return trackingCount;
- }
-
- InternalLogger getLogger(final String className)
- {
- if ( className != null )
- {
- final LoggerFactory factory = this.loggingFactoryTracker.getService();
- if (factory != null)
- {
- return new OSGiLogger(factory.getLogger(bundle, className, Logger.class));
- }
- }
- return this.getLogger();
- }
-
- @Override
- public boolean log(final Level level, final String pattern, final Throwable ex,
- final Object... arguments)
- {
- // delegate to parent if not logging
- if ( !super.log(level, pattern, ex, arguments) ) {
- return this.parent.log(level, pattern, ex, arguments);
- }
- return false;
- }
-
- @Override
- public boolean log(final Level level, final String message, final Throwable ex)
- {
- // delegate to parent if not logging
- if ( !super.log(level, message, ex) ) {
- return this.parent.log(level, message, ex);
- }
- return false;
- }
+ ComponentLogger component(Bundle m_bundle, String implementationClassName, String name);
}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/ComponentLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/ComponentLogger.java
index 13252c5..facf595 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/ComponentLogger.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/logger/ComponentLogger.java
@@ -18,98 +18,11 @@
*/
package org.apache.felix.scr.impl.logger;
-import org.apache.felix.scr.impl.logger.InternalLogger.Level;
-import org.apache.felix.scr.impl.metadata.ComponentMetadata;
-
/**
* The {@code ComponentLogger} is the logger to be used to log on behalf of a component.
* This avoids avoids that all clients doing logging on behalf of a component need to
* pass in things like {@code ComponentMetadata} or the component Id.
*/
-public class ComponentLogger extends AbstractLogger
-{
- private final String name;
-
- private final String className;
-
- private final BundleLogger parent;
-
- private volatile int trackingCount = -3;
-
- private volatile InternalLogger currentLogger;
-
- public ComponentLogger(final ComponentMetadata metadata, final BundleLogger parent)
- {
- super(""); // we set the prefix later
- this.parent = parent;
- if ( metadata.getName() != null )
- {
- this.name = metadata.getName();
- }
- else if ( metadata.getImplementationClassName() != null )
- {
- this.name = "implementation class " + metadata.getImplementationClassName();
- }
- else
- {
- this.name = "UNKNOWN";
- }
- if ( metadata.getImplementationClassName() != null )
- {
- this.className = metadata.getImplementationClassName();
- }
- else
- {
- this.className = null;
- }
- this.setComponentId(-1);
- }
-
- /**
- * Update the logger with the correct component id.
- * @param id The component id
- */
- public void setComponentId(final long id)
- {
- if ( id > -1 )
- {
- this.setPrefix(this.parent.getPrefix() + "[" + name + "(" + id + ")] : ");
- }
- else
- {
- this.setPrefix(this.parent.getPrefix() + "[" + name + "] : ");
- }
- }
-
- @Override
- InternalLogger getLogger()
- {
- if ( this.trackingCount < this.parent.getTrackingCount() )
- {
- this.currentLogger = this.parent.getLogger(this.className);
- this.trackingCount = this.parent.getTrackingCount();
- }
- return currentLogger;
- }
-
- @Override
- public boolean log(final Level level, final String pattern, final Throwable ex,
- final Object... arguments)
- {
- // delegate to parent if not logging
- if ( !super.log(level, pattern, ex, arguments) ) {
- return this.parent.log(level, pattern, ex, arguments);
- }
- return false;
- }
-
- @Override
- public boolean log(final Level level, final String message, final Throwable ex)
- {
- // delegate to parent if not logging
- if ( !super.log(level, message, ex) ) {
- return this.parent.log(level, message, ex);
- }
- return false;
- }
+public interface ComponentLogger extends InternalLogger {
+ void setComponentId(long m_componentId);
}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/ExtLogManager.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/ExtLogManager.java
new file mode 100644
index 0000000..1752987
--- /dev/null
+++ b/scr/src/main/java/org/apache/felix/scr/impl/logger/ExtLogManager.java
@@ -0,0 +1,79 @@
+/*
+ * 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.scr.impl.logger;
+
+import org.apache.felix.scr.impl.manager.ScrConfiguration;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Implements an extension to the SCR log manager that uses logger names to
+ * create a hierarchy of loggers. All messages will be logged via the SCR
+ * logger's bundle unlike the classic scr log manager that used the bundle's logger.
+ *
+ * <ul>
+ * <li>An ScrLogger will log with the name {@value #SCR_LOGGER_NAME}
+ * <li>A BundleLogger will log with the name {@value #SCR_LOGGER_PREFIX} + the
+ * bundle symbolic name
+ * <li>A ComponentLogger will log with the name {@value #SCR_LOGGER_PREFIX} +
+ * the bundle symbolic name + "." + component name
+ * </ul>
+ */
+class ExtLogManager extends ScrLogManager {
+ public static String SCR_LOGGER_NAME = "org.apache.felix.scr.impl";
+ public static String SCR_LOGGER_PREFIX = "org.apache.felix.scr.";
+ private final Bundle bundle;
+
+ ExtLogManager(BundleContext context, ScrConfiguration config) {
+ super(context, config);
+ this.bundle = context.getBundle();
+ }
+
+ @Override
+ public ScrLogger scr() {
+ return getLogger(bundle, SCR_LOGGER_NAME, ScrLoggerFacade.class);
+ }
+
+ @Override
+ public BundleLogger bundle(Bundle bundle) {
+ return getLogger(bundle, SCR_LOGGER_PREFIX.concat(bundle.getSymbolicName()), ScrLoggerFacade.class);
+ }
+
+ @Override
+ public ComponentLogger component(Bundle bundle, String implementationClass, String componentName) {
+
+ assert bundle != null;
+ assert bundle.getSymbolicName() != null : "scr requires recent bundles";
+ assert implementationClass != null;
+ assert componentName != null;
+
+ String loggerName = SCR_LOGGER_PREFIX.concat(bundle.getSymbolicName()).concat(".").concat(componentName);
+ ScrLoggerFacade logger = getLogger(bundle, loggerName, ScrLoggerFacade.class);
+ logger.setPrefix("["+componentName+"]");
+ return logger;
+ }
+
+ String componentPrefix(ScrLoggerFacade slf, long id) {
+ assert slf.prefix != null;
+ if ( slf.prefix.indexOf(')')<0)
+ return slf.prefix.replace("]", "(" + id + ")]");
+ else
+ return slf.prefix;
+ }
+}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/InternalLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/InternalLogger.java
index 2295dc9..4eca75f 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/InternalLogger.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/logger/InternalLogger.java
@@ -18,19 +18,93 @@
*/
package org.apache.felix.scr.impl.logger;
-public interface InternalLogger
-{
- public enum Level
- {
- AUDIT, ERROR, WARN, INFO, DEBUG, TRACE;
+import java.text.MessageFormat;
- public boolean implies(Level other)
- {
- return ordinal() >= other.ordinal();
- }
- }
+import org.apache.felix.scr.impl.manager.ScrConfiguration;
+import org.osgi.service.log.admin.LoggerAdmin;
- void log(Level level, String message, Throwable ex);
+/**
+ * Base interface for the different SCR Loggers. Since this is not used outside
+ * this package, it could be private. However, then Level should be standalone,
+ * which would change most files. So minimize the code change, it is kept public.
+ */
+public interface InternalLogger {
+ /**
+ * The level to log. This is aligned with the OSGi LogLevel
+ */
+ public enum Level {
+ AUDIT {
+ boolean err() {
+ return true;
+ }
+ },
+ ERROR {
+ boolean err() {
+ return true;
+ }
+ },
+ WARN, INFO, DEBUG, TRACE;
- boolean isLogEnabled(Level level);
+ /**
+ * Check if this log level is higher or the same of the other level.
+ *
+ * @param other
+ * the other level
+ * @return true if the level other should be logged
+ */
+ boolean implies(Level other) {
+ return ordinal() >= other.ordinal();
+ }
+
+ boolean err() {
+ return false;
+ }
+
+ }
+
+ /**
+ * Logs the message to an appropriate OSGi logger. If not such logger can be
+ * found then it will log to stderr for ERROR & AUDIT messages and stdout
+ * for other messages
+ *
+ * @param level
+ * only log when this level is implied by the current log
+ * level
+ * @param message
+ * the message to log
+ * @param ex
+ * a Throwable or null
+ */
+ void log(Level level, String message, Throwable ex);
+
+ /**
+ * Formats the message using the {@link MessageFormat} class, i.e. with {}
+ * place holders for the args. It then calls
+ * {@link #log(Level, String, Throwable)}.
+ *
+ * @param level
+ * only log when this level is implied by the current log
+ * level
+ * @param message
+ * the message to log
+ * @param ex
+ * a Throwable or null
+ * @param args
+ * the arguments to the {@link MessageFormat} formatting
+ */
+ void log(Level level, String message, Throwable ex, Object... args);
+
+ /**
+ * Answer true if the current logging level is enabled for the given level.
+ * For stdout/stderr fallback the logging level is defined by the
+ * {@link ScrConfiguration#getLogLevel()}. If there is an OSGi logger
+ * available then the logger name will define the log level via
+ * {@link LoggerAdmin}.
+ *
+ * @param level
+ * the level to check
+ * @return true if the given log level is enabled
+ */
+ boolean isLogEnabled(Level level);
+
}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/LogManager.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/LogManager.java
new file mode 100644
index 0000000..d5cd008
--- /dev/null
+++ b/scr/src/main/java/org/apache/felix/scr/impl/logger/LogManager.java
@@ -0,0 +1,236 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.LoggerFactory;
+import org.osgi.util.tracker.ServiceTracker;
+
+/*
+ * A class that tracks the LoggerFactory of a bundle and its associated Logger objects.
+ * When a bundle is stopped and/or the service is unregistered, the logger objects are cleaned.
+ *
+ * The basic technique is to use a facade. Instead of returning a log object, we return a facade. The
+ * methods delegate to the actual logger object. If there is no logger object, we create one.
+ *
+ * The LogDomain represents every bundle. Per LogDomain, we keep the facades. If the factory goes,
+ * we reset the facades.
+ */
+class LogManager extends ServiceTracker<Object, Object> implements BundleListener {
+
+ private static final String LOGGER_FACTORY_CLASS_NAME = "org.osgi.service.log.LoggerFactory";
+
+ final BundleContext scrContext;
+ final AtomicBoolean closed = new AtomicBoolean(false);
+
+ /*
+ * Locks access to guarded fields
+ */
+ class Lock {
+ final Map<Bundle, LogDomain> domains = new HashMap<>();
+ int trackingCount;
+ Object factory;
+ int ranking=0;
+
+ synchronized LogDomain getLogDomain(Bundle bundle) {
+ LogDomain domain = domains.get(bundle);
+ if (domain == null) {
+ domain = new LogDomain(bundle);
+ domains.put(bundle, domain);
+ }
+ return domain;
+ }
+
+ synchronized void removedFactory(Object service) {
+ if (this.factory == service) {
+ this.factory = null;
+ reset();
+ }
+ }
+
+ synchronized void setFactory(int ranking, Object service) {
+ if (this.factory == null) {
+ this.factory = service;
+ this.ranking = ranking;
+ } else if (this.ranking < ranking) {
+ this.factory = service;
+ this.ranking = ranking;
+ reset();
+ }
+ }
+
+ synchronized void reset() {
+ for (LogDomain domain : domains.values()) {
+ domain.reset();
+ }
+ }
+
+ synchronized Object getLogger(LoggerFacade facade, Bundle bundle, String name) {
+ if (factory == null)
+ return facade.logger = null;
+ else
+ return facade.logger = ((LoggerFactory) factory).getLogger(bundle, name, Logger.class);
+ }
+
+ synchronized LogDomain remove(Bundle bundle) {
+ return domains.remove(bundle);
+ }
+
+ synchronized void close() {
+ reset();
+ domains.clear();
+ }
+
+ }
+
+ final Lock lock = new Lock();
+
+ LogManager(BundleContext context) {
+ super(context, LOGGER_FACTORY_CLASS_NAME, null);
+ this.scrContext = context;
+ scrContext.addBundleListener(this);
+ }
+
+ @Override
+ public Object addingService(ServiceReference<Object> reference) {
+ Object service = super.addingService(reference);
+ Integer ranking = (Integer) reference.getProperty(Constants.SERVICE_RANKING);
+ if (ranking == null)
+ ranking = 0;
+ lock.setFactory(ranking, service);
+ return service;
+ }
+
+ @Override
+ public void removedService(ServiceReference<Object> reference, Object service) {
+ super.removedService(reference, service);
+ lock.removedFactory(service);
+ }
+
+ <T> T getLogger(Bundle bundle, String name, Class<T> type) {
+ return type.cast(lock.getLogDomain(bundle).getLogger(name));
+ }
+
+ @SuppressWarnings("resource")
+ @Override
+ public void bundleChanged(BundleEvent event) {
+ if (event.getType() == BundleEvent.STOPPED && !closed.get()) {
+ LogDomain domain = lock.remove(event.getBundle());
+ if (domain != null) {
+ domain.close();
+ }
+ }
+ }
+
+ /*
+ * Tracks a bundle's LoggerFactory service
+ */
+ class LogDomain
+ implements Closeable {
+
+ private final Bundle bundle;
+ private final Set<LoggerFacade> facades = new HashSet<>();
+
+ LogDomain(Bundle bundle) {
+ this.bundle = bundle;
+ }
+
+ private void reset() {
+ synchronized (facades) {
+ for (LoggerFacade facade : facades) {
+ facade.reset();
+ }
+ }
+ }
+
+ LoggerFacade getLogger(String name) {
+ LoggerFacade facade = createLoggerFacade(this, name);
+ synchronized (facades) {
+ facades.add(facade);
+ }
+ return facade;
+ }
+
+ @Override
+ public void close() {
+ reset();
+ }
+
+ }
+
+ class LoggerFacade {
+ private final String name;
+ private final LogDomain domain;
+ volatile Object logger;
+ volatile String prefix;
+
+ LoggerFacade(LogDomain logDomain, String name) {
+ this.domain = logDomain;
+ this.name = name;
+ }
+
+ void reset() {
+ logger = null;
+ }
+
+ Object getLogger() {
+ Object l = this.logger;
+ if (l == null) {
+ l = lock.getLogger(this, domain.bundle, name);
+ }
+ return l;
+ }
+
+ Bundle getBundle() {
+ return domain.bundle;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ }
+
+ public void close() {
+ if (closed.compareAndSet(false, true)) {
+ lock.close();
+ super.close();
+ this.context.removeBundleListener(this);
+ }
+ }
+
+ LoggerFacade createLoggerFacade(LogDomain logDomain, String name) {
+ assert !closed.get();
+ return new LoggerFacade(logDomain, name);
+ }
+
+}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/LogServiceEnabledLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/LogServiceEnabledLogger.java
deleted file mode 100644
index 933b3e5..0000000
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/LogServiceEnabledLogger.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.scr.impl.logger;
-
-import org.osgi.framework.Bundle;
-import org.osgi.service.log.Logger;
-import org.osgi.service.log.LoggerFactory;
-import org.osgi.util.tracker.ServiceTracker;
-
-/**
- * This abstract class adds support for using a LogService
- * (or LoggerFactory for R7+).
- */
-abstract class LogServiceEnabledLogger extends AbstractLogger
-{
- // the log service to log messages to
- protected final ServiceTracker<LoggerFactory, LoggerFactory> loggingFactoryTracker;
-
- protected final Bundle bundle;
-
- private volatile InternalLogger currentLogger;
-
- protected volatile int trackingCount = -2;
-
- public LogServiceEnabledLogger(final Bundle bundle, ServiceTracker<LoggerFactory, LoggerFactory> loggingFactoryTracker)
- {
- super(getBundleIdentifier(bundle));
- this.bundle = bundle;
- this.loggingFactoryTracker = loggingFactoryTracker;
- }
-
- @Override
- InternalLogger getLogger()
- {
- if (this.trackingCount < this.loggingFactoryTracker.getTrackingCount())
- {
- final LoggerFactory factory = this.loggingFactoryTracker.getService();
- if (factory == null)
- {
- this.currentLogger = this.getDefaultLogger();
- }
- else
- {
- this.currentLogger = new OSGiLogger(
- factory.getLogger(bundle, Logger.ROOT_LOGGER_NAME, Logger.class));
- }
- this.trackingCount = this.loggingFactoryTracker.getTrackingCount();
- }
- return currentLogger;
- }
-
- abstract InternalLogger getDefaultLogger();
-
- ServiceTracker<LoggerFactory, LoggerFactory> getLoggerFactoryTracker()
- {
- return loggingFactoryTracker;
- }
-}
\ No newline at end of file
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/OSGiLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/OSGiLogger.java
deleted file mode 100644
index 1a9fc07..0000000
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/OSGiLogger.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.scr.impl.logger;
-
-import org.osgi.service.log.Logger;
-
-final class OSGiLogger implements InternalLogger
-{
-
- private final Logger logger;
-
- public OSGiLogger(Logger logger)
- {
- this.logger = logger;
- }
-
- public void log(Level level, String message, Throwable ex)
- {
- if ( ex == null )
- {
- switch ( level )
- {
- case AUDIT : logger.audit(message); break;
- case ERROR : logger.error(message); break;
- case WARN : logger.warn(message); break;
- case INFO : logger.info(message); break;
- case DEBUG : logger.debug(message); break;
- case TRACE : logger.trace(message); break;
- default : logger.debug(message);
- }
- }
- else
- {
- switch ( level )
- {
- case AUDIT : logger.audit(message, ex); break;
- case ERROR : logger.error(message, ex); break;
- case WARN : logger.warn(message, ex); break;
- case INFO : logger.info(message, ex); break;
- case DEBUG : logger.debug(message, ex); break;
- case TRACE : logger.trace(message, ex); break;
- default : logger.debug(message, ex);
- }
- }
- }
-
- public boolean isLogEnabled(Level level)
- {
- switch (level)
- {
- case AUDIT: return true;
- case ERROR: return logger.isErrorEnabled();
- case WARN: return logger.isWarnEnabled();
- case INFO: return logger.isInfoEnabled();
- case DEBUG: return logger.isDebugEnabled();
- case TRACE: return logger.isTraceEnabled();
- default: return logger.isDebugEnabled();
- }
- }
-}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogManager.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogManager.java
new file mode 100644
index 0000000..1aa1f70
--- /dev/null
+++ b/scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogManager.java
@@ -0,0 +1,303 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+
+import org.apache.felix.scr.impl.logger.InternalLogger.Level;
+import org.apache.felix.scr.impl.manager.ScrConfiguration;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.Logger;
+
+/**
+ * Implements a SCR based log manager. This class was needed to not change the
+ * whole codebase. It looks very similar to the old logging but leverages the
+ * {@link LogManager}. This class implements the existing behavior and the
+ * {@link ExtLogManager} implements the extension behavior. The {@link #scr()}
+ * method makes this distinction based on the configuration.
+ */
+public class ScrLogManager extends LogManager {
+
+ private final Bundle bundle;
+ private final ScrConfiguration config;
+
+ /**
+ * Get a new log manager based on the configuration.
+ *
+ * @param context
+ * the bundle context of the SCR bundle
+ * @param config
+ * the SCR configuration
+ * @return a proper ScrLogManager
+ */
+
+ public static ScrLogger scr(BundleContext context, ScrConfiguration config) {
+ ScrLogManager manager;
+ if (config.isLogExtension())
+ manager = new ExtLogManager(context, config);
+ else
+ manager = new ScrLogManager(context, config);
+ manager.open();
+ return manager.scr();
+ }
+
+ ScrLogManager(BundleContext context, ScrConfiguration config) {
+ super(context);
+ this.config = config;
+ this.bundle = context.getBundle();
+ }
+
+ /**
+ * This logger is used for the main code of SCR. This will use the SCR
+ * bundle & the {@link Logger#ROOT_LOGGER_NAME}
+ *
+ * @return an Scr Logger.
+ */
+ public ScrLogger scr() {
+ ScrLoggerFacade scrl = super.getLogger(bundle, Logger.ROOT_LOGGER_NAME, ScrLoggerFacade.class);
+ scrl.setPrefix(getBundleIdentifier(bundle));
+ return scrl;
+ }
+
+ /**
+ * This logger is used for the logging on a per bundle basis. This will use
+ * the target bundle & the {@link Logger#ROOT_LOGGER_NAME}
+ *
+ * @param bundle
+ * the target bundle
+ * @return a logger suitable to log bundle entries
+ */
+ public BundleLogger bundle(Bundle bundle) {
+ ScrLoggerFacade logger = getLogger(bundle, Logger.ROOT_LOGGER_NAME, ScrLoggerFacade.class);
+ logger.setPrefix(getBundleIdentifier(bundle));
+ return logger;
+ }
+
+ /**
+ * This logger is used for the logging on a per bundle basis. This will use
+ * the target bundle & the implementation class as logger name.
+ *
+ * @param bundle
+ * the target bundle
+ * @return a logger suitable to log bundle entries
+ */
+ public ComponentLogger component(Bundle bundle, String implementationClass, String name) {
+
+ // assert bundle != null;
+ // assert bundle.getSymbolicName() != null : "scr requires recent
+ // bundles";
+ // assert implementationClass != null;
+ // assert name != null;
+
+ ScrLoggerFacade facade = getLogger(bundle, implementationClass, ScrLoggerFacade.class);
+ facade.setComponentId(-1);
+ return (ComponentLogger) facade;
+ }
+
+ class ScrLoggerFacade extends LoggerFacade implements InternalLogger, ScrLogger, BundleLogger, ComponentLogger {
+ ScrLoggerFacade(LogDomain logDomain, String name) {
+ super(logDomain, name);
+ }
+
+ @Override
+ public void setComponentId(long id) {
+ setPrefix(componentPrefix(this, id));
+ }
+
+ public boolean isLogEnabled(Level level) {
+
+ // assert !closed.get();
+
+ Object checkLogger = getLogger();
+ if (checkLogger != null) {
+ Logger logger = (Logger) checkLogger;
+ switch (level) {
+ case AUDIT:
+ return true;
+ case ERROR:
+ return logger.isErrorEnabled();
+ case WARN:
+ return logger.isWarnEnabled();
+ case INFO:
+ return logger.isInfoEnabled();
+ case TRACE:
+ return logger.isTraceEnabled();
+ case DEBUG:
+ default:
+ return logger.isDebugEnabled();
+ }
+ } else {
+ return getLogLevel().implies(level);
+ }
+ }
+
+ @Override
+ public void log(Level level, String format, Throwable ex, Object... arguments) {
+ if (isLogEnabled(level))
+ log0(level, format(format, arguments), ex);
+ }
+
+ @Override
+ public void log(Level level, String message, Throwable ex) {
+ if (isLogEnabled(level))
+ log0(level, message, ex);
+ }
+
+ void log0(Level level, String message, Throwable ex) {
+ if (prefix != null && prefix.length() > 0) {
+ message = prefix.concat(" ").concat(message);
+ }
+ Object checkLogger = getLogger();
+ if (checkLogger != null) {
+ Logger logger = (Logger) checkLogger;
+ if (ex == null) {
+ switch (level) {
+ case AUDIT:
+ logger.audit(message);
+ break;
+ case ERROR:
+ logger.error(message);
+ break;
+ case WARN:
+ logger.warn(message);
+ break;
+ case INFO:
+ logger.info(message);
+ break;
+ case TRACE:
+ logger.trace(message);
+ break;
+ case DEBUG:
+ default:
+ logger.debug(message);
+ }
+ } else {
+ switch (level) {
+ case AUDIT:
+ logger.audit(message, ex);
+ break;
+ case ERROR:
+ logger.error(message, ex);
+ break;
+ case WARN:
+ logger.warn(message, ex);
+ break;
+ case INFO:
+ logger.info(message, ex);
+ break;
+ case TRACE:
+ logger.trace(message, ex);
+ break;
+ case DEBUG:
+ default:
+ logger.debug(message, ex);
+ }
+ }
+ } else {
+ StringWriter buf = new StringWriter();
+ String l = String.format("%-5s", level);
+ buf.append(l).append(" : ").append(message);
+ if (ex != null) {
+ try (PrintWriter pw = new PrintWriter(buf)) {
+ pw.println();
+ ex.printStackTrace(pw);
+ }
+ }
+
+ @SuppressWarnings("resource")
+ PrintStream out = level.err() ? System.err : System.out;
+ out.println(buf);
+ }
+ }
+
+ void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ @Override
+ public ComponentLogger component(Bundle bundle, String implementationClassName, String name) {
+ // assert !closed.get();
+ return ScrLogManager.this.component(bundle, implementationClassName, name);
+ }
+
+ @Override
+ public BundleLogger bundle(Bundle bundle) {
+ // assert !closed.get();
+ return ScrLogManager.this.bundle(bundle);
+ }
+
+ @Override
+ public void close() {
+ // assert !closed.get();
+ ScrLogManager.this.close();
+ }
+ };
+
+ LoggerFacade createLoggerFacade(LogDomain logDomain, String name) {
+ // assert !closed.get();
+ return new ScrLoggerFacade(logDomain, name);
+ }
+
+ Level getLogLevel() {
+ return config.getLogLevel();
+ }
+
+ String getBundleIdentifier(final Bundle bundle) {
+ final StringBuilder sb = new StringBuilder("bundle ");
+
+ if (bundle.getSymbolicName() != null) {
+ sb.append(bundle.getSymbolicName());
+ sb.append(':');
+ sb.append(bundle.getVersion());
+ sb.append(" (");
+ sb.append(bundle.getBundleId());
+ sb.append(")");
+ } else {
+ sb.append(bundle.getBundleId());
+ }
+
+ return sb.toString();
+ }
+
+ String componentPrefix(ScrLoggerFacade slf, long id) {
+ if (id >= 0) {
+ return getBundleIdentifier(slf.getBundle()) + "[" + slf.getName() + "(" + id + ")] :";
+ } else {
+ return getBundleIdentifier(slf.getBundle()) + "[" + slf.getName() + "] :";
+ }
+ }
+
+ String format(final String pattern, final Object... arguments) {
+ if (arguments == null || arguments.length == 0) {
+ return pattern;
+ } else {
+ for (int i = 0; i < arguments.length; i++) {
+ if (arguments[i] instanceof Bundle) {
+ arguments[i] = getBundleIdentifier((Bundle) arguments[i]);
+ }
+ }
+ return MessageFormat.format(pattern, arguments);
+ }
+ }
+
+}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogger.java
index 9ef2562..cbf8a42 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogger.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/logger/ScrLogger.java
@@ -16,43 +16,23 @@
* specific language governing permissions and limitations
* under the License.
*/
+
package org.apache.felix.scr.impl.logger;
-import org.apache.felix.scr.impl.manager.ScrConfiguration;
-import org.osgi.framework.BundleContext;
-import org.osgi.service.log.LoggerFactory;
-import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.framework.Bundle;
/**
- * This is the "global" logger used by the implementation for all logging
- * not directly related to an extended bundle (and its components)
+ * Logger used by SCR main code
*/
-public class ScrLogger extends LogServiceEnabledLogger
-{
- private final ScrConfiguration config;
- public ScrLogger(final ScrConfiguration config, BundleContext context)
- {
- super(context.getBundle(), getTracker(context));
- this.config = config;
- }
+public interface ScrLogger extends InternalLogger {
- @Override
- InternalLogger getDefaultLogger()
- {
- return new StdOutLogger(config);
- }
+ /**
+ * Create a bundle logger
+ */
+ BundleLogger bundle(Bundle bundle);
- private static ServiceTracker<LoggerFactory, LoggerFactory> getTracker(
- BundleContext context)
- {
- ServiceTracker<LoggerFactory, LoggerFactory> tracker = new ServiceTracker<>(
- context, "org.osgi.service.log.LoggerFactory", null);
- tracker.open();
- return tracker;
- }
-
- public void closeTracker()
- {
- getLoggerFactoryTracker().close();
- }
-}
\ No newline at end of file
+ /**
+ * Close the log manager
+ */
+ void close();
+}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/logger/StdOutLogger.java b/scr/src/main/java/org/apache/felix/scr/impl/logger/StdOutLogger.java
deleted file mode 100644
index 627b812..0000000
--- a/scr/src/main/java/org/apache/felix/scr/impl/logger/StdOutLogger.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.scr.impl.logger;
-
-import java.io.PrintStream;
-
-import org.apache.felix.scr.impl.manager.ScrConfiguration;
-
-/**
- * This logger logs to std out / err
- */
-class StdOutLogger implements InternalLogger
-{
-
- private final ScrConfiguration config;
-
- public StdOutLogger(ScrConfiguration config)
- {
- this.config = config;
- }
-
- @Override
- public void log(final Level level, final String message, final Throwable ex)
- {
- if ( isLogEnabled(level) )
- {
- // output depending on level
- final PrintStream out = (level == Level.ERROR) ? System.err : System.out;
-
- // level as a string
- final StringBuilder buf = new StringBuilder();
- switch (level)
- {
- case AUDIT:
- buf.append("AUDIT");
- break;
- case TRACE:
- buf.append("TRACE");
- break;
- case DEBUG:
- buf.append( "DEBUG: " );
- break;
- case INFO:
- buf.append( "INFO : " );
- break;
- case WARN:
- buf.append( "WARN : " );
- break;
- case ERROR:
- buf.append( "ERROR: " );
- break;
- default:
- buf.append( "UNK : " );
- break;
- }
-
- buf.append(message);
-
- final String msg = buf.toString();
-
- if ( ex == null )
- {
- out.println(msg);
- }
- else
- {
- // keep the message and the stacktrace together
- synchronized ( out )
- {
- out.println( msg );
- ex.printStackTrace( out );
- }
- }
- }
- }
-
- @Override
- public boolean isLogEnabled(Level level)
- {
- return config.getLogLevel().implies(level);
-
- }
-}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java b/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java
index 065e515..8d0b650 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/manager/DependencyManager.java
@@ -22,6 +22,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
@@ -805,7 +806,11 @@
private class SingleDynamicCustomizer extends AbstractCustomizer
{
- private RefPair<S, T> refPair;
+ private RefPair<S, T> currentRefPair;
+ private RefPair<S, T> bindingRefPair;
+ // generally should be very small
+ private Collection<RefPair<S, T>> queuedRefPairs = new ArrayList<>();
+ private Thread bindingThread;
private int trackingCount;
@Override
@@ -820,7 +825,7 @@
}
@Override
- public void addedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
+ public void addedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair , int trackingCount,
int serviceCount, ExtendedServiceEvent event)
{
m_componentManager.getLogger().log(Level.DEBUG,
@@ -831,35 +836,12 @@
{
if (isActive())
{
- boolean invokeBind = false;
+
ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker = getTracker();
Object monitor = tracker == null ? null : tracker.tracked();
if (monitor != null)
{
- synchronized (monitor)
- {
- invokeBind = this.refPair == null || (!isReluctant()
- && refPair.getRef().compareTo(this.refPair.getRef()) > 0);
- }
- }
- if (invokeBind)
- {
- m_componentManager.invokeBindMethod(DependencyManager.this, refPair, trackingCount);
- if (!refPair.isFailed())
- {
- if (this.refPair != null)
- {
- m_componentManager.invokeUnbindMethod(DependencyManager.this, this.refPair,
- trackingCount);
- closeRefPair();
- }
- }
- else if (cardinalitySatisfied(0))
- {
- m_componentManager.registerMissingDependency(DependencyManager.this, serviceReference,
- trackingCount);
- }
- this.refPair = refPair;
+ tryInvokeBind(tracker, monitor, refPair, trackingCount);
}
}
else if (isTrackerOpened() && cardinalityJustSatisfied(serviceCount))
@@ -879,6 +861,165 @@
}
}
+ private void tryInvokeBind(
+ ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker,
+ Object monitor, RefPair<S, T> next, int trackingCount)
+ {
+ boolean checkQueue = false;
+ do
+ {
+ try
+ {
+ while ((next = tryInvokeBind0(tracker, monitor, next,
+ trackingCount)) != null)
+ {
+ }
+ }
+ finally
+ {
+ // be sure to clean up the bindingThread state
+ synchronized (monitor)
+ {
+ if (bindingThread != null
+ && bindingThread.equals(Thread.currentThread()))
+ {
+ // check that another thread didn't give us more work to do since
+ // the time we gave up the lock in tryInvokeBind0
+ if (queuedRefPairs.isEmpty())
+ {
+ // working thread is done, make sure state is cleared
+ bindingRefPair = null;
+ bindingThread = null;
+ }
+ else
+ {
+ next = getBestFromQueue();
+ // NOTE - getBestFromQueue cannot return null at this point
+ // because queuedRefPairs is not empty; always loop
+ // around to tryInvokeBind again in this case
+ checkQueue = true;
+ }
+ }
+ }
+ }
+ }
+ while (checkQueue);
+ }
+
+ private RefPair<S, T> getBestFromQueue()
+ {
+ RefPair<S, T> currentBest = null;
+ for (RefPair<S, T> betterCandidate : queuedRefPairs)
+ {
+ if (currentBest == null
+ || betterCandidate.getRef().compareTo(currentBest.getRef()) > 0)
+ {
+ currentBest = betterCandidate;
+ }
+ }
+ queuedRefPairs.clear();
+ return currentBest;
+ }
+
+ private RefPair<S, T> tryInvokeBind0(
+ ServiceTracker<T, RefPair<S, T>, ExtendedServiceEvent> tracker,
+ Object monitor, RefPair<S, T> refPair , int trackingCount)
+ {
+ boolean invokeBind = false;
+ RefPair<S, T> current;
+ synchronized (monitor)
+ {
+ current = this.currentRefPair;
+ invokeBind = current == null || current.isDeleted() || (!isReluctant()
+ && refPair.getRef().compareTo(current.getRef()) > 0);
+ if (invokeBind)
+ {
+ if (bindingThread != null && !bindingThread.equals(Thread.currentThread()))
+ {
+ // this thread "lost" see if it is better than the current bindingRefPair
+ if (refPair.getRef().compareTo(bindingRefPair.getRef()) > 0)
+ {
+ // got a better option, add it to the queue
+ queuedRefPairs.add(refPair);
+ }
+ return null;
+ }
+ // this thread "won" it gets to do the work
+ bindingRefPair = refPair;
+ bindingThread = Thread.currentThread();
+ }
+ }
+
+ if (invokeBind)
+ {
+ boolean invokedUnbind = false;
+ m_componentManager.invokeBindMethod(DependencyManager.this,
+ bindingRefPair, trackingCount);
+ if (!bindingRefPair.isFailed())
+ {
+ if (current != null)
+ {
+ m_componentManager.invokeUnbindMethod(DependencyManager.this,
+ current, trackingCount);
+ invokedUnbind = true;
+ ungetService(current);
+ }
+ }
+ else if (cardinalitySatisfied(0))
+ {
+ m_componentManager.registerMissingDependency(DependencyManager.this,
+ bindingRefPair.getRef(), trackingCount);
+ }
+ RefPair<S, T> next = null;
+ synchronized (monitor)
+ {
+ if (!queuedRefPairs.isEmpty())
+ {
+ // One of more threads offered better options
+ // Find the best option
+ next = getBestFromQueue();
+ this.bindingRefPair = next;
+ if (invokedUnbind)
+ {
+ // unbind was invoked clear the current RefPair
+ this.currentRefPair = null;
+ }
+ }
+ else
+ {
+ if (bindingRefPair.isDeleted())
+ {
+ // what we just bound got unregistered
+ // look for next best service
+ Iterator<RefPair<S, T>> iNext = getTracker().getTracked(null,
+ new AtomicInteger()).values().iterator();
+ next = iNext.hasNext() ? iNext.next() : null;
+ this.bindingRefPair = next;
+ if (invokedUnbind)
+ {
+ // unbind was invoked clear the current RefPair
+ this.currentRefPair = null;
+ }
+ }
+ else
+ {
+ // all done; set the current to the ref bound
+ this.currentRefPair = bindingRefPair;
+ this.bindingRefPair = null;
+ this.bindingThread = null;
+ return null;
+ }
+ }
+ }
+ // unbind the last refPair that we bound to try again with the next candidate
+ m_componentManager.invokeUnbindMethod(DependencyManager.this, refPair,
+ trackingCount);
+ ungetService(refPair);
+ return next;
+ }
+ return null;
+ }
+
@Override
public void modifiedService(ServiceReference<T> serviceReference, RefPair<S, T> refPair, int trackingCount,
ExtendedServiceEvent event)
@@ -893,7 +1034,7 @@
{
synchronized (monitor)
{
- invokeUpdated = isActive() && refPair == this.refPair;
+ invokeUpdated = isActive() && refPair == this.currentRefPair;
}
}
if (invokeUpdated)
@@ -914,7 +1055,6 @@
m_componentManager.getLogger().log(Level.DEBUG,
"dm {0} tracking {1} SingleDynamic removed {2} (enter)",
null, getName(), trackingCount, serviceReference );
- refPair.markDeleted();
boolean deactivate = false;
boolean untracked = true;
RefPair<S, T> oldRefPair = null;
@@ -925,29 +1065,28 @@
{
synchronized (monitor)
{
- if (refPair == this.refPair && isActive())
+ refPair.markDeleted();
+ if (refPair == this.currentRefPair && isActive())
{
if (!getTracker().isEmpty())
{
- AtomicInteger trackingCount2 = new AtomicInteger();
SortedMap<ServiceReference<T>, RefPair<S, T>> tracked = getTracker().getTracked(
- true, //TODO true here looks odd.
- trackingCount2);
+ null,
+ new AtomicInteger());
nextRefPair = tracked.values().iterator().next();
}
//n.b. we cannot use cardinalitySatisfied( serviceCount ) here as the call may come from an old tracker during target change.
if (isEffectivelyOptional() || nextRefPair != null)
{
- oldRefPair = this.refPair;
- this.refPair = null;
+ oldRefPair = this.currentRefPair;
}
else
{
deactivate = true; //required and no replacement service, deactivate
}
}
- else if (!cardinalitySatisfied() && this.refPair == null)
+ else if (!cardinalitySatisfied() && this.currentRefPair == null)
{
deactivate = true;
}
@@ -955,31 +1094,47 @@
}
if (nextRefPair != null)
{
- m_componentManager.invokeBindMethod(DependencyManager.this, nextRefPair, trackingCount);
+ tryInvokeBind(tracker, monitor, nextRefPair, trackingCount);
}
if (oldRefPair != null)
{
this.trackingCount = trackingCount;
- m_componentManager.invokeUnbindMethod(DependencyManager.this, oldRefPair, trackingCount);
- synchronized (getTracker().tracked())
+ synchronized (monitor)
{
- this.refPair = nextRefPair;
+ if (oldRefPair != this.currentRefPair)
+ {
+ oldRefPair = null;
+ // make sure the tryInvokeBind found something to bind
+ if (this.currentRefPair == null)
+ {
+ // must deactivate if we didn't find anything to bind
+ deactivate = !isEffectivelyOptional();
+ }
+ }
+ else
+ {
+ this.currentRefPair = null;
+ }
+ }
+ if (oldRefPair != null)
+ {
+ m_componentManager.invokeUnbindMethod(DependencyManager.this,
+ oldRefPair, trackingCount);
+ ungetService(oldRefPair);
}
tracked(trackingCount);
untracked = false;
}
- else if (deactivate)
+
+ if (deactivate)
{
this.trackingCount = trackingCount;
tracked(trackingCount);
untracked = false;
deactivateComponentManager();
}
- if (oldRefPair != null)
- {
- ungetService(oldRefPair);
- }
+
if (untracked) // not ours
{
this.trackingCount = trackingCount;
@@ -1009,7 +1164,7 @@
if (!tracked.isEmpty())
{
refPair = tracked.values().iterator().next();
- this.refPair = refPair;
+ this.currentRefPair = refPair;
}
}
}
@@ -1035,11 +1190,11 @@
private void closeRefPair()
{
- if (refPair != null)
+ if (currentRefPair != null)
{
- ungetService(refPair);
+ ungetService(currentRefPair);
}
- refPair = null;
+ currentRefPair = null;
}
@Override
@@ -1052,7 +1207,9 @@
synchronized (monitor)
{
trackingCount.set(this.trackingCount);
- return refPair == null ? Collections.<RefPair<S, T>> emptyList() : Collections.singleton(refPair);
+ return currentRefPair == null
+ ? Collections.<RefPair<S, T>> emptyList()
+ : Collections.singleton(currentRefPair);
}
}
else
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/manager/ScrConfiguration.java b/scr/src/main/java/org/apache/felix/scr/impl/manager/ScrConfiguration.java
index 77881e0..65a5254 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/manager/ScrConfiguration.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/manager/ScrConfiguration.java
@@ -64,12 +64,17 @@
long DEFAULT_STOP_TIMEOUT_MILLISECONDS = 60000;
String PROP_LOGLEVEL = "ds.loglevel";
+ /**
+ * See {@link #isLogExtension()}
+ */
+ String PROP_LOG_EXTENSION = "ds.log.extension";
String PROP_GLOBAL_EXTENDER="ds.global.extender";
String PROP_SERVICE_CHANGECOUNT_TIMEOUT = "ds.service.changecount.timeout";
String PROP_CACHE_METADATA = "ds.cache.metadata";
+
/**
* Returns the current log level.
@@ -102,4 +107,11 @@
boolean cacheMetadata();
+
+ /**
+ * If true, use a logging extension. The extension can be incompatible with the OSGi specification.
+ * @return true if extension allowed
+ */
+ boolean isLogExtension();
+
}
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/logger/LogService.java b/scr/src/test/java/org/apache/felix/scr/impl/logger/LogService.java
new file mode 100644
index 0000000..17fb479
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/impl/logger/LogService.java
@@ -0,0 +1,347 @@
+/*
+ * 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.scr.impl.logger;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.log.LogLevel;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.LoggerConsumer;
+import org.osgi.service.log.LoggerFactory;
+
+import aQute.lib.collections.MultiMap;
+
+public class LogService implements ServiceFactory<LoggerFactory> {
+ public BundleContext context;
+ public final List<LogEntry> entries = new ArrayList<>();
+ public final Map<String, LogLevel> levels = new HashMap<>();
+ public LogLevel defaultLogLevel = LogLevel.DEBUG;
+ public List<LoggerFactory> factories = new ArrayList<>();
+ public MultiMap<Bundle, Logger> loggers = new MultiMap<>();
+
+ public ServiceRegistration<LoggerFactory> registration;
+ private int ranking;
+
+ public static class LogEntry {
+
+ @Override
+ public String toString() {
+ return "LogEntry [level=" + level + ", format=" + format + ", args=" + Arrays.toString(args) + ", bundle="
+ + bundle + ", loggername=" + loggername + "]";
+ }
+
+ public LogLevel level;
+ public String format;
+ public Object[] args;
+ public Bundle bundle;
+ public String loggername;
+
+ public LogEntry(Bundle bundle, String loggername, LogLevel level, String format, Object[] args) {
+ this.bundle = bundle;
+ this.loggername = loggername;
+ this.level = level;
+ this.format = format;
+ this.args = args;
+ }
+
+ }
+
+ public LogService register() {
+ Hashtable<String,Object> properties = new Hashtable<>();
+ properties.put( Constants.SERVICE_RANKING, ranking);
+ registration = context.registerService(LoggerFactory.class, this, properties);
+ return this;
+ }
+
+ public void unregister() {
+ assert registration != null;
+ registration.unregister();
+ registration = null;
+ }
+
+ public LogService(BundleContext context) {
+ this.context = context;
+ }
+
+ @Override
+ public LoggerFactory getService(final Bundle bundle, ServiceRegistration<LoggerFactory> registration) {
+ LoggerFactory factory = new LoggerFactory() {
+
+ @Override
+ public Logger getLogger(String name) {
+ return getLogger(bundle, name, Logger.class);
+ }
+
+ @Override
+ public Logger getLogger(Class<?> clazz) {
+ return getLogger(bundle, clazz.getName(), Logger.class);
+ }
+
+ @Override
+ public <L extends Logger> L getLogger(String name, Class<L> loggerType) {
+ return getLogger(bundle, name, loggerType);
+ }
+
+ @Override
+ public <L extends Logger> L getLogger(Class<?> clazz, Class<L> loggerType) {
+ return getLogger(bundle, clazz.getName(), loggerType);
+ }
+
+ @Override
+ public <L extends Logger> L getLogger(final Bundle bundle, final String name, Class<L> loggerType) {
+ Logger logger = new Logger() {
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean isTraceEnabled() {
+ return getLevel().implies(LogLevel.TRACE);
+ }
+
+ @Override
+ public void trace(String message) {
+ log(LogLevel.TRACE, message);
+ }
+
+ @Override
+ public void trace(String format, Object arg) {
+ log(LogLevel.TRACE, format, arg);
+ }
+
+ @Override
+ public void trace(String format, Object arg1, Object arg2) {
+ log(LogLevel.TRACE, format, arg1, arg2);
+ }
+
+ @Override
+ public void trace(String format, Object... arguments) {
+ log(LogLevel.TRACE, format, arguments);
+ }
+
+ @Override
+ public <E extends Exception> void trace(LoggerConsumer<E> consumer) throws E {
+ consumer.accept(this);
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return getLevel().implies(LogLevel.DEBUG);
+ }
+
+ @Override
+ public void debug(String message) {
+ log(LogLevel.DEBUG, message);
+ }
+
+ @Override
+ public void debug(String format, Object arg) {
+ log(LogLevel.DEBUG, format, arg);
+ }
+
+ @Override
+ public void debug(String format, Object arg1, Object arg2) {
+ log(LogLevel.DEBUG, format, arg1, arg2);
+ }
+
+ @Override
+ public void debug(String format, Object... arguments) {
+ log(LogLevel.DEBUG, format, arguments);
+ }
+
+ @Override
+ public <E extends Exception> void debug(LoggerConsumer<E> consumer) throws E {
+ consumer.accept(this);
+ }
+
+ @Override
+ public boolean isInfoEnabled() {
+ return getLevel().implies(LogLevel.INFO);
+ }
+
+ @Override
+ public void info(String message) {
+ log(LogLevel.INFO, message);
+ }
+
+ @Override
+ public void info(String format, Object arg) {
+ log(LogLevel.INFO, format, arg);
+ }
+
+ @Override
+ public void info(String format, Object arg1, Object arg2) {
+ log(LogLevel.INFO, format, arg1, arg2);
+ }
+
+ @Override
+ public void info(String format, Object... arguments) {
+ log(LogLevel.INFO, format, arguments);
+ }
+
+ @Override
+ public <E extends Exception> void info(LoggerConsumer<E> consumer) throws E {
+ consumer.accept(this);
+ }
+
+ @Override
+ public boolean isWarnEnabled() {
+ return getLevel().implies(LogLevel.WARN);
+ }
+
+ @Override
+ public void warn(String message) {
+ log(LogLevel.WARN, message);
+ }
+
+ @Override
+ public void warn(String format, Object arg) {
+ log(LogLevel.WARN, format, arg);
+ }
+
+ @Override
+ public void warn(String format, Object arg1, Object arg2) {
+ log(LogLevel.WARN, format, arg1, arg2);
+ }
+
+ @Override
+ public void warn(String format, Object... arguments) {
+ log(LogLevel.WARN, format, arguments);
+ }
+
+ @Override
+ public <E extends Exception> void warn(LoggerConsumer<E> consumer) throws E {
+ consumer.accept(this);
+ }
+
+ @Override
+ public boolean isErrorEnabled() {
+ return getLevel().implies(LogLevel.ERROR);
+ }
+
+ @Override
+ public void error(String message) {
+ log(LogLevel.ERROR, message);
+ }
+
+ @Override
+ public void error(String format, Object arg) {
+ log(LogLevel.ERROR, format, arg);
+ }
+
+ @Override
+ public void error(String format, Object arg1, Object arg2) {
+ log(LogLevel.ERROR, format, arg1, arg2);
+ }
+
+ @Override
+ public void error(String format, Object... arguments) {
+ log(LogLevel.ERROR, format, arguments);
+ }
+
+ @Override
+ public <E extends Exception> void error(LoggerConsumer<E> consumer) throws E {
+ consumer.accept(this);
+ }
+
+ @Override
+ public void audit(String message) {
+ log(LogLevel.AUDIT, message);
+ }
+
+ @Override
+ public void audit(String format, Object arg) {
+ log(LogLevel.AUDIT, format, arg);
+ }
+
+ @Override
+ public void audit(String format, Object arg1, Object arg2) {
+ log(LogLevel.AUDIT, format, arg1, arg2);
+ }
+
+ @Override
+ public void audit(String format, Object... arguments) {
+ log(LogLevel.AUDIT, format, arguments);
+ }
+
+ public void log(LogLevel level, String format, Object... args) {
+ if (getLevel().implies(level)) {
+ entries.add(new LogEntry(bundle, name, level, format, args));
+ }
+ }
+
+ public LogLevel getLevel() {
+ String ancestor = name;
+ while (true) {
+ LogLevel logLevel = levels.get(ancestor);
+ if (logLevel != null) {
+ return logLevel;
+ }
+
+ int n = ancestor.lastIndexOf('.');
+ if (n < 0) {
+ LogLevel logLevel2 = levels.get(Logger.ROOT_LOGGER_NAME);
+ if ( logLevel2 != null)
+ return logLevel2;
+
+ return defaultLogLevel;
+ }
+
+ ancestor = ancestor.substring(0, n);
+ }
+ }
+
+ };
+ loggers.add(bundle, logger);
+ return loggerType.cast(logger);
+ }
+ };
+ factories.add(factory);
+ return factory;
+ }
+
+ @Override
+ public void ungetService(Bundle bundle, ServiceRegistration<LoggerFactory> registration, LoggerFactory service) {
+ factories.add(service);
+ }
+
+ public void clear() {
+ loggers.clear();
+ entries.clear();
+ factories.clear();
+ levels.clear();
+ }
+
+ public LogService ranking(int ranking) {
+ this.ranking = ranking;
+ return this;
+ }
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/logger/LoggerTest.java b/scr/src/test/java/org/apache/felix/scr/impl/logger/LoggerTest.java
new file mode 100644
index 0000000..e0714da
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/impl/logger/LoggerTest.java
@@ -0,0 +1,504 @@
+/*
+ * 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.scr.impl.logger;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+import org.apache.felix.scr.impl.logger.InternalLogger.Level;
+import org.apache.felix.scr.impl.logger.LogManager.LoggerFacade;
+import org.apache.felix.scr.impl.logger.LogService.LogEntry;
+import org.apache.felix.scr.impl.manager.ScrConfiguration;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.launch.Framework;
+import org.osgi.service.log.LogLevel;
+import org.osgi.service.log.Logger;
+
+import aQute.bnd.osgi.Builder;
+import aQute.bnd.osgi.Jar;
+import aQute.bnd.osgi.JarResource;
+import aQute.lib.io.IO;
+
+public class LoggerTest {
+
+ private Framework framework;
+ private Bundle scr;
+ private Bundle component;
+ private Bundle log;
+ private File tmp = IO.getFile("target/tmp/loggertest");
+
+ @Before
+ public void setup() throws Exception {
+ Map<String, String> configuration = new HashMap<>();
+ configuration.put(Constants.FRAMEWORK_STORAGE, tmp.getAbsolutePath());
+ configuration.put(Constants.FRAMEWORK_STORAGE_CLEAN, Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT);
+ framework = ServiceLoader.load(org.osgi.framework.launch.FrameworkFactory.class).iterator().next()
+ .newFramework(configuration);
+ framework.init();
+ framework.start();
+
+ scr = framework.getBundleContext().installBundle("scr", makeBundle("scr").openInputStream());
+ component = framework.getBundleContext().installBundle("component",
+ makeBundle("component").openInputStream());
+ log = framework.getBundleContext().installBundle("component", makeBundle("log").openInputStream());
+ scr.start();
+ component.start();
+ log.start();
+ }
+
+ @After
+ public void after() throws Exception {
+ framework.stop();
+ framework.waitForStop(100000);
+ }
+
+ static class Buf extends FilterOutputStream {
+ StringWriter buffer = new StringWriter();
+ final PrintStream stream;
+
+ public Buf(PrintStream out) {
+ super(out);
+ this.stream = out;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ stream.write(b);
+ buffer.write(b);
+ }
+
+ PrintStream out() {
+ return new PrintStream(this);
+ }
+
+ public void reset() {
+ buffer = new StringWriter();
+ }
+ }
+
+ @Test
+ public void formatTest() {
+ LogService l = new LogService(log.getBundleContext());
+ l.register();
+ ScrConfiguration config = mock(ScrConfiguration.class);
+ when(config.getLogLevel()).thenReturn(Level.DEBUG);
+ ExtLogManager elm = new ExtLogManager(scr.getBundleContext(), config);
+ elm.open();
+
+ ComponentLogger clog = elm.component(component, "i.c", "c");
+
+ clog.log(Level.ERROR, "error {0} {1} {2}", null, "a", 1, scr);
+ LogEntry le = l.entries.get(0);
+ assertThat(le.format).isEqualTo("[c] error a 1 bundle scr:0.0.0 (1)");
+ }
+
+ @Test
+ public void testStandardOutput() throws IOException {
+ ScrConfiguration config = mock(ScrConfiguration.class);
+ when(config.getLogLevel()).thenReturn(Level.DEBUG);
+ ExtLogManager elm = new ExtLogManager(scr.getBundleContext(), config);
+ elm.open();
+ try (Buf out = new Buf(System.out);
+ Buf err = new Buf(System.err);) {
+ try {
+ System.setOut(out.out());
+ System.setErr(err.out());
+ ScrLogger scr = elm.scr();
+ {
+ scr.log(Level.AUDIT, "audit ", null);
+ scr.log(Level.AUDIT, "audit", new Exception("FOOBAR"));
+ scr.log(Level.TRACE, "audit", new Exception("FOOBAR"));
+
+ String logged = (Level.AUDIT.err() ? err : out).buffer.toString();
+ assertThat(logged).startsWith("AUDIT : audit").contains("FOOBAR");
+ }
+
+ {
+ int n = 0;
+ reset(config);
+
+ for (Level configLevel : Level.values()) {
+
+ for (Level requestedLevel : Level.values()) {
+ out.reset();
+ err.reset();
+ when(config.getLogLevel()).thenReturn(configLevel);
+
+ String message = configLevel + ":" + requestedLevel;
+ scr.log(requestedLevel, message, null);
+ verify(config, times(++n)).getLogLevel();
+
+ String logged = (requestedLevel.err() ? err : out).buffer.toString();
+
+ if (configLevel.implies(requestedLevel)) {
+ assertThat(logged).contains(message);
+ } else {
+ assertThat(logged).isEmpty();
+ }
+ }
+ }
+ }
+ } finally {
+ System.setOut(out.stream);
+ System.setErr(err.stream);
+ }
+ }
+ }
+
+ @Test
+ public void testSelectionOfExtensionManager() {
+ LogService l = new LogService(log.getBundleContext());
+ l.register();
+
+ ScrConfiguration config = mock(ScrConfiguration.class);
+ when(config.getLogLevel()).thenReturn(Level.DEBUG);
+ when(config.isLogExtension()).thenReturn(true);
+ ScrLogger logger = ScrLogManager.scr(scr.getBundleContext(), config);
+ BundleLogger bundle = logger.bundle(component);
+ bundle.log(Level.ERROR, "Ext", null);
+ assertThat(l.entries).hasSize(1);
+ LogEntry le = l.entries.get(0);
+ assertThat(le.bundle).isEqualTo(component);
+ }
+
+ @Test
+ public void testSelectionOfBaseManager() {
+ LogService l = new LogService(log.getBundleContext());
+ l.register();
+
+ ScrConfiguration config = mock(ScrConfiguration.class);
+ when(config.isLogExtension()).thenReturn(false);
+ when(config.getLogLevel()).thenReturn(Level.DEBUG);
+
+ ScrLogger logger = ScrLogManager.scr(scr.getBundleContext(), config);
+ BundleLogger bundle = logger.bundle(component);
+ bundle.log(Level.ERROR, "Ext", null);
+ assertThat(l.entries).hasSize(1);
+ LogEntry le = l.entries.get(0);
+ assertThat(le.bundle).isEqualTo(component);
+ }
+
+ @Test
+ public void testExtensionLogLevelNotLoggingWhenRootSetToInfoAndLevelIsDebug() {
+ ScrConfiguration config = mock(ScrConfiguration.class);
+ when(config.isLogExtension()).thenReturn(true);
+ when(config.getLogLevel()).thenReturn(Level.DEBUG);
+
+ LogService l = new LogService(log.getBundleContext());
+ l.register();
+ l.levels.put(Logger.ROOT_LOGGER_NAME, LogLevel.INFO);
+ l.defaultLogLevel = LogLevel.TRACE;
+
+ ScrLogger lscr = ScrLogManager.scr(scr.getBundleContext(), config);
+
+ assertThat(lscr.isLogEnabled(Level.DEBUG)).isFalse();
+ assertThat(lscr.isLogEnabled(Level.INFO)).isTrue();
+
+ lscr.log(Level.DEBUG, "I should not be reported", null);
+
+ assertThat(l.entries).isEmpty();
+ }
+
+ @Test
+ public void testExtensionLogLevelNotLoggingWhenPartialNameSetToInfoAndLevelIsDebug() {
+ ScrConfiguration config = mock(ScrConfiguration.class);
+ when(config.isLogExtension()).thenReturn(true);
+ when(config.getLogLevel()).thenReturn(Level.DEBUG);
+
+ LogService l = new LogService(log.getBundleContext());
+ l.defaultLogLevel = LogLevel.TRACE;
+ l.register();
+ l.levels.put("org.apache.felix.scr", LogLevel.INFO);
+
+ ScrLogger lscr = ScrLogManager.scr(scr.getBundleContext(), config);
+
+ assertThat(lscr.isLogEnabled(Level.DEBUG)).isFalse();
+ assertThat(lscr.isLogEnabled(Level.INFO)).isTrue();
+
+ lscr.log(Level.DEBUG, "I should not be reported", null);
+
+ assertThat(l.entries).isEmpty();
+ }
+
+ @Test
+ public void testExtensionLogManager() {
+ ScrConfiguration config = mock(ScrConfiguration.class);
+ when(config.isLogExtension()).thenReturn(true);
+ when(config.getLogLevel()).thenReturn(Level.DEBUG);
+
+ LogService l = new LogService(log.getBundleContext());
+ l.register();
+
+ ScrLogManager lm = new ExtLogManager(scr.getBundleContext(), config);
+ lm.open();
+
+ {
+ l.entries.clear();
+ ScrLogger scr = lm.scr();
+ scr.log(Level.ERROR, "Scr", null);
+ assertThat(l.entries).hasSize(1);
+ LogEntry le = l.entries.get(0);
+ assertThat(le.format).isEqualTo("Scr");
+ assertThat(le.bundle).isEqualTo(this.scr);
+ assertThat(le.loggername).isEqualTo(ExtLogManager.SCR_LOGGER_NAME);
+ }
+
+ {
+ l.entries.clear();
+ BundleLogger blog = lm.bundle(component);
+ blog.log(Level.ERROR, "Bundle", null);
+ assertThat(l.entries).hasSize(1);
+ LogEntry le = l.entries.get(0);
+ assertThat(le.format).isEqualTo("Bundle");
+ assertThat(le.bundle).isEqualTo(component);
+ assertThat(le.loggername).isEqualTo(ExtLogManager.SCR_LOGGER_PREFIX + "component");
+ }
+
+ {
+ l.entries.clear();
+ ComponentLogger clog = lm.component(component, "implementation.class", "name");
+ clog.log(Level.ERROR, "Component", null);
+ assertThat(l.entries).hasSize(1);
+ LogEntry le = l.entries.get(0);
+ assertThat(le.format).isEqualTo("[name] Component");
+ assertThat(le.bundle).isEqualTo(component);
+ assertThat(le.loggername).isEqualTo(ExtLogManager.SCR_LOGGER_PREFIX + "component.name");
+
+ l.entries.clear();
+ clog.setComponentId(100);
+ clog.log(Level.ERROR, "Component", null);
+ le = l.entries.get(0);
+ assertThat(le.format).isEqualTo("[name(100)] Component");
+ }
+
+ {
+ lm.scr().close();
+ }
+ }
+
+ @Test
+ public void testBackwardCompatibilityOutput() {
+ ScrConfiguration config = mock(ScrConfiguration.class);
+ when(config.isLogExtension()).thenReturn(false);
+ when(config.getLogLevel()).thenReturn(Level.DEBUG);
+
+ LogService l = new LogService(log.getBundleContext());
+ l.register();
+
+ ScrLogManager lm = new ScrLogManager(scr.getBundleContext(), config);
+ lm.open();
+
+ {
+ l.entries.clear();
+ ScrLogger scr = lm.scr();
+ scr.log(Level.ERROR, "Scr", null);
+ assertThat(l.entries).hasSize(1);
+ LogEntry le = l.entries.get(0);
+ assertThat(le.format).isEqualTo("bundle scr:0.0.0 (1) Scr");
+ assertThat(le.bundle).isEqualTo(this.scr);
+ assertThat(le.loggername).isEqualTo(Logger.ROOT_LOGGER_NAME);
+ }
+
+ {
+ l.entries.clear();
+ BundleLogger blog = lm.bundle(component);
+ blog.log(Level.ERROR, "Bundle", null);
+ assertThat(l.entries).hasSize(1);
+ LogEntry le = l.entries.get(0);
+ assertThat(le.format).isEqualTo("bundle component:0.0.0 (2) Bundle");
+ assertThat(le.bundle).isEqualTo(component);
+ assertThat(le.loggername).isEqualTo(Logger.ROOT_LOGGER_NAME);
+ }
+
+ {
+ l.entries.clear();
+ ComponentLogger clog = lm.component(component, "implementation.class", "name");
+ clog.log(Level.ERROR, "Component", null);
+ assertThat(l.entries).hasSize(1);
+ LogEntry le = l.entries.get(0);
+ assertThat(le.format).isEqualTo("bundle component:0.0.0 (2)[implementation.class] : Component");
+ assertThat(le.bundle).isEqualTo(component);
+ assertThat(le.loggername).isEqualTo("implementation.class");
+
+ l.entries.clear();
+ clog.setComponentId(100);
+ clog.log(Level.ERROR, "Component", null);
+ le = l.entries.get(0);
+ assertThat(le.format).isEqualTo("bundle component:0.0.0 (2)[implementation.class(100)] : Component");
+ }
+ }
+
+ @Test
+ public void testLifeCycle() {
+
+ LogManager lm = new LogManager(scr.getBundleContext());
+ lm.open();
+ LoggerFacade facade = lm.getLogger(scr, "lifecycle", LoggerFacade.class);
+ assertThat(facade.logger).isNull();
+
+ LogService l = new LogService(log.getBundleContext());
+ l.register();
+ assertThat(l.loggers.get(scr)).isNull();
+
+ Logger logger = (Logger) facade.getLogger();
+
+ assertThat(logger).isNotNull();
+ assertThat(facade.logger).isEqualTo(logger);
+ assertThat(l.loggers.get(scr)).hasSize(1);
+
+ assertThat(logger.getName()).isEqualTo("lifecycle");
+
+ Logger logger2 = (Logger) facade.getLogger();
+ assertThat(logger2).isEqualTo(logger);
+ assertThat(l.loggers.get(scr)).hasSize(1);
+
+ l.unregister();
+ l.register();
+
+ assertThat(facade.logger).isNull();
+ logger = (Logger) facade.getLogger();
+ assertThat(l.loggers.get(scr)).hasSize(2);
+
+ assertThat(facade.logger).isNotNull();
+ lm.close();
+ assertThat(facade.logger).isNull();
+ }
+
+ @Test
+ public void testPrioritiesLogService() {
+
+ LogManager lm = new LogManager(scr.getBundleContext());
+ lm.open();
+ LoggerFacade facade = lm.getLogger(scr, "lifecycle", LoggerFacade.class);
+ assertThat(facade.logger).isNull();
+
+ LogService la = new LogService(log.getBundleContext());
+ la.register();
+ Logger loggera = (Logger) facade.getLogger();
+ assertThat(loggera).isNotNull();
+ assertThat(facade.logger).isEqualTo(loggera);
+ assertThat(la.loggers.get(scr)).hasSize(1);
+
+ LogService higherRanking = new LogService(log.getBundleContext()).ranking(10).register();
+
+ assertThat(facade.logger).isNull();
+
+ Logger loggerb = (Logger) facade.getLogger();
+ assertThat(loggerb).isNotNull();
+ assertThat(higherRanking.loggers.get(scr)).hasSize(1);
+
+ LogService lowerRanking = new LogService(log.getBundleContext()).ranking(-10).register();
+ assertThat(facade.logger).isNotNull();
+ assertThat(lowerRanking.loggers.get(scr)).isNull();
+ }
+
+ @Test
+ public void testLifeCycleOfComponentBundle() throws BundleException, InterruptedException {
+
+ ScrConfiguration config = mock(ScrConfiguration.class);
+
+ LogService l = new LogService(log.getBundleContext());
+ l.register();
+
+ ScrLogManager lm = new ScrLogManager(scr.getBundleContext(), config);
+ lm.open();
+ lm.component(component, "implementation.class", "component");
+
+ assertThat(lm.lock.domains).hasSize(1);
+ component.stop();
+
+ for (int i = 0; i < 100; i++) {
+ if (lm.lock.domains.isEmpty())
+ return;
+ Thread.sleep(10);
+ }
+ lm.close();
+ fail("domains not cleared within 1 sec");
+ }
+
+ @Test
+ public void testLogLevels() {
+
+ ScrConfiguration config = mock(ScrConfiguration.class);
+
+ when(config.getLogLevel()).thenReturn(Level.DEBUG);
+ when(config.isLogExtension()).thenReturn(false);
+
+ LogService l = new LogService(log.getBundleContext());
+
+ l.register();
+
+ ScrLogManager lm = new ScrLogManager(scr.getBundleContext(), config);
+ lm.open();
+ ScrLogger facade = lm.scr();
+
+ assert LogLevel.values().length == Level.values().length;
+ for (int i = 0; i < LogLevel.values().length; i++) {
+ assert LogLevel.values()[i].name().equals(Level.values()[i].name());
+ }
+
+ Exception ex = new Exception("exception");
+
+ for (LogLevel logLevel : LogLevel.values()) {
+
+ l.defaultLogLevel = logLevel;
+
+ for (Level level : Level.values()) {
+
+ if (logLevel.ordinal() >= level.ordinal())
+ assertThat(facade.isLogEnabled(level)).isTrue();
+ else
+ assertThat(facade.isLogEnabled(level)).isFalse();
+
+ facade.log(level, "level " + level, null);
+ facade.log(level, "level " + level, ex);
+ }
+ }
+ assertThat(l.entries).hasSize(42);
+ }
+
+ private JarResource makeBundle(String bsn) throws Exception {
+ @SuppressWarnings("resource")
+ Builder b = new Builder();
+ b.setBundleSymbolicName(bsn);
+ Jar jar = b.build();
+ b.removeClose(jar);
+ return new JarResource(jar);
+ }
+
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/logger/MockBundleLogger.java b/scr/src/test/java/org/apache/felix/scr/impl/logger/MockBundleLogger.java
index acbd8fb..3040101 100644
--- a/scr/src/test/java/org/apache/felix/scr/impl/logger/MockBundleLogger.java
+++ b/scr/src/test/java/org/apache/felix/scr/impl/logger/MockBundleLogger.java
@@ -21,18 +21,11 @@
import java.text.MessageFormat;
-import org.apache.felix.scr.impl.MockBundle;
-import org.apache.felix.scr.impl.logger.InternalLogger.Level;
+import org.osgi.framework.Bundle;
-public class MockBundleLogger extends BundleLogger
+public class MockBundleLogger implements BundleLogger
{
- public MockBundleLogger()
- {
- super(new MockBundle(), new MockScrLogger());
- }
-
-
private String lastMessage;
@@ -43,22 +36,20 @@
}
@Override
- public boolean log(final Level level, final String pattern, final Throwable ex,
+ public void log(final Level level, final String pattern, final Throwable ex,
final Object... arguments)
{
if ( isLogEnabled( level ) )
{
log( level, MessageFormat.format( pattern, arguments ), ex );
}
- return true;
}
@Override
- public boolean log(final Level level, final String message, final Throwable ex)
+ public void log(final Level level, final String message, final Throwable ex)
{
lastMessage = message;
- return true;
}
@@ -66,4 +57,10 @@
{
return lastMessage != null && lastMessage.indexOf( value ) >= 0;
}
+
+ @Override
+ public ComponentLogger component(Bundle m_bundle, String implementationClassName, String name) {
+ return new MockComponentLogger();
+ }
+
}
\ No newline at end of file
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/logger/MockComponentLogger.java b/scr/src/test/java/org/apache/felix/scr/impl/logger/MockComponentLogger.java
index 8194ebe..18f7575 100644
--- a/scr/src/test/java/org/apache/felix/scr/impl/logger/MockComponentLogger.java
+++ b/scr/src/test/java/org/apache/felix/scr/impl/logger/MockComponentLogger.java
@@ -18,29 +18,23 @@
*/
package org.apache.felix.scr.impl.logger;
-import org.apache.felix.scr.impl.logger.InternalLogger.Level;
-import org.apache.felix.scr.impl.metadata.ComponentMetadata;
-import org.apache.felix.scr.impl.metadata.DSVersion;
-
-public class MockComponentLogger extends ComponentLogger
+public class MockComponentLogger implements ComponentLogger
{
- public MockComponentLogger()
- {
- super(new ComponentMetadata(DSVersion.DS14), new MockBundleLogger());
- }
- @Override
- public boolean log(final Level level, final String pattern, final Throwable ex,
- final Object... arguments)
- {
- // ignore
- return true;
- }
+ @Override
+ public void log(Level level, String message, Throwable ex) {
+ }
- @Override
- public boolean log(final Level level, final String message, final Throwable ex)
- {
- // ignore
- return true;
- }
+ @Override
+ public void log(Level level, String message, Throwable ex, Object... args) {
+ }
+
+ @Override
+ public boolean isLogEnabled(Level level) {
+ return true;
+ }
+
+ @Override
+ public void setComponentId(long m_componentId) {
+ }
}
\ No newline at end of file
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/logger/MockScrLogger.java b/scr/src/test/java/org/apache/felix/scr/impl/logger/MockScrLogger.java
index f2f2f94..3634439 100644
--- a/scr/src/test/java/org/apache/felix/scr/impl/logger/MockScrLogger.java
+++ b/scr/src/test/java/org/apache/felix/scr/impl/logger/MockScrLogger.java
@@ -18,72 +18,31 @@
*/
package org.apache.felix.scr.impl.logger;
+import org.osgi.framework.Bundle;
-import org.apache.felix.scr.impl.MockBundle;
-import org.apache.felix.scr.impl.MockBundleContext;
-import org.apache.felix.scr.impl.logger.InternalLogger.Level;
-import org.apache.felix.scr.impl.manager.ScrConfiguration;
-
-
-public class MockScrLogger extends ScrLogger
+public class MockScrLogger implements ScrLogger
{
- public MockScrLogger()
- {
- super(new ScrConfiguration()
- {
+ @Override
+ public void log(Level level, String message, Throwable ex) {
+ }
- @Override
- public long stopTimeout()
- {
- return 0;
- }
+ @Override
+ public void log(Level level, String message, Throwable ex, Object... args) {
+
+ }
- @Override
- public long lockTimeout()
- {
- return 0;
- }
+ @Override
+ public boolean isLogEnabled(Level level) {
+ return true;
+ }
- @Override
- public boolean keepInstances()
- {
- return false;
- }
+ @Override
+ public BundleLogger bundle(Bundle bundle) {
+ return new MockBundleLogger();
+ }
- @Override
- public boolean isFactoryEnabled()
- {
- return false;
- }
+ @Override
+ public void close() {
+ }
- @Override
- public boolean infoAsService()
- {
- return false;
- }
-
- @Override
- public long serviceChangecountTimeout()
- {
- return 0;
- }
-
- @Override
- public Level getLogLevel()
- {
- return Level.ERROR;
- }
-
- @Override
- public boolean globalExtender() {
- return false;
- }
-
- @Override
- public boolean cacheMetadata()
- {
- return false;
- }
- }, new MockBundleContext(new MockBundle()));
- }
}
\ No newline at end of file
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/ComponentTestBase.java b/scr/src/test/java/org/apache/felix/scr/integration/ComponentTestBase.java
index ce27281..0966c5b 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/ComponentTestBase.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/ComponentTestBase.java
@@ -242,6 +242,10 @@
scrTracker.close();
scrTracker = null;
}
+ catch (IllegalStateException e)
+ {
+ // not sure why this is thrown
+ }
finally
{
log.stop();
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/Felix6274Test.java b/scr/src/test/java/org/apache/felix/scr/integration/Felix6274Test.java
index 08c4117..879d09f 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/Felix6274Test.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/Felix6274Test.java
@@ -50,16 +50,16 @@
// This test creates its own component bundles
descriptorFile = null;
DS_LOGLEVEL = "debug";
- // paxRunnerVmOption = DEBUG_VM_OPTION;
+ //paxRunnerVmOption = DEBUG_VM_OPTION;
}
private final List<Bundle> installedBundles = new ArrayList<>();
- private Bundle log_1_4_bundle;
- private Bundle log_1_3_bundle;
+ private Bundle log_bundle1;
+ private Bundle log_bundle2;
@Before
public void installBundles() throws BundleException {
- String log101 = mavenBundle( "org.apache.felix", "org.apache.felix.log", "1.0.1" ).getURL();
+ String log101 = mavenBundle( "org.apache.felix", "org.apache.felix.log", "1.2.2" ).getURL();
// install and start the resolver hook first
Bundle hookBundle = bundleContext.installBundle("integration.test.6274_hook", createHookBundle());
@@ -70,13 +70,13 @@
installedBundles.add( bundleContext.installBundle( log101 ) );
- log_1_4_bundle = bundleContext.installBundle( "integration.test.6274",
- createStream( "6274", "[1.4,1.5)" ) );
- installedBundles.add( log_1_4_bundle );
+ log_bundle1 = bundleContext.installBundle("integration.test.6274_1",
+ createStream("6274_1", "[1.4,1.5)"));
+ installedBundles.add( log_bundle1 );
- log_1_3_bundle = bundleContext.installBundle( "integration.test.6274_2",
- createStream( "6274_2", "[1.3,1.4)" ) );
- installedBundles.add( log_1_3_bundle );
+ log_bundle2 = bundleContext.installBundle( "integration.test.6274_2",
+ createStream("6274_2", "[1.4,1.5)"));
+ installedBundles.add( log_bundle2 );
for( Bundle b : installedBundles ) {
b.start();
@@ -134,10 +134,10 @@
// Locate the components
ComponentDescriptionDTO log_1_4_dto = scrTracker.getService().getComponentDescriptionDTO(
- log_1_4_bundle, "R7LoggerComponent" );
+ log_bundle1, "R7LoggerComponent" );
ComponentDescriptionDTO log_1_3_dto = scrTracker.getService().getComponentDescriptionDTO(
- log_1_3_bundle, "LogServiceComponent" );
+ log_bundle2, "LogServiceComponent" );
assertNotNull( "No Log 1.4 Component DTO", log_1_4_dto );
assertNotNull( "No Log 1.3 Component DTO", log_1_3_dto );
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/Felix6325OptionalLogTest.java b/scr/src/test/java/org/apache/felix/scr/integration/Felix6325OptionalLogTest.java
new file mode 100644
index 0000000..b4d0137
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/integration/Felix6325OptionalLogTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.scr.integration;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.hooks.resolver.ResolverHook;
+import org.osgi.framework.hooks.resolver.ResolverHookFactory;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.FrameworkWiring;
+
+@RunWith(PaxExam.class)
+public class Felix6325OptionalLogTest extends ComponentTestBase
+{
+ static
+ {
+ // This test creates its own component bundles
+ descriptorFile = null;
+ DS_LOGLEVEL = "debug";
+ //paxRunnerVmOption = DEBUG_VM_OPTION;
+ }
+
+ final ResolverHookFactory resolverHookFactory = new ResolverHookFactoryImpl();
+
+ class ResolverHookFactoryImpl implements ResolverHookFactory
+ {
+ final ResolverHook resolverHook = new ResolverHookImpl();
+
+ @Override
+ public ResolverHook begin(Collection<BundleRevision> triggers)
+ {
+ return resolverHook;
+ }
+ }
+
+ class ResolverHookImpl implements ResolverHook
+ {
+ @Override
+ public void filterSingletonCollisions(BundleCapability singleton,
+ Collection<BundleCapability> collisionCandidates)
+ {
+ // nothing to do
+ }
+
+ @Override
+ public void filterResolvable(Collection<BundleRevision> candidates)
+ {
+ // nothing to do
+ }
+
+ @Override
+ public void filterMatches(BundleRequirement requirement,
+ Collection<BundleCapability> candidates)
+ {
+ if (!candidates.isEmpty()
+ && PackageNamespace.PACKAGE_NAMESPACE.equals(requirement.getNamespace()))
+ {
+ String packageName = (String) candidates.iterator().next().getAttributes().get(
+ PackageNamespace.PACKAGE_NAMESPACE);
+ if (packageName != null && packageName.startsWith("org.osgi.service.log"))
+ {
+ candidates.clear();
+ }
+ }
+ return;
+ }
+
+ @Override
+ public void end()
+ {
+ // nothing to do
+ }
+ }
+
+ private ServiceRegistration<ResolverHookFactory> hookReg;
+ private Bundle scrBundle;
+ private FrameworkWiring fwkWiring;
+
+ @Before
+ public void registerResolverHook() throws BundleException
+ {
+ scrBundle = null;
+ for (Bundle b : bundleContext.getBundles())
+ {
+ if ("org.apache.felix.scr".equals(b.getSymbolicName()))
+ {
+ scrBundle = b;
+ break;
+ }
+ }
+ assertNotNull("No SCR bundle found!", scrBundle);
+
+ Bundle systemBundle = bundleContext.getBundle(Constants.SYSTEM_BUNDLE_LOCATION);
+ BundleContext systemContext = systemBundle.getBundleContext();
+ fwkWiring = systemContext.getBundle().adapt(FrameworkWiring.class);
+
+ // Use a resolver hook to make sure scr does not import the log packages
+ hookReg = systemContext.registerService(ResolverHookFactory.class,
+ resolverHookFactory, null);
+ }
+
+ @After
+ public void unregisterResolverHook() throws InterruptedException
+ {
+ if (hookReg != null)
+ {
+ try
+ {
+ hookReg.unregister();
+ }
+ catch (IllegalStateException e)
+ {
+ // ignore
+ }
+ hookReg = null;
+ }
+ refresh(scrBundle);
+ }
+
+ private void refresh(Bundle b) throws InterruptedException
+ {
+ final CountDownLatch refreshWait = new CountDownLatch(1);
+ fwkWiring.refreshBundles(Collections.singleton(b), new FrameworkListener()
+ {
+ @Override
+ public void frameworkEvent(FrameworkEvent event)
+ {
+ refreshWait.countDown();
+ }
+ });
+ refreshWait.await(10, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void testNoLogPackage() throws Exception
+ {
+ scrBundle.stop();
+ refresh(scrBundle);
+ scrBundle.start();
+ }
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/ServiceBindGreedyTest.java b/scr/src/test/java/org/apache/felix/scr/integration/ServiceBindGreedyTest.java
index 30bec4f..34d0298 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/ServiceBindGreedyTest.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/ServiceBindGreedyTest.java
@@ -19,7 +19,14 @@
package org.apache.felix.scr.integration;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.Hashtable;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.felix.scr.integration.components.SimpleComponent;
import org.apache.felix.scr.integration.components.SimpleComponent2;
@@ -46,7 +53,7 @@
static
{
// uncomment to enable debugging of this test class
- // paxRunnerVmOption = DEBUG_VM_OPTION;
+ //paxRunnerVmOption = DEBUG_VM_OPTION;
descriptorFile = "/integration_test_simple_components_service_binding_greedy.xml";
}
@@ -165,6 +172,120 @@
TestCase.assertTrue( comp32.m_multiRef.isEmpty() );
}
+ @Test
+ public void test_optional_single_dynamic_multi_thread1() throws Exception
+ {
+ do_test_optional_single_dynamic_multi_thread(false);
+ }
+
+ @Test
+ public void test_optional_single_dynamic_multi_thread2() throws Throwable
+ {
+ do_test_optional_single_dynamic_multi_thread(true);
+ }
+
+ AtomicBoolean enabledConfig = new AtomicBoolean();
+ private void do_test_optional_single_dynamic_multi_thread(
+ Boolean unregPreExistingFirst)
+ throws Exception
+ {
+ String name = "test_optional_single_dynamic";
+ final int numRanks = 1000;
+ final int ranksPerThread = 100;
+ final SimpleServiceImpl preExistingServ = SimpleServiceImpl.create(bundleContext,
+ "preExistingServ", numRanks / 4);
+
+ if (enabledConfig.compareAndSet(false, true))
+ {
+ getDisabledConfigurationAndEnable(name, ComponentConfigurationDTO.ACTIVE);
+ }
+ final SimpleComponent comp = SimpleComponent.INSTANCE;
+ TestCase.assertNotNull(comp);
+ TestCase.assertEquals(preExistingServ, comp.m_singleRef);
+ TestCase.assertTrue(comp.m_multiRef.isEmpty());
+
+ final List<Integer> ranks = Collections.synchronizedList(
+ new LinkedList<Integer>());
+ for (int i = 0; i < numRanks; i++)
+ {
+ ranks.add(i);
+ }
+ Collections.shuffle(ranks);
+
+ Collection<Thread> threads = new HashSet<>();
+ final List<SimpleServiceImpl> registrations = Collections.synchronizedList(
+ new LinkedList<SimpleServiceImpl>());
+ final AtomicReference<SimpleServiceImpl> highest = new AtomicReference<>();
+ for (int i = 0; i < numRanks; i = i + ranksPerThread)
+ {
+ threads.add(new Thread(new Runnable()
+ {
+ public void run()
+ {
+ for (int j = 0; j < ranksPerThread; j++)
+ {
+ int rank = ranks.remove(0);
+ SimpleServiceImpl srv = SimpleServiceImpl.create(bundleContext,
+ "srv" + rank, rank);
+ registrations.add(srv);
+ if (rank == numRanks - 1)
+ {
+ highest.set(srv);
+ }
+ }
+ };
+ }));
+ }
+ for (Thread t : threads)
+ {
+ t.start();
+ }
+ for (Thread t : threads)
+ {
+ t.join();
+ }
+
+ TestCase.assertNotNull(highest.get());
+ TestCase.assertEquals(highest.get(), comp.m_singleRef);
+ TestCase.assertTrue(comp.m_multiRef.isEmpty());
+
+ threads.clear();
+ for (int i = 0; i < numRanks; i = i + ranksPerThread)
+ {
+ threads.add(new Thread(new Runnable()
+ {
+ public void run()
+ {
+ for (int j = 0; j < ranksPerThread; j++)
+ {
+ registrations.remove(0).drop();
+ }
+ };
+ }));
+ }
+ if (unregPreExistingFirst)
+ {
+ preExistingServ.drop();
+ }
+ for (Thread t : threads)
+ {
+ t.start();
+ }
+ for (Thread t : threads)
+ {
+ t.join();
+ }
+ if (!unregPreExistingFirst)
+ {
+ TestCase.assertEquals(preExistingServ, comp.m_singleRef);
+ TestCase.assertTrue(comp.m_multiRef.isEmpty());
+ preExistingServ.drop();
+ }
+
+ TestCase.assertEquals("Found bound reference: " + comp.m_singleRefBind + '-'
+ + comp.m_singleRefUnbind, null, comp.m_singleRef);
+ TestCase.assertTrue(comp.m_multiRef.isEmpty());
+ }
@Test
public void test_required_single_dynamic() throws Exception
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleComponent.java b/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleComponent.java
index 886d9c4..4d697fa 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleComponent.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleComponent.java
@@ -41,7 +41,7 @@
public static final Set<SimpleComponent> PREVIOUS_INSTANCES = new HashSet<SimpleComponent>();
- public static SimpleComponent INSTANCE;
+ public volatile static SimpleComponent INSTANCE;
private Map<?, ?> m_config;
@@ -49,11 +49,11 @@
public ComponentContext m_activateContext;
- public SimpleService m_singleRef;
+ public volatile SimpleService m_singleRef;
- public int m_singleRefBind = 0;
+ public volatile int m_singleRefBind = 0;
- public int m_singleRefUnbind = 0;
+ public volatile int m_singleRefUnbind = 0;
public final Set<SimpleService> m_multiRef = new HashSet<SimpleService>();
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleServiceImpl.java b/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleServiceImpl.java
index 090902b..d365e6e 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleServiceImpl.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleServiceImpl.java
@@ -21,14 +21,13 @@
import java.util.Dictionary;
import java.util.Hashtable;
-import java.util.Properties;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
-public class SimpleServiceImpl implements SimpleService
+public class SimpleServiceImpl implements SimpleService, Comparable<SimpleServiceImpl>
{
private String m_value;
@@ -37,7 +36,7 @@
private String m_filterProp;
- private ServiceRegistration m_registration;
+ private ServiceRegistration<SimpleService> m_registration;
public static SimpleServiceImpl create( BundleContext bundleContext, String value )
@@ -50,7 +49,8 @@
{
SimpleServiceImpl instance = new SimpleServiceImpl( value, ranking );
Dictionary<String,?> props = instance.getProperties();
- instance.setRegistration( bundleContext.registerService( SimpleService.class.getName(), instance, props ) );
+ instance.setRegistration(
+ bundleContext.registerService(SimpleService.class, instance, props));
return instance;
}
@@ -104,7 +104,7 @@
public SimpleServiceImpl drop()
{
- ServiceRegistration sr = getRegistration();
+ ServiceRegistration<SimpleService> sr = getRegistration();
if ( sr != null )
{
setRegistration( null );
@@ -120,14 +120,15 @@
}
- public SimpleServiceImpl setRegistration( ServiceRegistration registration )
+ public SimpleServiceImpl setRegistration(
+ ServiceRegistration<SimpleService> registration)
{
m_registration = registration;
return this;
}
- public ServiceRegistration getRegistration()
+ public ServiceRegistration<SimpleService> getRegistration()
{
return m_registration;
}
@@ -136,6 +137,13 @@
@Override
public String toString()
{
- return getClass().getSimpleName() + ": value=" + getValue() + ", filterprop=" + m_filterProp;
+ return getClass().getSimpleName() + ": value=" + getValue() + ", filterprop="
+ + m_filterProp + ", m_registration=" + m_registration;
+ }
+
+ @Override
+ public int compareTo(SimpleServiceImpl o)
+ {
+ return Integer.compare(this.m_ranking, o.m_ranking);
}
}
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/components/felix3680_2/Main.java b/scr/src/test/java/org/apache/felix/scr/integration/components/felix3680_2/Main.java
index 6de36ee..71f64b4 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/components/felix3680_2/Main.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/components/felix3680_2/Main.java
@@ -29,7 +29,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.felix.scr.impl.manager.ThreadDump;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274/Component.java b/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274_1/Component.java
similarity index 93%
rename from scr/src/test/java/org/apache/felix/scr/integration/components/felix6274/Component.java
rename to scr/src/test/java/org/apache/felix/scr/integration/components/felix6274_1/Component.java
index 32eae52..6bef4f4 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274/Component.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274_1/Component.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.felix.scr.integration.components.felix6274;
+package org.apache.felix.scr.integration.components.felix6274_1;
import org.osgi.service.log.Logger;
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274_hook/Activator.java b/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274_hook/Activator.java
index fa54fa6..1e8b230 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274_hook/Activator.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/components/felix6274_hook/Activator.java
@@ -68,23 +68,20 @@
public void filterMatches(BundleRequirement requirement,
Collection<BundleCapability> candidates)
{
- Bundle b = requirement.getResource().getBundle();
-
+ Bundle b = requirement.getRevision().getBundle();
if (PackageNamespace.PACKAGE_NAMESPACE.equals(requirement.getNamespace())
&& "org.osgi.service.log".equals(
candidates.iterator().next().getAttributes().get(
PackageNamespace.PACKAGE_NAMESPACE))
- && "org.apache.felix.log".equals(b.getSymbolicName())
- && Version.valueOf("1.0.1").equals(b.getVersion()))
+ && (b.getSymbolicName() != null && !b.getSymbolicName().contains("6274_1")))
{
- // Felix log 1.0.1 incorrectly allows import of R7 log
+ // force bundles to import felix log package
Iterator<BundleCapability> iCaps = candidates.iterator();
while (iCaps.hasNext())
{
- if (!iCaps.next().getRevision().getBundle().equals(
- requirement.getRevision().getBundle()))
+ if (!"org.apache.felix.log".equals(
+ iCaps.next().getRevision().getBundle().getSymbolicName()))
{
- System.out.println("TJW - removed");
iCaps.remove();
}
}
diff --git a/scr/src/test/resources/integration_test_FELIX_6274.xml b/scr/src/test/resources/integration_test_FELIX_6274_1.xml
similarity index 96%
rename from scr/src/test/resources/integration_test_FELIX_6274.xml
rename to scr/src/test/resources/integration_test_FELIX_6274_1.xml
index 91d8b63..6999c1d 100644
--- a/scr/src/test/resources/integration_test_FELIX_6274.xml
+++ b/scr/src/test/resources/integration_test_FELIX_6274_1.xml
@@ -20,7 +20,7 @@
<components xmlns:scr="http://www.osgi.org/xmlns/scr/v1.4.0">
<scr:component name="R7LoggerComponent">
- <implementation class="org.apache.felix.scr.integration.components.felix6274.Component" />
+ <implementation class="org.apache.felix.scr.integration.components.felix6274_1.Component" />
<reference name="LOG" interface="org.osgi.service.log.LoggerFactory" field="logger"/>
</scr:component>
diff --git a/webconsole-plugins/event/pom.xml b/webconsole-plugins/event/pom.xml
index 6c2c384..26c9c2a 100644
--- a/webconsole-plugins/event/pom.xml
+++ b/webconsole-plugins/event/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>org.apache.felix</groupId>
<artifactId>felix-parent</artifactId>
- <version>4</version>
+ <version>7</version>
<relativePath>../../../pom/pom.xml</relativePath>
</parent>
@@ -32,8 +32,7 @@
<name>Apache Felix Web Console Event Plugin</name>
<description>
- This is a plugin for the Apache Felix OSGi web console for displaying
- OSGi events.
+ This is a plugin for the Apache Felix OSGi web console for displaying OSGi events.
</description>
<scm>
@@ -71,7 +70,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
- <version>3.3.0</version>
+ <version>5.1.1</version>
<extensions>true</extensions>
<configuration>
<instructions>
@@ -82,24 +81,42 @@
org.apache.felix.webconsole.plugins.event.internal.Activator
</Bundle-Activator>
<Import-Package>
- org.osgi.service.cm;version="[1.2,2)";resolution:=optional,
- org.osgi.service.event;;version="[1.2,2)",*
+ org.osgi.service.cm;version="[1.4,2)";resolution:=optional,*
</Import-Package>
<Private-Package>
org.apache.felix.webconsole.plugins.event.*
</Private-Package>
<DynamicImport-Package>
- org.osgi.service.cm;version="[1.2,2)",
- javax.servlet,
- javax.servlet.http
+ org.osgi.service.cm;version="[1.4,2)"
</DynamicImport-Package>
- <Include-Resource>{maven-resources},OSGI-INF=target/classes/OSGI-INF</Include-Resource>
<Embed-Dependency>
org.apache.felix.utils;inline=org/apache/felix/utils/json/JSONWriter**
</Embed-Dependency>
</instructions>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>3.2.1</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <createDependencyReducedPom>false</createDependencyReducedPom>
+ <relocations>
+ <relocation>
+ <pattern>org.apache.felix.utils</pattern>
+ <shadedPattern>org.apache.felix.webconsole.plugins.event.utils</shadedPattern>
+ </relocation>
+ </relocations>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
<plugin>
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
@@ -127,19 +144,25 @@
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.core</artifactId>
- <version>4.3.1</version>
+ <version>6.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
- <artifactId>osgi.cmpn</artifactId>
- <version>4.3.1</version>
+ <artifactId>org.osgi.service.event</artifactId>
+ <version>1.4.0</version>
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.cm</artifactId>
+ <version>1.5.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.utils</artifactId>
- <version>1.9.0</version>
+ <version>1.11.4</version>
<scope>provided</scope>
</dependency>
</dependencies>
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/Activator.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/Activator.java
index 06be18b..ca7bbda 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/Activator.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/Activator.java
@@ -32,7 +32,7 @@
public class Activator implements BundleActivator
{
/** Registration for the plugin. */
- private ServiceRegistration pluginRegistration;
+ private ServiceRegistration<Servlet> pluginRegistration;
/** Listener */
private EventListener eventListener;
@@ -46,6 +46,7 @@
/**
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
+ @Override
public void start(final BundleContext context) throws Exception
{
// we start with the plugin
@@ -58,14 +59,14 @@
this.featuresHandler = new OptionalFeaturesHandler(this.plugin, context);
// finally we register the plugin
- final Dictionary props = new Hashtable();
+ final Dictionary<String, Object> props = new Hashtable<>();
props.put( Constants.SERVICE_DESCRIPTION, "Event Plugin for the Apache Felix Web Console" );
props.put( Constants.SERVICE_VENDOR, "The Apache Software Foundation" );
props.put( "felix.webconsole.label", "events");
props.put( "felix.webconsole.title", "%plugin.events.title");
props.put( "felix.webconsole.css", "/events/res/ui/events.css");
props.put( "felix.webconsole.category", "OSGi");
- this.pluginRegistration = context.registerService(Servlet.class.getName(),
+ this.pluginRegistration = context.registerService(Servlet.class,
plugin,
props);
}
@@ -73,6 +74,7 @@
/**
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
+ @Override
public void stop(final BundleContext context) throws Exception
{
if ( this.pluginRegistration != null )
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/ConfigurationListener.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/ConfigurationListener.java
index ecfbf7b..f8e3dfe 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/ConfigurationListener.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/ConfigurationListener.java
@@ -23,7 +23,9 @@
import java.util.Hashtable;
import org.apache.felix.webconsole.plugins.event.internal.converter.ConfigurationEventConverter;
-import org.osgi.framework.*;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationEvent;
import org.osgi.service.cm.ManagedService;
@@ -34,11 +36,11 @@
private final String pid;
- static ServiceRegistration create( final BundleContext context, final PluginServlet plugin )
+ static ServiceRegistration<?> create( final BundleContext context, final PluginServlet plugin )
{
ConfigurationListener cl = new ConfigurationListener( plugin );
- Dictionary props = new Hashtable();
+ Dictionary<String, Object> props = new Hashtable<>();
props.put( Constants.SERVICE_VENDOR, "The Apache Software Foundation" );
props.put( Constants.SERVICE_DESCRIPTION, "Event plugin for the Felix Web Console Configuration Receiver" );
props.put( Constants.SERVICE_PID, cl.pid );
@@ -57,6 +59,7 @@
/**
* @see org.osgi.service.cm.ConfigurationListener#configurationEvent(org.osgi.service.cm.ConfigurationEvent)
*/
+ @Override
public void configurationEvent(ConfigurationEvent event)
{
this.plugin.getCollector().add(ConfigurationEventConverter.getInfo(event));
@@ -68,7 +71,8 @@
/**
* @see org.osgi.service.cm.ManagedService#updated(java.util.Dictionary)
*/
- public void updated( Dictionary config )
+ @Override
+ public void updated( Dictionary<String, ?> config )
{
plugin.updateConfiguration( config );
}
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventCollector.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventCollector.java
index 14ae432..769c182 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventCollector.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventCollector.java
@@ -16,7 +16,9 @@
*/
package org.apache.felix.webconsole.plugins.event.internal;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
/**
* This class collects events
@@ -27,16 +29,16 @@
private static final String PROPERTY_MAX_SIZE = "max.size";
private static final int DEFAULT_MAX_SIZE = 250;
- private List eventInfos;
+ private List<EventInfo> eventInfos;
private long startTime;
private int maxSize;
- public EventCollector(final Dictionary props)
+ public EventCollector()
{
this.clear();
- this.updateConfiguration(props);
+ this.updateConfiguration(null);
}
public void add(final EventInfo info)
@@ -62,7 +64,7 @@
{
synchronized ( this )
{
- this.eventInfos = new ArrayList();
+ this.eventInfos = new ArrayList<>();
this.startTime = System.currentTimeMillis();
}
}
@@ -70,15 +72,15 @@
/**
* Return a copy of the current event list
*/
- public List getEvents()
+ public List<EventInfo> getEvents()
{
synchronized ( this )
{
- return new ArrayList(eventInfos);
+ return new ArrayList<>(eventInfos);
}
}
- public void updateConfiguration( final Dictionary props)
+ public void updateConfiguration( final Dictionary<String, ?> props)
{
this.maxSize = OsgiUtil.toInteger(props, PROPERTY_MAX_SIZE, DEFAULT_MAX_SIZE);
synchronized ( this )
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventHandler.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventHandler.java
index 07d9e52..9349889 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventHandler.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventHandler.java
@@ -20,7 +20,9 @@
import java.util.HashMap;
import java.util.Map;
-import org.apache.felix.webconsole.plugins.event.internal.converter.*;
+import org.apache.felix.webconsole.plugins.event.internal.converter.BundleEventConverter;
+import org.apache.felix.webconsole.plugins.event.internal.converter.FrameworkEventConverter;
+import org.apache.felix.webconsole.plugins.event.internal.converter.ServiceEventConverter;
import org.osgi.service.event.Event;
/**
@@ -49,6 +51,7 @@
/**
* @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
*/
+ @Override
public void handleEvent( Event event )
{
boolean include = true;
@@ -62,11 +65,11 @@
}
if ( include )
{
- Map props = null;
+ Map<String, Object> props = null;
final String[] names = event.getPropertyNames();
if ( names != null && names.length > 0 )
{
- props = new HashMap();
+ props = new HashMap<>();
for ( int i = 0; i < names.length; i++ )
{
props.put(names[i], event.getProperty(names[i]));
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventInfo.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventInfo.java
index 3e1405a..d2fc33c 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventInfo.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/EventInfo.java
@@ -37,7 +37,7 @@
public final long received;
/** Properties. */
- public final Map properties;
+ public final Map<String, Object> properties;
/** The event class. */
public final String category;
@@ -51,7 +51,7 @@
this.category = category;
}
- public EventInfo( final String topic, final String info, final String category, final Map props )
+ public EventInfo( final String topic, final String info, final String category, final Map<String, Object> props )
{
this.topic = topic;
this.info = info;
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OptionalFeaturesHandler.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OptionalFeaturesHandler.java
index 7559f90..0f98f5a 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OptionalFeaturesHandler.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OptionalFeaturesHandler.java
@@ -20,7 +20,13 @@
import java.util.Dictionary;
import java.util.Hashtable;
-import org.osgi.framework.*;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
import org.osgi.service.event.EventAdmin;
/**
@@ -40,16 +46,16 @@
/** Event admin service id */
//private Long eventAdminServiceId;
- private ServiceReference eventAdminServiceRef;
+ private ServiceReference<?> eventAdminServiceRef;
/** Registration for the event handler. */
- private ServiceRegistration eventHandlerRegistration;
+ private ServiceRegistration<?> eventHandlerRegistration;
/** Configuration admin service id */
private Long configAdminServiceId;
/** Registration for the configuration listener. */
- private ServiceRegistration configListenerRegistration;
+ private ServiceRegistration<?> configListenerRegistration;
/** Bundle context. */
private final BundleContext bundleContext;
@@ -62,7 +68,7 @@
this.plugin = plugin;
this.bundleContext = context;
// check if event admin is already available
- final ServiceReference ref = this.bundleContext.getServiceReference(EVENT_ADMIN_CLASS_NAME);
+ final ServiceReference<?> ref = this.bundleContext.getServiceReference(EVENT_ADMIN_CLASS_NAME);
if ( ref != null )
{
bindEventAdmin(ref);
@@ -70,7 +76,7 @@
// check if config admin is already available
this.configAdminServiceId = null;
- final ServiceReference cfaRef = this.bundleContext.getServiceReference(CONFIGURATION_ADMIN_CLASS_NAME);
+ final ServiceReference<?> cfaRef = this.bundleContext.getServiceReference(CONFIGURATION_ADMIN_CLASS_NAME);
if ( cfaRef != null )
{
final Long id = (Long)cfaRef.getProperty(Constants.SERVICE_ID);
@@ -98,9 +104,10 @@
/**
* @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent)
*/
+ @Override
public void serviceChanged(final ServiceEvent event)
{
- final ServiceReference ref = event.getServiceReference();
+ final ServiceReference<?> ref = event.getServiceReference();
final String[] objectClasses = (String[])ref.getProperty(Constants.OBJECTCLASS);
if ( objectClasses != null)
{
@@ -112,6 +119,7 @@
{
new Thread()
{
+ @Override
public void run()
{
try {
@@ -125,6 +133,7 @@
{
new Thread()
{
+ @Override
public void run()
{
try {
@@ -142,6 +151,7 @@
{
new Thread()
{
+ @Override
public void run()
{
try {
@@ -155,6 +165,7 @@
{
new Thread()
{
+ @Override
public void run()
{
try {
@@ -169,14 +180,14 @@
}
}
- synchronized void bindEventAdmin(ServiceReference ref)
+ synchronized void bindEventAdmin(ServiceReference<?> ref)
{
if ( this.eventAdminServiceRef != null)
{
this.unbindEventAdmin(this.eventAdminServiceRef);
}
this.eventAdminServiceRef = ref;
- final Dictionary props = new Hashtable();
+ final Dictionary<String, Object> props = new Hashtable<>();
props.put( Constants.SERVICE_DESCRIPTION, "Event handler for the Apache Felix Web Console" );
props.put( Constants.SERVICE_VENDOR, "The Apache Software Foundation" );
props.put( "event.topics", "*"); //$NON-NLS-1$ //$NON-NLS-2$
@@ -186,7 +197,7 @@
new EventHandler(this.plugin.getCollector()), props);
}
- synchronized void unbindEventAdmin(ServiceReference ref)
+ synchronized void unbindEventAdmin(ServiceReference<?> ref)
{
if ( this.eventAdminServiceRef != null && this.eventAdminServiceRef.equals(ref) )
{
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OsgiUtil.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OsgiUtil.java
index 3abac87..b4471f6 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OsgiUtil.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/OsgiUtil.java
@@ -18,7 +18,11 @@
*/
package org.apache.felix.webconsole.plugins.event.internal;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.List;
/**
* The <code>OsgiUtil</code> is a utility class providing some usefull utility
@@ -26,7 +30,7 @@
*/
public class OsgiUtil {
- public static boolean toBoolean(Dictionary props, String key, boolean defaultValue)
+ public static boolean toBoolean(Dictionary<String, ?> props, String key, boolean defaultValue)
{
Object propValue = toObject( props, key );
if (propValue instanceof Boolean)
@@ -41,13 +45,13 @@
return defaultValue;
}
- public static String toString(Dictionary props, String key, String defaultValue)
+ public static String toString(Dictionary<String, ?> props, String key, String defaultValue)
{
Object propValue = toObject( props, key );
return (propValue != null) ? propValue.toString() : defaultValue;
}
- public static int toInteger(Dictionary props, String key, int defaultValue)
+ public static int toInteger(Dictionary<String, ?> props, String key, int defaultValue)
{
Object propValue = toObject( props, key );
if (propValue instanceof Integer)
@@ -69,7 +73,7 @@
return defaultValue;
}
- public static Object toObject(Dictionary props, String key)
+ public static Object toObject(Dictionary<String, ?> props, String key)
{
if (props == null || key == null )
{
@@ -87,7 +91,7 @@
}
else if (propValue instanceof Collection)
{
- Collection prop = (Collection) propValue;
+ Collection<?> prop = (Collection<?>) propValue;
return prop.isEmpty() ? null : prop.iterator().next();
}
else
@@ -96,7 +100,7 @@
}
}
- public static String[] toStringArray(Dictionary props, String key, String[] defaultArray)
+ public static String[] toStringArray(Dictionary<String, ?> props, String key, String[] defaultArray)
{
if (props == null || key == null )
{
@@ -125,7 +129,7 @@
{
// other array
Object[] valueArray = (Object[]) propValue;
- List values = new ArrayList(valueArray.length);
+ List<String> values = new ArrayList<>(valueArray.length);
for(int i=0; i<valueArray.length; i++)
{
final Object value = valueArray[i];
@@ -134,15 +138,15 @@
values.add(value.toString());
}
}
- return (String[]) values.toArray(new String[values.size()]);
+ return values.toArray(new String[values.size()]);
}
else if (propValue instanceof Collection)
{
// collection
- Collection valueCollection = (Collection) propValue;
- List valueList = new ArrayList(valueCollection.size());
- final Iterator i = valueCollection.iterator();
+ Collection<?> valueCollection = (Collection<?>) propValue;
+ List<String> valueList = new ArrayList<>(valueCollection.size());
+ final Iterator<?> i = valueCollection.iterator();
while ( i.hasNext() )
{
final Object value = i.next();
@@ -151,7 +155,7 @@
valueList.add(value.toString());
}
}
- return (String[]) valueList.toArray(new String[valueList.size()]);
+ return valueList.toArray(new String[valueList.size()]);
}
return defaultArray;
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PluginServlet.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PluginServlet.java
index 15d7e61..92819a5 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PluginServlet.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PluginServlet.java
@@ -27,7 +27,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@@ -44,6 +43,8 @@
public class PluginServlet extends HttpServlet
{
+ private static final long serialVersionUID = -8601361741848077124L;
+
private static final String ACTION_POST = "post"; //$NON-NLS-1$
private static final String ACTION_SEND = "send"; //$NON-NLS-1$
private static final String ACTION_CLEAR = "clear"; //$NON-NLS-1$
@@ -62,11 +63,11 @@
public PluginServlet()
{
- this.collector = new EventCollector(null);
+ this.collector = new EventCollector();
TEMPLATE = readTemplateFile(getClass(), "/res/events.html"); //$NON-NLS-1$
}
- private final String readTemplateFile(final Class clazz, final String templateFile)
+ private final String readTemplateFile(final Class<?> clazz, final String templateFile)
{
InputStream templateStream = getClass().getResourceAsStream(templateFile);
if (templateStream != null)
@@ -119,6 +120,7 @@
/**
* @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
+ @Override
protected void doPost( HttpServletRequest req, HttpServletResponse resp )
throws ServletException, IOException
{
@@ -141,7 +143,7 @@
private void renderJSON( final PrintWriter pw ) throws IOException
{
- List events = this.collector.getEvents();
+ List<EventInfo> events = this.collector.getEvents();
StringBuffer statusLine = new StringBuffer();
statusLine.append( events.size() );
@@ -155,7 +157,7 @@
{
statusLine.append( " since " );
Date d = new Date();
- d.setTime( ( ( EventInfo ) events.get( 0 ) ).received );
+ d.setTime( events.get( 0 ).received );
statusLine.append( d );
}
statusLine.append( ". (Event admin: " );
@@ -172,7 +174,7 @@
// Compute scale: startTime is 0, lastTimestamp is 100%
final long startTime = this.collector.getStartTime();
- final long endTime = (events.size() == 0 ? startTime : ((EventInfo)events.get(events.size() - 1)).received);
+ final long endTime = (events.size() == 0 ? startTime : events.get(events.size() - 1).received);
final float scale = (endTime == startTime ? 100.0f : 100.0f / (endTime - startTime));
final JSONWriter writer = new JSONWriter(pw);
@@ -186,7 +188,7 @@
// display list in reverse order
for ( int index = events.size() - 1; index >= 0; index-- )
{
- eventJson( writer, ( EventInfo ) events.get( index ), index, startTime, scale );
+ eventJson( writer, events.get( index ), index, startTime, scale );
}
writer.endArray();
@@ -195,6 +197,7 @@
}
+ @Override
protected void doGet( HttpServletRequest request, HttpServletResponse response )
throws ServletException, IOException
{
@@ -272,10 +275,10 @@
jw.object();
if ( info.properties != null && info.properties.size() > 0 )
{
- final Iterator i = info.properties.entrySet().iterator();
+ final Iterator<Map.Entry<String, Object>> i = info.properties.entrySet().iterator();
while ( i.hasNext() )
{
- final Map.Entry current = (Entry) i.next();
+ final Map.Entry<String, Object> current = i.next();
jw.key( current.getKey().toString() );
jw.value(current.getValue());
}
@@ -285,7 +288,7 @@
jw.endObject();
}
- public void updateConfiguration( Dictionary dict)
+ public void updateConfiguration( Dictionary<String, ?> dict)
{
this.collector.updateConfiguration(dict);
}
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PropertiesEditorSupport.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PropertiesEditorSupport.java
index 1105a7b..55d81fd 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PropertiesEditorSupport.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/PropertiesEditorSupport.java
@@ -39,13 +39,13 @@
* @param request the request to process
* @return the converted properties
*/
- public static final Dictionary convertProperties(HttpServletRequest request)
+ public static final Dictionary<String, ?> convertProperties(HttpServletRequest request)
{
String keys[] = request.getParameterValues("key"); //$NON-NLS-1$
String vals[] = request.getParameterValues("val"); //$NON-NLS-1$
String types[] = request.getParameterValues("type"); //$NON-NLS-1$
- final Hashtable properties = new Hashtable();
+ final Dictionary<String, Object> properties = new Hashtable<>();
synchronized (properties)
{
for (int i = 0; keys != null && i < keys.length; i++)
@@ -85,7 +85,7 @@
}
else if ("char".equals(type)) //$NON-NLS-1$
{
- return new Character(value.toString().charAt(0));
+ return value.toString().charAt(0);
}
else if ("byte array".equals(type)) //$NON-NLS-1$
{
diff --git a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/converter/ServiceEventConverter.java b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/converter/ServiceEventConverter.java
index 0092cd1..2ddb1e6 100644
--- a/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/converter/ServiceEventConverter.java
+++ b/webconsole-plugins/event/src/main/java/org/apache/felix/webconsole/plugins/event/internal/converter/ServiceEventConverter.java
@@ -17,7 +17,10 @@
package org.apache.felix.webconsole.plugins.event.internal.converter;
import org.apache.felix.webconsole.plugins.event.internal.EventInfo;
-import org.osgi.framework.*;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceReference;
public class ServiceEventConverter {
@@ -29,7 +32,7 @@
return null;
}
- final ServiceReference ref = event.getServiceReference();
+ final ServiceReference<?> ref = event.getServiceReference();
final StringBuffer buffer = new StringBuffer( "Service " );
final Object pid = ref.getProperty( Constants.SERVICE_PID );
diff --git a/webconsole/changelog.txt b/webconsole/changelog.txt
index e2a9788..0d8e7d6 100644
--- a/webconsole/changelog.txt
+++ b/webconsole/changelog.txt
@@ -1,3 +1,9 @@
+Changes in 4.5.6
+----------------
+** Bug
+ * [FELIX-6328] - Web Console (All In One) imports javax.portlet via fileupload
+
+
Changes in 4.5.4
----------------
** Bug
diff --git a/webconsole/pom.xml b/webconsole/pom.xml
index 2da6a4e..085b56c 100644
--- a/webconsole/pom.xml
+++ b/webconsole/pom.xml
@@ -93,7 +93,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
- <version>4.2.1</version>
+ <version>5.1.1</version>
<extensions>true</extensions>
<configuration>
<instructions>
@@ -136,9 +136,6 @@
org.osgi.service.prefs;version="[1.1,2)",
org.osgi.service.wireadmin;version="[1.0,2)"
</DynamicImport-Package>
- <Include-Resource>
- {maven-resources},OSGI-INF=target/classes/OSGI-INF
- </Include-Resource>
<Embed-Dependency>
org.apache.felix.utils;inline=org/apache/felix/utils/manifest/**,
org.apache.felix.utils;inline=org/apache/felix/utils/json/**
@@ -233,7 +230,7 @@
commons-fileupload;inline=true,
commons-io;inline=true
</Embed-Dependency>
-
+ <Import-Package>!javax.portlet,*</Import-Package>
<_removeheaders>
Embed-Dependency,Private-Package,Include-Resource
</_removeheaders>