Wednesday 2 December 2009

Hello Tatooine

So in my last couple of posts I've been showing the power of Nimble. You will have noticed that it is primarily a console environment. As such you may be wondering how you can provide your own commands to execute in the Nimble shell - Posh (Paremus OSGi Shell).

Posh is an implementation of the command line interface specified in RFC-147 from the OSGi alliance. If you are familiar with OSGi development you will know that to date every framework implementation has defined it's own particular API for providing command line utilities within an OSGi runtime. This has meant that there is significant duplication of effort when writing commands to work in the various environments.

To this end the alliance proposed RFC 147 in order to provide a common standard that different frameworks could implement such that a command that worked in one framework could work unchanged in another. The initial implementation of RFC 147 was developed primarily by Peter Kriens and donated to the Felix project earlier this year. Since then there have been a number of maintenance releases and it has been included as a component of the Felix Karaf container and the Nimble container from Paremus.

This gives you some background, so now the standard thing for me to do would be to write a trivial hello world application. But that's no fun, so instead of conforming to the norm I thought it would be more interesting to port the Starwars Asciimation work to run in OSGi as an RFC 147 command line interface.




Yep this is very probably the geekiest post you will ever see... :)

I hasten to add that I did not undertake the core task of this myself, instead I took the liberty of contacting Simon Jansen (the author of Asciimation) to ask his permission to "borrow" the ascii text from his player. This he was very kind to do.

The first thing we need to do to define our cli is define a class that implements the core functionality as shown below:

package org.chronologicalthought;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.net.URL;

public class Starwars {
public void starwars() throws IOException, InterruptedException {
play(67);
}

public void starwars(int frameLength) throws IOException, InterruptedException {
URL res = Starwars.class.getResource("/starwars.txt");
if (res == null)
throw new IllegalStateException("Missing resource");
InputStream in = res.openStream();
try {
InputStreamReader reader = new InputStreamReader(new BufferedInputStream(in));
render(reader, System.out, frameLength);
} finally {
in.close();
}
}

private void render(Reader reader, PrintStream out, int frameLength) {
// ...
}
}

Here the command provides two methods, play and play(int) and prints the individual frames from the "starwars.txt" file embedded in our bundle to System.out.

Wait a minute you might be thinking. Where's the API to the CLI? Well this is one of the neat things about RFC 147 you don't need to write your code to any API. The specification provides a clever utility in the form of a ThreadIO service that multiplexes the references to System.in, System.out, and System.err so the command can interact with the user. It also calls methods on the class reflectively so there is no need to implement a defined interface. Simply declare a method and Posh will attempt to convert arguements supplied from the command line to match the method signature.

The next step is to define an activator that publishes our cli class to the OSGi bundle context.

package org.chronologicalthought;

import java.util.Hashtable;
import org.osgi.service.command.CommandProcessor;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator {

public void start(BundleContext ctx) throws Exception {
Hashtable props = new Hashtable();
props.put(CommandProcessor.COMMAND_SCOPE, "ct");
props.put(CommandProcessor.COMMAND_FUNCTION, new String[] { "starwars" });
ctx.registerService(Starwars.class.getName(), new Starwars(), props);
}

public void stop(BundleContext ctx) throws Exception {
}
}

This activator publishes the Starwars class with two attributes:

  • CommandProcessor.COMMAND_SCOPE - a unique namespace for our command
  • CommandProcessor.COMMAND_FUNCTION - the names of the methods to expose as commands in the cli

The code is available from here for those who want to take a look around:

Now the final stage is to load our command within nimble. Since my cli has such a trivial set of dependencies I will defer the usage of the full nimble resolution to another blog post. So instead I will use a set of trivial commands which I've defined here. So finally let the show commence:

$ svn co http://chronological-thought.googlecode.com/svn/trunk/starwars
$ cd starwars
$ ant
$ posh
Paremus Nimble 30-day license, expires Wed Dec 30 23:59:59 GMT 2009.
________________________________________
Welcome to Paremus Nimble!
Type 'help' for help.
[feynman.local.0]% source http://chronological-thought.googlecode.com/svn/trunk/nimble-examples/basic-commands.osh
[feynman.local.0]% installAndStart file:build/lib/org.chronologicalthought.starwars.jar
[feynman.local.0]% starwars





WWW.ASCIIMATION.CO.NZ


presents




The final piece of Nimble functionality that it would be fun to demonstrate is stopping the movie. Simply hit Ctrl-C. The nimble shell then sends a Thread.interrupt to the currently running command. For those who want to see the movie to the end and don't want to wait try running:

% starwars 20

To set the frame length as 20 milliseconds.

Enjoy the show.

Laters.

No comments: