Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write an OSGI command-line application

I'm currently getting my feet wet with OSGI and decided to go for a slightly atypical OSGI use-case. I'd like to use it in a command-line application. I want a main(..) method that takes some flags and arguments, does something and shuts down again. What I don't want is to start Apache Karaf (or similar) and run commands within the OSGI console (this could become an optional feature though).

Why OSGI for a command-line application in the first place? The application is supposed to use different versions of the same library (elasticsearch that is). And simply because it's bad-ass of course.

Should I consume the service within a bundle or outside? How would one do that? What problems could arise?

like image 862
sfussenegger Avatar asked Jul 19 '13 09:07

sfussenegger


2 Answers

There is a very easy way to write command line apps when you use bnd. bnd has a function to create an executable jar with the package command:

 $ bnd run xyz.bnd
 .... whatever your app does
 $ bnd package xyz.bnd
 $ ls
   xyz.jar  xyz.bnd .....
 $ java -jar xyz.jar ...
 .... whatever your app does

Note that this jar is complete, it contains ALL the bundles, the framework, the launcher, and the properties to run it. There are no external dependencies.

The trick is to get the main thread (where static main is called in). The only thing you have to do is register a Runnable service with a property main.thread=true. The launcher will then call run() on this service and then exit (you can stay in the run as long as you want).

To get the command line arguments, you can get the Object service with the launcher.arguments property. This property will have your command arguments. Or to do this with a DS component:

 @Component(immediate=true, property="main.thread=true")
 public class Main implements Runnable {
     String[] args;

     public void run(){ ... }

     @Reference(target="(launcher.arguments=*)")
     void setArgs(Object service, Map<String,Object> props) {
        this.args = (String[]) props.get("launcher.arguments");
     }
 }

The best way to do this is with bndtools since it makes it easy to test/debug your code. You likely want to use bndrun files then.

P.S. In the latest bnd you can use a Callable<Integer> instead of a Runnable. The return value is then the exit code of the process. This might, however, not yet be present in bndtools.

like image 175
Peter Kriens Avatar answered Oct 02 '22 07:10

Peter Kriens


To answer my own question (Q&A style): I currently believe would be best to

  1. start OSGI embedded (clean)
  2. start the container
  3. export API package (org.osgi.framework.system.packages.extra)
  4. install and start the required bundles
  5. consume services outside of OSGI
  6. shutdown the container
  7. exit the app

After all bundles are started it's safe to assume the required services are available. This also avoids passing configuration into OSGI that's actually only arguments to a service invocation.

like image 31
sfussenegger Avatar answered Oct 02 '22 08:10

sfussenegger