Samstag, 26. Januar 2013

Providing Services from Embedding Applications to 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 provides services to bundles.

The Service

The application shall offer a service so bundles can register additional commands. The interface is shown below. It is defined in the sub project bundle_set2appapi.

public interface CommandRegistry { abstract void addCommand(Action action); abstract void removeCommand(Action action); }

Bundles provide commands by implementing the interface javax.swing.Action and registering them with the CommandRegistry.addCommand method.

The Service Implementation in the Application

The application (which is the guitool project) implements the CommandRegistry interface. The implementing class is CommandRegistryImpl in the package example.guitool.expsvc:

public class CommandRegistryImpl implements CommandRegistry { final JToolBar toolBar; final Map<Action, JButton> actionToButtonMap = new HashMap<>(); ServiceRegistration<CommandRegistry> svcReg; public CommandRegistryImpl(JToolBar toolBar) { super(); this.toolBar = toolBar; } public void open(BundleContext context) { svcReg = context.registerService(CommandRegistry.class, this, null); } public void close() { svcReg.unregister(); } @Override public void addCommand(final Action action) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { JButton b = toolBar.add(action); actionToButtonMap.put(action, b); } }); } @Override public void removeCommand(final Action action) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { JButton b = actionToButtonMap.remove(action); if (b==null) return; toolBar.remove(b); toolBar.revalidate(); } }); } }

The application creates an instance of CommandRegistryImpl and calls the open method. The open method registers the instance (note the second argument this to the registerService call) under the interface CommandRegistry in the OSGi framework. The returned ServiceRegistration is later used to unregister again.

The CommandRegistryImpl adds the Action instances to a JToolBar. The JToolBar can only remove Component instances and not Actions. The JToolBar.add() method returns a JButton instance which wraps the Action. This JButton is stored in the actionToButtonMap so it can be looked up when the Action shall be removed. The revalidate() is needed to let the JToolBar update its appearance after the remove() call.

Note that since the addCommand and removeCommand methods can be called from outside the EDT, the actual work is scheduled to run on the EDT with the SwingUtilities.invokeLater method.

The class CommandRegistryImpl is used like this:

commandRegistry = new CommandRegistryImpl(pluginCmdToolBar); ... commandRegistry.open(osgiFwLoader.getBundleContext());

Using the Service in a Bundle

The bundle bundle_set2cons1 uses the CommandRegistry service. When the bundle is started, the CommandRegistry service is looked up and the Action instance is added with the CommandRegistry.addCommand() call. When the bundle is stopped, the Action instance is removed. The methods for starting and stopping a bundle are implemented in the bundle activator. The source code is shown below:

public class Activator implements BundleActivator { Logger logger = LoggerFactory.getLogger(Activator.class); Action action; @Override public void start(BundleContext context) throws Exception { logger.info("Set2Cons1 Bundle: start()"); action = new HelloAction(); ServiceReference<CommandRegistry> svcRef = context.getServiceReference(CommandRegistry.class); if (svcRef!=null) { try { CommandRegistry cmdReg = context.getService(svcRef); if (cmdReg!=null) { cmdReg.addCommand(action); } } finally { context.ungetService(svcRef); } } } @Override public void stop(BundleContext context) throws Exception { logger.info("Set2Cons1 Bundle: stop()"); ServiceReference<CommandRegistry> svcRef = context.getServiceReference(CommandRegistry.class); if (svcRef!=null) { try { CommandRegistry cmdReg = context.getService(svcRef); if (cmdReg!=null) { cmdReg.removeCommand(action); } } finally { context.ungetService(svcRef); } } action = null; } static class HelloAction extends AbstractAction { // details omitted for brevity // (creates a JDialog and presents it on screen.) } }

In the bold lines the CommandRegistry service is fetched and CommandRegistry.addCommand() (in Activator.start()) and CommandRegistry.removeCommand() (in Activator.stop()) are called. Note that the returned ServiceReference may be null, which means, that no CommandRegistry service is available right now. Also note that after the CommandRegistry service is not needed anymore, the ungetService method is called, to let the OSGi framework know, that the service is not needed.

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-set2cons1.jar is installed and started, a button will appear on that toolbar.

When the bundle is stopped, the button 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-set2cons1 imports the package example.set2appapi.api which contains the interface CommandRegistry. The guitool application must have this package and interface on its classpath. A wiring between the bundle example-set2cons1 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.set2appapi.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-set2appapi are already available with the system classloader, the bundle does not need to be installed. This can be checked by starting the bundle set2cons1 without installing set2appapi. It will start and register the command 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 CommandRegistry interface to add and remove Action instance by calling CommandRegistry.addCommand and CommandRegistry.removeCommand.

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 provides the CommandRegistry service to other bundles.

The application is started as a standard Java application without knowing about OSGi initially. The CommandRegistryimpl class implements the CommandRegistry interface and thus this interface must be on the classpath. This is the system classpath. The package example.set2appapi.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-set2cons1 to the black package box which actually is available by the system classloader.

The bundle example.set2appapi 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 set2appapi 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 veröffentlichen