Samstag, 26. Januar 2013

Consuming Services in Embedding Applications from Bundles

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

Keine Kommentare:

Kommentar posten