The basic problem is of course that you cannot access the artefacts that you deploy in a bundle directly from your test case, because they are loaded from different classloaders.
Most solutions to the problem somehow bring the test case into the OSGi Framework such that it can access the test bundle. They run the test and finally communicate the results back to the test framework. This approach is well documented in Chapter 11 of the Spring Dynamic Modules Reference Guide
Lets have a look at the specific JBossOSGi test requirements
- OSGi Framework agnostic
- non-intrusive to the test bundle
- works for embedded and remote test scenarios
- simple and fast to implement
- Test Case registers a LogListener with the LogReaderService
- Test bundle logs messages to the LogService
- Test Case verifies the order and content of the received log messages
But let's talk a little detour into the Log Service from the OSGi compendium specification.
LogService – The service interface that allows a bundle to log information, including a message, a level, an exception, a ServiceReference object, and a Bundle object.
LogEntry - An interface that allows access to a log entry in the log. It includes all the information that can be logged through the Log Service and a time stamp.
LogReaderService - A service interface that allows access to a list of recent LogEntry objects, and allows the registration of a LogListener object that receives LogEntry objects as they are created.
LogListener - The interface for the listener to LogEntry objects. Must be registered with the Log Reader Service.
First a test bundle needs to obtain an instance of the LogService. Here is a bad way of doing this
String sName = LogService.class.getName();The code above has two issues
ServiceReference sRef = ctx.getServiceReference(sName);
LogService log = sysContext.getService(sRef);
- It assumes that there is LogService registered
- It does not detect when the LogService disappears
String sName = LogService.class.getName();The code above initializes the log instance with the SystemLogService that is part of the jboss-osgi-common.jar bundle. The tracker overrides the log instance when an actual LogService gets registered. The tracker restores the SystemLogService, in case the LogService disappears again.
LogService log = new SystemLogService(context);
tracker = new ServiceTracker(context, sName, null)
public Object addingService(ServiceReference reference)
log = (LogService)super.addingService(reference);
public void removedService(ServiceReference reference, Object service)
log = new SystemLogService(context);
This LogService tracking code is so common that the jboss-osgi-common.jar bundle already contains a tracker which does all of the above. Hence, the best way to obtaining the LogService with JBossOSGi ist
Now that the test bundle actually has an instance of the LogService it can start logging messages to that service. These messages are seen by the LogReaderService that the test case can register a LogListener with.LogService log = new LogServiceTracker(context);
In the test case you would create a new LogEntryCache and associate one or more LogEntryFilter objects with it.
LogEntryCache logEntryCache = new LogEntryCache();The LogEntryCache is then added to the LogReaderService using a ServiceTracker similar to the one above. Because the filters are 'or' combined the cache stores log enties that pass at least one filter in the list. Each filter has three filter criteria
LogEntryFilter filter = new LogEntryFilter("example-log(.*)", 0, null);
- A regular expression that matches a Bundle-SymbolicName
- A minimum LogLevel, whereas 0 indicates any LogLevel
- A regular expression that matches the log message
List entries = logEntryCache.getLog();In the remote test scenario it works essentially in the same way, only that a RemoteLogListener sends the LogEnties to a RemoteLogReaderService. The local test case registers it's LogEntryCache with the RemoteLogReaderService.
assertEquals("Number of entries", 1, entries.size());
assertEquals("[ServiceA] new Service", entries.get(0).getMessage());
The actual communication is done with the JBoss Remoting socket transport. Both, the RemoteLogListener and the RemoteLogReaderService are part of the jboss-osgi-remotelog.jar bundle. That bundle needs to be installed with matching properties in the local and the remote OSGi Framework.
- org.jboss.osgi.service.remote.log.sender - Enable the server side LogListener
- org.jboss.osgi.service.remote.log.reader - Enable the client side LogReaderService
- org.jboss.osgi.service.remote.log.host - The remote host that log messages are sent to
- org.jboss.osgi.service.remote.log.port - The remote port that log messages are sent to
In my next post I'm going to look into the Blueprint Service (RFC-124) specification, which provides a rich component model for declaring components within a bundle and for instantiating, configuring, assembling and decorating such components - stay tuned.
May this be useful