IntroductionThe Springfield Command system allows you to write UNIX-style text-based commands within your OSGi application and make them available in the UNIX shell. Because of this, you get the following cool features: - Multiple user support
- Output processing abilities such as sort, grep, awk, sed, etc.
- History
- Command completion
- etc.
Requirements- A UNIX-like environment (tested on Solaris, Linux, Mac OSX (not tested in Cygwin))
- Java 1.5
- Perl (tested on v5.8.4 and greater)
BundlesThere are three bundles that make up the Command system: | Bundle | Description | | cim.sf.core.command | Provides API and core CommandShell service implementation | | cim.sf.core.command.sh | Provides Commands via the UNIX shell. | | cim.sf.core.command.equinox | Bridges legacy Equinox CommandProvider objects to the new system. |
Writing a CommandCommands are registered with the CommandShell via the whiteboard pattern. To add a command, register an OSGi service that implements the cim.sf.core.command.Command interface. Standard methodTake the following example of a command that prints out the given input arguments: public class EchoCommand implements Command {
/**
* Return the path of the command. Each command should have a unique path,
* fully qualified from the root of the CommandShell.
*/
public String getPath() {
return "/test/echo";
}
/**
* Return usage scenarios for the command. In this case we only have one. We
* can optionally return <code>null</code> if there are no usage scenarios.
*/
public String[] getUsage() {
return new String[] { "echo <params>" };
}
/**
* Return a description for this command. We can optionally return <code>null</code>
* here.
*/
public String getDescription() {
return "Echo the passed in parameters to stdout.";
}
/**
* Execute the command. The CommandInvocation object provides us with the necessary
* structures to handle command input/output. Additionally, the command returns an
* integer value depending on the outcome of execution.
*/
public int execute(CommandInvocation ci) {
if (ci.args.length == 0) {
throw new InvalidUsageException("Must provide arguments");
}
for (int i = 1; i <= ci.args.length; i++) {
ci.out.println(i + ". " + ci.args[i - 1]);
}
return 0;
}
}We can register our command in our bundle's start method: public void start(BundleContext context) {
context.registerService("cim.sf.core.command.Command", new EchoCommand(), null);
}This is simple enough, however it gets tedious to create a class and register a service when the number of commands increases. Annotation MethodFor convenience purposes, there is another way to register commands. Using a combination of the AnnotatedCommands marker interface and the @Command annotation, we can register any number of Commands from the same class: import cim.sf.core.command.annotations.Command;
public class MyCommands implements AnnotatedCommands {
@Command(path="/test/echo", description="Echo the passed in parameters to stdout.",
usage={"echo <params>"})
public void echo(CommandInvocation ci) {
if (ci.args.length == 0) {
throw new InvalidUsageException("Must provide arguments");
}
for (int i = 1; i <= ci.args.length; i++) {
ci.out.println(i + ". " + ci.args[i - 1]);
}
}
@Command(path="/test/exit-status", description="Return the exit status code specified in parameter 1.",
usage={"exit-status <code>"})
public int exitStatus(CommandInvocation ci) {
if (ci.args.length != 1) {
throw new InvalidUsageException();
}
try {
int status = Integer.parseInt(ci.args[0]);
ci.out.println("Returning status code: " + status);
return status;
} catch (NumberFormatException e) {
throw new InvalidUsageException("Invalid integer value: " + ci.args[0]);
}
}
}And the bundle's start method: public void start(BundleContext context) {
context.registerService("cim.sf.core.command.AnnotatedCommands", new MyCommands(), null);
}This example registers the same echo command as the first example. It also registers a new exit-status command. The @Command annotation can be used on a method with one of two signatures: - void command(CommandInvocation ci);
- int command(CommandInvocation ci);
In the former case, 0 is always returned if the method returns successfully. In the latter case, the value returned by the method is returned by the Command's execute method. Running CommandsConfigurationThe cim.sf.core.command.sh bundle is responsible for making commands available to the user. This bundle can be configured with the following system properties: | Property | Description | | cim.sf.core.command.sh.basedir | Defines the directory on the system under which commands will be placed. For example, if I specify "/usr/local/myapp/cmd" for this property, my echo command will exist on the system at "/usr/local/myapp/cmd/test/echo". It is important that this be set to a directory which contains no other files, since the directory will be cleaned when the bundle is first started. The default value is a directory called "cmd" under the JVM's working directory. | | cim.sf.core.command.sh.port | Defines the port on which the command server listens. The command server only listens to the loopback interface. This property defaults to 29233. | | cim.sf.core.command.sh.groupallowed | Defines whether or not users which belong to the JVM's group are able to access the commands. Defaults to true. | | cim.sf.core.command.sh.othersallowed | Defines whether or not all other users (not the JVM user or a user in the JVM's group) are able to access the commands. Defaults to false. |
ExecutionNavigating your commands and executing them is as simple as using standard shell scripts. The cim.sf.core.command bundle registers a number of test commands under the /.test/ path (a hidden directory on UNIX machines): MacOSX:.test jvolkman$ ls
echo pp pwd return sleep This directory contains 5 commands. Each command responds to a --help parameter and gives the description and usage information provided in the command implementation: MacOSX:.test jvolkman$ ./pp --help
USAGE
pp <param> [param [param...]]
DESCRIPTION
Enumerate and print given parameters
BUNDLE
cim.sf.core.command Running the command described above with some test input yields the following MacOSX:.test jvolkman$ ./pp p1 p2 "quoted string"
1. p1
2. p2
3. quoted string As noted before, commands can optionally return a value. The return command will return the integer value passed as the first parameter: MacOSX:.test jvolkman$ ./return 123; echo "Command returned: $?"
Returning status code: 123
Command returned: 123 Lastly, a command can read input from STDIN. When - is passed as parameter to the command, the command's CommandInvocation.in stream will be opened and the data sent to the client's STDIN will be available: MacOSX:.test jvolkman$ ./echo - < /tmp/hello.txt
ECHOING STDIN
Hello, World!
Goodbye, World.
FINISHED ECHOING STDIN: 30 bytes
|