When embedding an OSGi framework, usually the application provides
some services to bundles or consumes some services from bundles. In this
article I describe how the application consumes services from bundles.
The Service
The bundles shall be able to provide additional commands to
the application. These commands will be registered in the OSGi framework
as services. To do that, an interface shall be defined under which
the services are available. This interface is defined in the bundle example-set2cmdapi.
public interface Command extends Action {
/* No additional features */
}
The commands implement the javax.swing.Action interface. To keep things
simple, a Command interface is created which just extends javax.swing.Action.
Bundles provide commands by implementing the Command interface
and registering the instances in the OSGi framework under that interface.
The Service Implementation in the Bundle
The bundle example-set2svc1 contains a BundleActivator instance. When
the bundle is started, it creates Command instances and registers them
in the OSGi framework. The Activator class is shown below:
public class Activator implements BundleActivator {
Logger logger = LoggerFactory.getLogger(Activator.class);
List<ServiceRegistration<Command>> svcRegs =
new ArrayList<>();
List<Command> svcs = new ArrayList<>();
@Override
public void start(BundleContext context) throws Exception {
logger.info("Set2Svc1 Bundle: start()");
svcs.add(new HelloAction());
svcs.add(new HelloAction2());
for (Command cmd : svcs) {
ServiceRegistration<Command> svcReg =
context.registerService(Command.class, cmd, null);
svcRegs.add(svcReg);
}
}
@Override
public void stop(BundleContext context) throws Exception {
logger.info("Set2Svc1 Bundle: stop()");
for (ServiceRegistration<Command> svcReg : svcRegs) {
svcReg.unregister();
}
svcRegs.clear();
svcs.clear();
}
static class HelloAction extends AbstractAction implements Command {
(... creates a JDialog and shows it ...)
}
static class HelloAction2 extends AbstractAction implements Command {
(... creates a JDialog and shows it ...)
}
}
In the start method, the bold lines register a Command instance
in the OSGi framework under the Command interface. In the stop method the
service is unregistered.
Using the Service in the Application
The application collects all services under the Command interface
from the OSGi framework and adds the instances to the toolbar. To do this,
it uses a ServiceTracker. This is a very convenient class which observes
the service registrations in the framework and notifies when things change.
Without ServiceTracker the application would need to take care of
services being added and removed throughout the applications lifetime.
Given that an OSGi setup is multithreaded and concurrent modifications
are likely to occur, deadlocks are waiting around each corner.
One might think about polling the services at regular intervals, however,
all this is not really easier. ServiceTracker is a very handy class provided
by the OSGi framework and it pays off quickly to use it.
The class CommandObserver takes care of looking up the services for the
application. It is located in the package example.guitool.impsvc.
public class CommandObserver {
final JToolBar toolBar;
final Map<Command, JButton> cmdButtonMap = new HashMap<>();
final CT ct;
public CommandObserver(JToolBar toolBar, BundleContext context) {
super();
this.toolBar = toolBar;
this.ct = new CT(context);
}
public void open() {
ct.open();
}
public void close() {
ct.close();
}
public void addCommand(final Command cmd) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JButton b = toolBar.add(cmd);
cmdButtonMap.put(cmd, b);
}
});
}
public void removeCommand(final Command cmd) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JButton b = cmdButtonMap.remove(cmd);
if (b!=null) {
toolBar.remove(b);
toolBar.revalidate();
}
}
});
}
class CT extends ServiceTracker<Command, Command> {
public CT(BundleContext context) {
super(context, Command.class, null);
}
@Override
public Command addingService(ServiceReference<Command> reference) {
Command cmd = super.addingService(reference);
addCommand(cmd);
return cmd;
}
@Override
public void modifiedService(ServiceReference<Command> reference,
Command service) {
super.modifiedService(reference, service);
}
@Override
public void removedService(ServiceReference<Command> reference,
Command service) {
super.removedService(reference, service);
removeCommand(service);
}
}
}
The CommandObserver class creates an instance of the CT class which is a
subclass of ServiceTracker. Three methods are overridden. These three
methods are called by the ServiceTracker when services are added,
modified or removed. The CT class just calls addCommand and removeCommand
of the CommandObserver class from within these three methods.
When a Command service is added, the Command instance is added to the
toolbar. When a Command service is removed, it must be removed from the
JToolBar. However, the JToolBar allows adding Action instances, but does not
support removing them. When an Action is added with JToolBar.add(), the method
returns a JButton instance which wraps the Action. The CommandObserver
class stores these JButton instances in a map. When a Command shall be
removed, the matching JButton is fetched from the map and used to remove the
Action from the JToolBar.
Running
Time to try it. When the guitool is started, the toolbar at the
bottom of the window doesn't contain any buttons. When the bundle file
example-set2svc1.jar is installed and started, two buttons will appear on that
toolbar.
When the bundle is stopped, the buttons will disappear.
Bundle Wiring
Since the CommandRegistry service is provided by the application (guitool)
which embeds the OSGi framework, additional steps must be taken for
the visibility of the packages.
The bundle example-set2svc1 imports the package example.set2cmdapi.api
which contains the interface Command. The guitool application
must have this package and interface on its classpath. A wiring between
the bundle example-set2svc1 and the system classpath must be established
now.
There is a configuration entry in the OSGi framework, which
"connects" the system classloader with
bundle classloaders. The OWGiFwLoader class contains a method prepareConfigMap, which
is shown below:
private void prepareConfigMap(Map<Object, Object> configMap) {
...
// List the packages which shall be exported by the System Bundle.
configMap.put(
Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA,
"example.set2appapi.api, " +
"example.set2cmdapi.api, " +
"org.slf4j");
...
}
The entry Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA defines the packages
which are exported by the system bundle. So if a bundle wants to import
the package example.set2cmdapi.api, it does not "connect" to another
bundle's classloader, but to the system bundle and thus to the system classloader.
(The system classloader is the parent classloader of the system bundle.)
Since the classes of the bundle example-set2cmdapi are already available with the system classloader,
the bundle does not need to be installed. This can be checked by starting the bundle
set2svc1 without installing set2cmdapi. It will start and register the commands
in the toolbar.
Bundle Wiring Details
The following diagram describes the bundle wiring when two bundles
are involved and the embedding application does not need to access the package.
The diagram is using the conventions described in the OSGi spec.
Gray boxes represent bundles. White boxes with package names indicate,
that the package is required by the bundle; black boxes provide the package.
Hence a wiring exists between the white and the black box, because one
bundle uses the package which is provided by the other.
The class Activator uses the Command interface for the HelloAction and HelloAction2 classes,
and to register the Actions in the OSGi framework under that interface.
Things get a bit more complicated when the embedding application uses or
provides the package. The following diagram shows among other things the wiring
when the application consumes Command service from other bundles.
The application is started as a standard Java application without knowing
about OSGi initially. The CommandObserver class uses the
Command interface and thus this interface must be on the classpath.
This is the system classpath. The package example.set2cmdapi.api is denoted
as a black box in the gray application box.
When the OSGi framework is started, the framework acts as the system bundle.
The system bundle has its own classloader just like any other bundle has its
own classloader.
The configuration entry FRAMEWORK_SYSTEMPACKAGES_EXTRA declares
packages which shall be exported by the system bundle. For these packages
the system bundle sets the parent classloader to the system classloader.
So it "forwards" the wiring from the white package box in bundle
example-set2svc1 to the black package box which actually is available
by the system classloader.
The bundle example.set2cmdapi is not needed in this scenario, because the
package is already provided. Hence it is "crossed out" in the diagram.
Note: In the guitool project the set2cmdapi bundle jar file is simply added
to the classpath. It is used as a standard jar file and the extra
information about OSGi is ignored this way. It will not appear as a bundle
in the OSGi frame just by being on guitool's classpath.
Resources