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

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

Samstag, 19. Januar 2013

Using Swing in a Bundle

If a bundle shall provide a user interface, it could create a JFrame, add some components and present it on screen. (For instance assume you built some home automation and a fridge monitoring bundle shall present some feedback when the temperature is too high.)

Create and Dispose the JFrame

One approach is to create the JFrame when the bundle's start method is called. When the bundle is stopped, it should clean up and thus dispose the JFrame. Bundles may have an "Activator" which is an implementation of the BundleActivator interface. The interface defines a start method - which is called by the framework when the bundle is started - and a stop method - which is called by the framework when the bundle is stopped.

The OSGi snippets project contains a "bundleswing1" project which contains exactly one class:

public class Activator implements BundleActivator { Logger logger = LoggerFactory.getLogger(Activator.class); volatile JFrame frame; @Override public void start(BundleContext context) throws Exception { logger.info("Swing1 Bundle: start()"); // All access to Swing must be on the EDT. This start method // may have been called outside the EDT, so the JFrame // creation must be put into the EDT. And this is how // it is done: SwingUtilities.invokeLater(new Runnable() { @Override public void run() { frame = new JFrame("Swing1 Bundle"); Container contentPane = frame.getContentPane(); contentPane.setLayout( new BoxLayout(contentPane, BoxLayout.PAGE_AXIS)); contentPane.add(new JLabel( "JFrame created in Activator.start(), stop bundle to dispose")); contentPane.add(new JButton(new HelloAction())); contentPane.add(new JButton(new HelloSharedOwnerAction())); frame.setSize(300, 200); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } @Override public void stop(BundleContext context) throws Exception { logger.info("Swing1 Bundle: stop()"); // Access Swing in EDT - see comment in start method: SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (frame!=null) { frame.dispose(); frame = null; } } }); } ... (two action classes) }

In the guitool, start the framework, install the bundle file named "bundleswing1" from the files list and start it in the bundles list. (Bundle files are on the right hand side and bundles are on the left hand side in the guitool scteen. Bundles appear when bundle files are installed.)

The bundle starts and a JFrame is shown:

Pressing the "Stop" button in the guitool stops the bundle and closes the JFrame.

To use Swing classes, they must be available to the bundle, otherwise a ClassNotFoundException is thrown. The bundle has a MANIFEST file which among other things contains these two lines:

Bundle-Activator: example.swing1.impl.Activator Import-Package: org.osgi.framework, org.slf4j, javax.swing

The entry Bundle-Activator defines the class which is called when the bundle shall be started or stopped. The entry Import-Package defines the java packages which shall be available to the bundle which in this case must include javax.swing.

Playing with Import-Package

As a little exercise, you may want to change the Import-Package entry and see what happens. The build scripts create the Import-Package entry from information stored in the build.default.properties file in the project. This file is in the root of project bundleswing1. In the line prj.bundle.import-package=org.osgi.framework, org.slf4j, javax.swing remove the javax.swing package and save the file. Then build the bundle by either running build-bundles.xml or the project's build.xml script. Install the bundle and start it. The bundle information will contain "Activator start error in bundle" and the Eclipse console will show " java.lang.ClassNotFoundException: javax.swing.JLabel not found by example.swing1 [2]".

The Shared Owner Frame

When creating a JDialog with a null owner, Swing creates a so called "shared owner frame". This will prevent a clean exit, when - as usual - System.exit shall not be used.

The JFrame contains two buttons. The first refers to an Action which uses JOptionPane.createDialog with the main JFrame as owner. As a test, do these steps:

