As with most things, there are multiple ways to skin a cat. The route I chose was to embed Equinox in a Java app and mediate command line access through this class. Fortunately, most of the work is already done for us via the EcliseStarter class (if you're on Felix, check out this). Assuming Equinox is on your classpath, simply calling
EclipseStarter#startup()
will fire up the Equinox runtime. More importantly, it will give you a BundleContext
which you can use to interact with the OSGi framework. Once we have a BundleContext, we can do interesting things like install and start additional bundles:
public static void main(final String[] args) throws Exception {
// start the framework
context = EclipseStarter.startup(new String[0], null);
// install all bundles
installAllPlugins();
// start our platform bundles
startPlugin("org.eclipse.core.runtime");
// start plugins
for (Bundle b : context.getBundles()) {
startPlugin(b.getSymbolicName());
}
...
The final piece is to do the command line interaction. For this, I created an interface that bundles can publish services under to make them available to the command line:
public interface ICommand {
/**
* Execute this command.
*
* @param args
* the args.
* @return the return value.
*/
Object execute(String[] args) throws Exception;
/**
* Gets the help text that explains this command.
*
* @return the help text.
*/
String getHelp();
}
Unfortunately since there is a lot of classloader magic going on, we can't just get these
ICommand
classes from the service registry and invoke them directly (like we would do from inside OSGi). The OSGi classes are on a different classloader than the one we started things on. At first this may seem annoying but its actually a good thing--it means fools can't crash the OSGi implementation. So we either can specify some classloader chicanery (osgi.parentClassloader=app) or we can invoke the commands via reflection. I opted for this route because I was always taught not to mess with things you don't understand and the ClassLoader hierarchy under OSGi is definitely something I don't understand. Here's the two applicable methods:
private static Object invokeCommand(final String name, final String[] args)
throws Exception {
String filter = "(&(" + Constants.OBJECTCLASS + "="
+ ICommand.class.getName() + ")(name=" + name + "))";
ServiceReference[] services = context.getAllServiceReferences(
ICommand.class.getName(), filter);
if ((services != null) && (services.length != 0)) {
Object c = context.getService(services[0]);
if (c != null) {
Method m = c.getClass().getMethod("execute", String[].class);
return m.invoke(c, (Object) args);
}
}
return "Command not found: " + name;
private static MapgetAllCommands() {
Mapcommands = new LinkedHashMap ();
try {
ServiceReference[] services = context.getAllServiceReferences(
ICommand.class.getName(), null);
if (services != null) {
for (ServiceReference r : services) {
Object c = context.getService(r);
if (c != null) {
try {
Method m = c.getClass().getMethod("getHelp");
commands.put((String) r.getProperty("name"),
(String) m.invoke(c));
} catch (SecurityException e) {
// ignore
} catch (IllegalArgumentException e) {
// ignore
} catch (NoSuchMethodException e) {
// ignore
} catch (IllegalAccessException e) {
// ignore
} catch (InvocationTargetException e) {
// ignore
}
}
}
}
} catch (InvalidSyntaxException e) {
// should never happen
}
return commands;
}
Not my finest hour, throwing Exception, but it should get you on your way. It works like a charm in my app.
Cheers,
Josh