This website and its associated repositories, are deprecated and no longer supported by the OSGi Alliance. Please visit https://enroute.osgi.org for the latest supported version of OSGi enRoute.
This enRoute v2 archive site is kept for those who do not intend to use the latest version of OSGi enRoute. If you are new to OSGi enRoute, then please start with the latest OSGi enRoute.
The Gogo shell is a bash like shell that closely interacts with the OSGi framework. Gogo was designed because there is a need for a service that allows human users as well as well as programs to interact with on OSGi based system with a line based interface: a shell. This shell should allow interactive and string based programmatic access to the core features of the framework as well as provide access to functionality that resides in bundles.
Shells can be used from many different sources it is therefore necessary to have a flexible scheme that allows bundles to provide shells based on telnet, the Java 6 Console class, plain Java console, serial ports, files, etc. Supporting commands from bundles should be made very lightweight and simple as to promote supporting the shell in any bundle.
The Gogo shell feels very bash like but has a number of differences. Primary, the shell uses plain data objects and their public methods as command names. Instead of coercing everything to strings, the shell actually manipulates objects. It has all the necessary features to program in it like variables and closures.
Let’s explore the shell to show what it can do out of the box. The examples in this OSGi enRoute application note are executed in the OSGi enRoute osgi.enroute.gogo.commands.provider project on Github. If you check out the workspace then you can go to the osgi.enroute.gogo.commands.provider project, double click the bnd.bnd file. You should then resolve it and and then debug it. This should provide you with the following output in the console:
g!
The most simple command is echo
which works as expected.
g! echo Hello World
Hello World
In the Eclipse console you can unfortunately not edit the commands. However, you can access the history using the bang (‘!’).
g! !ech
Hello World
g! !1
Hello World
In standard terminals, you can use the cursor keys to move back and forth.
Quoting (double or single) is optional if the word does not contain spaces or some special characters like ‘ | ’, ‘;’, and some others. So in this case 2 tokens are passed to echo. Notice that we can quote the two words turning it into a single token: |
g! echo Hello World
Hello World
g! echo 'Hello World'
Hello World
You can execute multiple commands on a line by separating the commands with a semicolon (';'
).
g! echo Hello; echo World
Hello
World
Multiple commands can also be separated by a pipe character. In that case the output is the input of the next command. Gogo has a built-in grep
command so we can use echo
to create output and grep
to check the output.
g! echo Hello | grep Hello
Hello
true
g! echo Hello | grep World
Hello
false
Two built-in commands cat
and tac
( tac = reversed cat because it stores) are available to provide file data and store file data. Together with the pipe operator they replace the input and output redirection of the bash shell.
g! echo Hello | tac temp.txt
Hello
g! echo World | tac -a temp.txt
World
g! cat temp.txt
Hello
World
Notice that You can find out about the options of a command by using -?
(This is not implemented on commands without options and it is not always consistently implemented:
g! tac -?
Usage: tac \[-al\] \[FILE\]
-a --append append to FILE
-l --list return List<String>
-? --help show help
Gogo’s commands are methods on objects. By default Gogo adds all public methods on the java.lang.System
class and the public methods on the session’s BundleContext
as command. This gives us access to some interesting System functions:
G! currenttimemillis
1458158111374
G! property user.dir
property user.dir
/Ws/enroute/osgi.enroute.examples/osgi.enroute.gogo.commands.provider
G! nanotime
1373044343558515
G! identityhashcode abc
828301628
G! property foo FOO
G! property foo
FOO
G! env JAVA_HOME
/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home
G! gc
G!
More interesting is the fact that the current Bundle Context is also available. This interface has a method getBundles
so we can now just get the bundles with the bundles
command.
g! bundles
0|Active | 0|org.eclipse.osgi (3.10.100.v20150529-1857)
1|Active | 1|org.apache.felix.configadmin (1.8.6)
2|Active | 1|org.apache.felix.gogo.runtime (0.16.2)
3|Active | 1|org.apache.felix.log (1.0.1)
4|Active | 1|org.apache.felix.scr (2.0.0)
5|Active | 1|org.eclipse.equinox.metatype (1.4.100.v20150408-1437)
6|Active | 1|org.osgi.service.metatype (1.3.0.201505202024)
7|Active | 1|osgi.enroute.gogo.commands.provider (1.0.0.201603161954)
8|Active | 1|org.apache.felix.gogo.shell (1.0.0.201603041814)
We could now also get a specific bundle:
g! bundle 4
Location reference:file:/...
State 32
Bundle 4|Active | 1|org.apache.felix.scr (2.0.0)
Module osgi.identity; ...
RegisteredServices [ConfigurationListener, ServiceComponentRuntime, ScrGogoCommand, ManagedService]
ServicesInUse [LogService]
BundleContext org.eclipse.osgi.internal.framework.BundleContextImpl@3943a2be
SymbolicName org.apache.felix.scr
BundleId 4
LastModified 1458156749977
Headers ...
Version 2.0.0
Notice that neither command required anything special, they are just the methods defined on Bundle Context. The implementation has no clue about Gogo. All these commands return domain plain unadorned objects. We can test this because Gogo has variables that store these plain objects. We can then use those objects in the shell as commands.
G! bundle = bundle 4
...
G! $bundle tostring
org.apache.felix.scr_2.0.0 [4]
G! $bundle bundleid
4
G! $bundle headers
Bundle-License http://www.apache.org/licenses/LICENSE-2.0.txt
Manifest-Version 1.0
Created-By Apache Maven Bundle Plugin
Bnd-LastModified 1438861013315
Bundle-Name Apache Felix Declarative Services
...
Variables can be have any name. They are set with <name>=<expr>
. They are referred to by $<name>
. Gogo uses variables also itself. For example, the prompt can be changed by setting a new prompt
variable.
G! prompt= '$ '
$
The following variables are in use by the shell:
e
– A function to print the last exception’s stack traceexception
– The last exceptionexception-cmd
– The command that threw the exceptions.context
– The Bundle Contextprompt
– The shell promptThe syntax feels very natural but there is something a bit tricky going on. The first token in the command can either identify the name of a command or an object. With an object, the next token is the method on that object. This can cause ambiguity. The scope is further discussed later when we add custom commands. Just be aware that the first token is either an object or a command name.
We’ve already use string literals. However, it is also possible to use lists and maps:
g! [1 2 3] size
3
g! [a=1 b=2 c=3] get b
2
So how do we access a specific header. A command like $bundle headers get Bundle-Version
cannot work because Gogo will see this as one command and will complain with: Cannot coerce headers(String, String) to any of [(), (String)]
. The parentheses come to the rescue:
G! $bundle headers get Bundle-Version
Cannot coerce headers(String, String) to any of [(), (String)]
G! ($bundle headers) get Bundle-Version
2.0.0
The parentheses first calculate the expression in their inner bowels which then becomes available as the target object for the remaining command. I.e. ($bundle headers)
returns a Dictionary object, which subsequently becomes the target object. The get
token is the method called on this target object, with the Bundle-Version
as parameter.
The bash shell has this wonderful capability of executing commands to get an argument by placing back ticks around a command. We can use the parentheses for the same effect, with the added benefit that the parentheses work recursively.
G! echo Bundle ($bundle bundleid) has name ((bundle ($bundle bundleid)) symbolicname)
Bundle 4 has name org.apache.felix.scr
The Gogo shell can store commands for later execution. The {
and }
delimiters are used for that purpose. We can store these functions in objects or pass them as parameters. To execute a function as a command, you should use the name of the variable without the dollar (‘$’) sign.
G! f = { echo Hello }
echo Hello
G! f
Hello
You can pass arguments to the function. They are named $1..$9. $0 is the command name if available. The $it macro refers to $1.
g! f = { echo $it }
echo $1
g! f Hello World
Hello
Obviously it is not very nice that we miss the World
because we only used $1. There is a magic variable called $args
. This variable is list that gets expanded into separate arguments. So we can change our function to use all the arguments when the function is invoked:
g! f = { echo $args }
echo $args
g! f Hello World
Hello World
The $args
list of arguments cannot be manipulated as a normal object, it gets expanded into its members wherever you use it.
Gogo provides a number of built in commands that use the functions to provide conditional and repeated execution. For example, the each
command takes a collection and a function. It then iterates over the collection and calls the function with the element of the iteration.
G! each [1 2 3] { echo -- $it -- }
-- 1 --
-- 2 --
-- 3 --
null
null
null
We can now also use the if
command:
g! l = []
g! if {$l isempty} { echo empty } { echo not empty }
empty
g! $l add foo
g! if {$l isempty} { echo empty } { echo not empty }
not empty
You can negate with the not
command, which takes a function:
g! if { not {$l isempty}} { echo not empty } { echo empty }
not empty
TODO while, until
You can add any object as a command. If you add an instance then the methods of that instance will be available as commands. However, make sure that you can only add one instance of a given type. The commands would overshadow eachother. For example, we could add a list as a command. Since this is an Array List, all its methods become available.
g! addcommand silly-scope [1 2 3 4]
g! size
4
g! isempty
false
g! get 3
4
g!
Clearly this is not that useful. However, it is also possible to add all static methods of a class. This is done automatically for you at startup with the System class. If you ask for the properties then this is actually calling the static method getProperties()
. For example, we can add all the Math functions to be in the math scope.
g! addcommand math ((bundle 0) loadclass java.lang.Math)
g! sin 2
0.9092974268256817
g!
The ((bundle 0) loadclass java.lang.Math)
is caused by a bug in Gogo. It overloads the addCommand()
methods in such a way that Gogo cannot coerce the string java.lang.Math
to a class. Ah well.
The g!
prompt is not hard coded. You can override the prompt by setting the variable prompt
(Surprise!). The value can be a simple string:
g! prompt = "$ "
$ sin 2
0.9092974268256817
$
However, Gogo would not be Gogo if we could not do this a little more exciting. Lets be original and add the name of the local host. For that, we need to add the InetAddress static methods to our command set.
g! addcommand inet ((bundle 0) loadclass java.net.InetAddress)
g! prompt = { echo ((localhost) hostname) "$ " }
zeno.local $
Gogo was designed to format an object to a string in two places.
.Format.Pipe
.Gogo.format
You (and any code you call) can throw exceptions. The last exception is stored in the $exception
variable and there is a built in function e
that shows the stack trace.
G! throw Foo
Foo
G! $exception message
Foo
G! e
java.lang.IllegalArgumentException: Foo
at org.apache.felix.gogo.shell.Procedural._throw(Procedural.java:83)
...
You can also catch the exceptions with a try
command.
G! exception = null
G! try { throw Foo }
G! $exception
G!
Of course we now silently ignore the exception, not a good idea. So we can provide a catch function that receives the exception as the $it variable.
G! try { throw Foo } { echo ouch }
ouch
G!
Bundles that can add commands to the shell by registering any service with the osgi.command.scope
property and osgi.command.function
service properties. The Gogo shell then call any method name listed in the osgi.command.function
property. To disambiguate, the osgi.command.scope
can be used by prefixing the command with the scope and a ‘:’. The constants for these properties can be found in osgi.enroute.debug.api.Debug
.
For example, assume the following component is registered:
@Component(
property = {
Debug.COMMAND_SCOPE + "=hello", //
Debug.COMMAND_FUNCTION + "=world" //
}
)
public class FooImpl implements Foo {
public void sysout() {
System.out.println("Hello World");
}
}
A primary goal of the design was to make it possible to add commands to existing service implementation objects.
In the shell, we can now call the function:
G! sysout
Hello World
G! hello:sysout
Hello World
In the example we use System.out
. For some surprisingly, this is ok, even if the shell is accessed via SSH or telnet. The Gogo shell redirects System.out
for the duration of a command. However, the command is currently not usable for other code. It would be nicer if we could return the text:
public String value() {
return "Hello World";
}
No running it in the shell makes it look the same:
G! value
Hello World
We can also provide an argument. Gogo attempts to use a syntax that one expects in a shell but then translate this to method calls. So if we have the command:
public String parameter(String parameter) {
return "Hello " + parameter;
}
So let’s try this out:
G! parameter OSGi
Hello OSGi
It is also quite easy to make the parameter an option:
public String option(
@Parameter(
absentValue="World",
names={"-p","--parameter"})
String parameter
) {
return "Hello " + parameter;
}
Since we have an absent value, we do not need the value to be specified. So we can call the command with and without a parameter.
G! option
Hello World
G! option -p OSGi
Hello OSGi
G! option --parameter OSGi
Hello OSGi
So far we have had a 1:1 relation between the command name and the method. However, it was a primary design goal of Gogo to make it feel like a shell but interact seamlessly with standard Java code. In Java we often use design patterns like prefixing the name of a property with get
. Gogo will therefore try to match a command to a method removing the prefixes and case sensitivity. So if we add a method getFoo
then we can stil call it:
public String getFoo() {
return "Foo";
}
We can now call this command in many different ways.
G! foo
Foo
G! FOO
Foo
G! Foo
Foo