  1. Start guitool
  2. Start Framework
  3. Install and start bundleswing1
  4. Press the "Hello" button (a message box will apear - do not close it!)
  5. Stop the bundle
  6. Exit guitool
  7. The application will terminate and the JVM exits. This is the expected behaviour.

The class HelloSharedOwnerAction looks like shown below. Interesting are the lines marked with bold font. When passing null to createDialog, Swing sets a Shared Owner Frame as parent of the JDialog. The two bold lines in the propertyChange method fetch the owner and dispose it.

class HelloSharedOwnerAction extends AbstractAction { private static final long serialVersionUID = 1L; public HelloSharedOwnerAction() { super(); putValue(NAME, "Hello Shared Owner"); } @Override public void actionPerformed(ActionEvent e) { JOptionPane p = new JOptionPane( "Hello there! My owner is the Shared Owner Frame"); final JDialog dlg = p.createDialog(null, "Hello"); dlg.setModalityType(ModalityType.MODELESS); p.addPropertyChangeListener("value", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { dlg.dispose(); Window parent = dlg.getOwner(); parent.dispose(); } }); dlg.setVisible(true); } }

As a test, comment these two lines out (the ones around parent.dispose()) and repeat the steps listed above, except that the button "Hello Shared Owner" is used. The guitool will disappear when pressing "Exit", but the JVM will not terminate. It does so, because a displayable component still exists. The two commented lines were disposing that component.

Resources

Donnerstag, 17. Januar 2013

Embedding OSGi

The guitool in OSGi snippets is a plain Java application. In order to use OSGi, the OSGi framework must be started somehow. This is usually called "embedding the OSGi framework". In the OSGi snippets project the class OSGiFwLoader takes care of that.

Using OSGiFwLoader

The class OSGiFwLoader is used like this:

OSGiFwLoader osgiFwLoader = new OSGiFwLoader(); osgiFwLoader.start();

Eventually the framework needs to be stopped:

osgiFwLoader.requestStop();osgiFwLoader.waitForStop();

Usually bundles should be installed:

Bundle bundle = osgiFwLoader.installBundle( new File("somebundle.jar"));

and started:

osgiFwLoader.start(bundle);

Implementation of OSGiFwLoader

The constructor does nothing, so the first interesting method is start():

public void start() { logger.info("Starting framework..."); try { Map<Object,Object> configMap = new HashMap<Object, Object>(); prepareConfigMap(configMap); FrameworkFactory fwf = new FrameworkFactory(); fw = fwf.newFramework(configMap); fw.init(); fw.start(); } catch (Exception e) { e.printStackTrace(); } }

The relevant lines are in black. After the fw.start() call, the OSGi framework is up and running. The configMap contains configuration data, I will come back to that later.

In the guitool there is a button labelled "Start Framework". When that button is pressed, among a few other things, OSGIFwLoader.start() is called. After the framework has been started, the list of bundles has one entry:

As the text reads, it is the "System Bundle" which is the framework itself. An overview can be found here http://wiki.osgi.org/wiki/System_Bundle. The bundle id is 0 (zero) and the bundle symbolic name is "org.apache.felix,framework". The symbolic name of the system bundle is implementation dependent. Apache Felix is used here, hence the name.

Things are rather boring without other bundles, so the OSGi loader should be able to install bundles:

public Bundle installBundle(File file) { Bundle bundle; try { BundleContext bc = fw.getBundleContext(); String url = new URL("file", "", file.getPath()).toString(); bundle = bc.installBundle(url); } catch (BundleException | MalformedURLException e) { logger.error("Error installing bundle", e); bundle = null; } return bundle; }

The relevant method call is BundleContext.installBundle() here. It takes the location of the bundle file as a parameter and returns a reference to the bundle instance.

If needed, the returned bundle reference is used to start the bundle later. So its time to look at this method:

public void startBundle(Bundle bundle) { logger.info("Starting bundle..."); if (isFragmentBundle(bundle)) { logger.warn( "Bundle is a Fragment-Bundle, won't start bundle {}", bundle); return; } try { bundle.start(); } catch (BundleException e) { logger.error("Error starting bundle", e); } catch (Exception e) { logger.error("Error starting bundle", e); } }

More methods exist to stop and uninstall a bundle.

Eventually the framework should be stopped somehow. The first step is to tell the framework, that it should stop and the second step is to wait for the framework to have stopped.

public void requestStop() { logger.info("Stopping framework..."); try { fw.stop(0); } catch (BundleException e) { logger.error("Error during framework stop, ignored.", e); } }

The stop method returns immediately and the framework does the actual work in other threads. To wait for the framework to have stopped, the loader class offers the following method:

public void waitForStop() { logger.info("Waiting for framework to stop..."); try { fw.waitForStop(0); logger.info("Framework stopped..."); } catch (Exception e) { logger.error("Error while waiting", e); } }

The two methods are basically wrappers around the Framework's methods to catch Exceptions. Since the Exceptions are only logged and the caller of the OSGiFwLoader's methods wouldn't know, if any were thrown, the implementation is by far not production ready. So if you use the code, you need to change the error handling.

Going back to starting the framework, there was configuration data involved. OWGiFwLoader.start() calls this method:

private void prepareConfigMap(Map<Object, Object> configMap) { // List the packages which shall be exported by the System Bundle. configMap.put( Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, "org.slf4j"); // Each time the OSGi Framework starts, it shall clean up its // storage. I decided to do that, so one gets a clean environment // each time. configMap.put( Constants.FRAMEWORK_STORAGE_CLEAN, Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT); }

When embedding an OSGi framework into an application, that application usually provides services to bundles or uses services provided by bundles. The packages of these services must be accessible and this is accomplished with the entry Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA. In the OSGi snippets project, the slf4j logging library is used and is in the classpath of the application. The package org.slf4j is added to the configuration entry, so it is available to bundles.

The entry FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT makes sure, that the storage maintained by the OSGi implementation is cleaned each time it is run. Usually this is not needed, but for something which is used for playing around, I prefer to clean up frequently.

Resources

Montag, 14. Januar 2013

OSGi Snippets

Some time ago I was starting to learn OSGi. Every now and then I would have liked a simple test setup to play around with bundles, services a.s.o. So eventually I have decided to create one myself and publish it.

The visible center is a guitool which looks like this:

The guitool can start an OSGi framework and stop it. It observes a directory and list the bundle files on the right hand side. It observes the OSGi environment and lists the bundles on the left hand side. With a button click one can install the bundle files, start or stop the bundles a.s.o.

The sources include a set of ant files and template project files to make building bundles very simple. This simplicity comes at a price: A lot of sophisticated features are not supported. However, the main goal is to help beginners with OSGi and to keep the required tools to an absolute minimum. (A second goal is to have a simple test bed for my own little prototypes, which I may need for making decisions for larger projects.)

The project is hosted at sourceforge: https://sourceforge.net/p/osgisnippets/

The sources are licensed under a BSD-2-Clause license.

Contributions, comments, critics are welcome :)

-- Rainer Schwarze