How to write an OSGI command-line application
Asked Answered
U

2

8

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?

Unorthodox answered 19/7, 2013 at 9:44 Comment(1)
It's an interesting problem, made even harder if you want to allow packages to consume discoverable command-line options (which was our use-case). Alas, our final solution was rather heavyweight, and I've not had the time to distill it into something usable by others yet…Bereft
G
9

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.

Garrison answered 19/7, 2013 at 12:42 Comment(4)
Thanks for your answer. That sounds pretty easy indeed. Do you happen to know whether the package command is exposed by maven-bundle-plugin?Unorthodox
What do you mean by Object.class? That should simply be removed, making it a regular setter, right?Unorthodox
I think it should be an Object parameter. Unfortunately you can't just remove it, even though it's only the Map parameter that you're interested in.Cocke
The package commnand is only available in (bnd)toolsGarrison
U
1

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.

Unorthodox answered 19/7, 2013 at 9:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.