Meecrowave is a light CDI/JAX-RS server which makes it very efficient to implement any application. In coming release it will get a smoother extension point for your command line interface (CLI).

Why would I use it for a CLI? If you missed it you can have a look to Should you run your daemon/batch in Tomcat? but it can also be fancy for demo or just development: the server starts and you have a small CLI to provision some data etc...

The old way was to use the programmatic API of Meecrowave but it also meant you were loosing the default CLI of meecrowave and therefore you needed to own all that configuration :(.

Since 0.3.1 you can just inherit it!

All you have to do is to inherit from the default Meecrowave CLI and override the doWait() method to implement your own CLI:

public class MyCli extends Cli {
    private MyCli(final String[] args) {
        super(args);
    }

    @Override
    protected void doWait(final Meecrowave meecrowave, final CommandLine line) {
        System.out.println("Started");
    }

    public static void main(String[] args) {
        new MyCli(args).run();
    }
}

In this snippet the server will start, log Started and stop since doWait() is intended to wait for the end of the application. If you think about the CLI usage, it matches exactly the exit flow, other commands just need to loop until exit is entered :).

The other nice thing about this is you can access the command line. It would be pointless if we would just get the meecrowave options but since meecrowave CLI options were already extensible it allows you to access your custom one and just merge the meecrowave and your application options in a single consistent set of options.

To add custom options just create a class implementing org.apache.meecrowave.runner.Cli.Options (it doesn't require any method, it is just a marker to load the class), register it with a standard SPI mecanism (META-INF/services/org.apache.meecrowave.runner.Cli$Options should contain the qualified name of your option class) and decorate option fields with @CliOption. Here is an example:

public class MyOptions implements Options {
    @CliOption(name = "my-option", description = "Support nice option for my blog")
    private String option;
}

With that class registered your can just add to your main parameters something like:

--my-option blog

Therefore your doWait() can now be:

@Override
public void doWait(final Meecrowave meecrowave, final CommandLine line) {
    System.out.println("Started with option: " + line.getOptionValue("my-option"));
}

At that point we can implement a custom CLI and access main parameters, what we miss is the actual CLI implementation.

Overall idea is to implement:

@Override
public void doWait(final Meecrowave meecrowave, final CommandLine line) {
    String command;
    while ((command = nextCommand()) != null) {
        execute(command);
    }
}

Then the way to read commands and execute them is up to you. There are tons of frameworks and I'll not detail them all but I'd like to mention that using classpath deployment of meecrowave you can map commands on....CDI beans :). It means you can map commands on your repositories, access directly your entity manager or any part of your application. This is very powerful!

Personally I use this stack:

  • line reading; jline
  • conversion of the line to a command (String -> String[]): a CommandLineTokenzer, idea has this one for instance
  • execution: depends the application but tomitribe-crest or jcommander are very nice once integrated with CDI for the command execution.

Indeed if you have a few commands you can just use the CommandLine and don't need much more.

Last point before letting you implement your own CLI: it can be interesting to reverse the pattern and just fire the command as a CDI event and let observers handle it or not:

@Override
public void doWait(final Meecrowave meecrowave, final CommandLine line) {
    String command;
    while ((command = nextCommand()) != null) {
        CDI.current().getBeanManager()
           .fireEvent(new CommandEvent(toStringArray(command)));
    }
}

A more advanced option would be to extract the first argument to match a qualifier:

@Override
public void doWait(final Meecrowave meecrowave, final CommandLine line) {
    String command;
    while ((command = nextCommand()) != null) {
        final String[] args = toStringArray(command);
        if (args.length == 0) {
            continue;
        }
        CDI.current().getBeanManager()
           .fireEvent(new CommandEvent(args), new CommandLiteral(args[0]));
    }
}

This allows to implement the commands as:

@ApplicationScoped
public class ListCommand {
    public void execute(@Observes @Command("list") final CommandEvent event) {
       // do the list logic
    }
}

Very nice thing about that solution is you don't need to register the commands but commands are registered by themself and each command can parse the arguments in a different way if needed.

To be complete this pattern would only need in the event a wasExecuted flag to be able to log it was ignored from the Cli which is important to report to the user even if desired as behavior.

Happy tooling!

From the same author:

In the same category: