A request for comments (RFC 147) by Peter Kriens, an attachment to the OSGi 4.2 specifications document early draft, describes a proposed interface for the processing and launching of commands for the OSGi framework. It defines the blueprint for a shell service and its language.
The goal behind such an endeavor is to attempt to standardize the way humans and external systems interact with an OSGi framework using a text command-based interface. For example, such an interface would be used for launching, configuring, and controlling the framework using a local or remote console or scripting without locking an enterprise platform to a specific OSGi framework implementation.
Felix Gogo, a sub-project of Apache Felix, is an implementation of this early draft specification. The Gogo shell is included with the Felix Framework Distribution since version 3.0.
It is worth noting that this specification is not yet part of the official OSGi specifications, and therefore, may change in the future.
In this chapter, you will:
So let's start with a quick overview of the language.
The command syntax for the shell interface is based on the Tiny Shell Language (TSL). It is simple enough to allow a lightweight implementation, yet provides features such as pipes, closures, variable setting and referencing, collection types such as lists and maps, and so on.
The TSL syntax allows the creation of scripts that can be executed by the shell runtime service. The introduction you will get here does not cover the complete syntax; instead, you will see the basic parts of it.
For a review of the proposal in its initial state, please refer to the OSGi 4.2 early draft appendix (http://www.osgi.org/download/osgi-4.2-early-draft.pdf). You may also refer to the RFC 147 Overview on the Felix documentation pages (http://felix.apache.org/site/rfc-147-overview.html) for potential differences with the initial draft.
A program is a set of chained execution blocks. Blocks are executed in parallel, and the output of a block is streamed as input to the next. Blocks are separated by the pipe character ( |
). Each block is made up of a sequence of statements, separated by a semicolon (; ).
For example, as we'll see in the next section, the bundles
command lists the currently installed bundles and the grep
command takes a parameter that it uses to filter the input. The program below:
bundles | grep gogo
is made of two statement blocks, namely, bundles
and grep gogo
. The output of the bundles
statement will be connected to the input of the grep gogo
statement (here each the statement block contains one statement).
Running this program on your Felix installation, in the state it is now, will produce:
g! bundles | grep gogo
2|Active | 1|org.apache.felix.gogo.command (0.6.0)
3|Active | 1|org.apache.felix.gogo.runtime (0.6.0)
4|Active | 1|org.apache.felix.gogo.shell (0.6.0)
true
The grep
statement has filtered the output of the bundles
statement for lines containing the filter string gogo
. In this case, the grep
statement outputs the results of its execution to the shell which prints it.
Executing the statement grep gogo
on its own, without a piped block that feeds it input, will connect its input to the user command line. In that case, use Ctrl-Z to terminate your input:
g! grep gogo
line 1
line 2 gogo
line 2 gogo
line 3
^Z
true
Notice that line 2 gogo
is repeated right after you have entered it, showing that the grep
statement is running in parallel. It receives the input and processes it right after you enter it.
A session variable is assigned a value using the equal character ( =
) and referenced using its name preceded with a dollar character ( $
). For example:
g! var1 = 'this is a string'
this is a string
g! echo $var1
this is a string
The assignment operation returns the assigned value.
We've seen the string type previously, which is indicated by surrounding text with single quotes ('
).
A list is a sequence of terms separated by whitespace characters and is delimited by an opening and a closing square bracket.
For example:
g! days = [ mon tue wed thu fri sat sun ]
mon
tue
wed
thu
fri
sat
sun
Here the variable, days, was created, assigned the list as a value, and stored in the session.
A map is a list of assignments, the value is assigned to the key using the equal character ( =
).
For example:
g! sounds = [ dog=bark cat=meow lion=roar ]
dog bark
cat meow
Here, the variable sounds
is assigned a map with the preceding key value pairs.
The shell uses a mapping process that involves reflection to find the best operation to perform for a request. We're not going to go into the details of how this happens; instead, we'll give a few examples of the operations that can be performed. We'll see a few others as we go along.
In the same session, days
and sounds
are defined previously to retrieve an entry in the $days
list:
g! $days get 1
tue
To retrieve an entry in the sounds
map:
! $sounds get dog
bark
An example we've seen earlier is the bundles
command used when illustrating the piping. Bundles was mapped to the method getBundles()
from the Gogo Runtime bundle BundleContext instance. Another property of this object that we'll use in the next section is bundle <id>
to get a bundle object instance using getBundle(long)
.
Similar to the UNIX back-quote syntax, but providing one that's simpler for a lightweight parser, the execution quotes are used to return the output of an executed program.
For example:
g!(bundle 1) location
file:/C:/felix/bundle/org.apache.felix.bundlerepository-1.6.2.jar,
Here, (bundle 1)
has returned the bundle with ID 1, which we've re-used to retrieve the property location
making use of Gogo's reflexion on beans (location is mapped to getLocation()
on the Bundle
object).
The Gogo Runtime command processor is extensible and allows any bundle to register the commands it needs to expose to the user. Then, when the user types a command, the processor will attempt to find the method that's best fit to be executed, based on the command name and passed arguments.
However, there are potential cases where two bundles would need to register the same command name. To avoid this clash, commands are registered with an optional scope. When there is no ambiguity as to which scope the command belongs to, the command can be used without a scope; otherwise, the scope must be included.
The scope of a command is specified by pre-pending it to the command, separated from the command with a colon ( :
). In the previous examples, we've used the grep
command, which is in the gogo
scope. In this case, grep
and gogo:grep
achieve the same result.
We will look closer at the command registration mechanism in Chapter 8, Adding a Command-Line Interface, when we define our own for the Bookshelf case study.
Let's take a tour of some of the commands available in the Felix distribution.