Although not defined as an OSGi specification, all (non-embedded) OSGi frameworks have had a console to provide a means to interact with the framework. Some, such as Equinox, had a console built into the core JAR; others, such as Felix, provided console services through separate bundles.
In this chapter, we'll look at the Gogo shell, which is used by Felix and Equinox, and learn how to write commands in Gogo script as well as Java.
Until the end of Eclipse 3.7, Equinox supported a built-in console that was available by running the org.eclipse.osgi
JAR file with a -console
argument:
$ java -jar org.eclipse.osgi_3.7*.jar -console Framework is launched. osgi>
With the release of Eclipse 4.2 (Juno) and onwards, this console is no longer available by default:
$ java -jar org.eclipse.osgi_3.8*.jar -console
This is because the implementation provider for the console defers to the Gogo shell, which was developed by the Apache Felix project.
With Equinox 3.8 (Eclipse Juno 4.2 and above), it is necessary to install additional bundles at start-up to provide a console. This is covered in the Running Equinox from the command line section later in this chapter.
The easiest way to experiment with the console is to use Eclipse's Console View. This is typically used for seeing the output of running Java programs, but in fact the console view can show many other types of consoles as well. In the top-right corner of the view, there is a dropdown that can show alternative consoles.
Choosing the
Host OSGi Console action creates a new Gogo shell. It warns that the console is connected to the running Eclipse instance; typing exit
will call System.exit
and terminate Eclipse and the JVM:
The console has a built-in help system that can be used to find out what commands are available and what their individual functions are. The help
command will provide a list of all the available commands, and running help command
will give more information:
osgi> help getprop getprop - displays the system properties with the given name, or all of them scope: equinox parameters: String[] name of system property to display osgi> getprop os.name os.name=Mac OS X
Each command has a scope and a name. Optionally, it may require a number of parameters. Many commands take no arguments, but the help text should say what is required. Some commands have limited help, but they will display additional information when run with no arguments.
Commands may be prefixed with their scope to avoid ambiguity. These two commands are therefore equivalent:
osgi> echo Hello World Hello World osgi> gogo:echo Hello World Hello World
Disambiguation is necessary for some command names that are defined in more than one scope. For example, the ls
command is provided by both the equinox
scope (to list the Declarative Services components) and by the felix
scope (to list the contents of the current directory):
osgi> felix:ls /Applications/Eclipse.app/Contents/MacOS/eclipse /Applications/Eclipse.app/Contents/MacOS/eclipse.ini osgi> equinox:ls All Components: ID State Component Name 1 Registered org.eclipse.e4.core.services.preferences 2 Registered org.eclipse.e4.core.services.events ...
By default, the shell prints out a value after each statement. To disable this, run .Gogo.format=false
(with the correct capitalization):
osgi> 'Hello World' Hello World osgi> .Gogo.format=false osgi> 'Hello World' osgi> .Gogo.format=true true osgi> 'Hello World' Hello World
With formatting disabled, the echo
or format
commands can be used to display results. Along with printing the output, format
will also return the value. This may result in two values being displayed if autoformatting is turned on:
osgi> echo 'hello' hello osgi> format 'hello' hello hello osgi>
The shell has a way to set and get variables, which can be useful when interacting with bundle identifiers or names. Variables can be assigned with the equals sign (=
) and can be evaluated with a dollar symbol ($
), similar to Unix shell scripts. Identifiers start with an extended alphabet followed by alphanumeric characters (including underscores):
osgi> name = Alex Alex osgi> echo Hello $name Hello Alex osgi> id = 0 0 osgi> headers $id Bundle headers: Built-By = e4Build Bundle-Description = OSGi System Bundle Bundle-SymbolicName = org.eclipse.osgi; singleton:=true …
The special variable $_
is used to store the result of the last command. Other variables are also predefined; exception
is used to store the result of the last exception and e
is a function that will print out the last exception's stack trace. The set
command will print out all the currently defined variables:
osgi> 'hello' hello osgi> echo $_ hello osgi> set null 0 null String SCOPE equinox:* null _ null Closure e $exception printStackTrace HeapCharBuffer prompt osgi> osgi> misteak gogo: CommandNotFoundException: Command not found: misteak osgi> $exception Command misteak Cause null Message Command not found: misteak osgi> e org.apache.felix.gogo.runtime.CommandNotFoundException: Command not found: misteak at org.apache.felix.gogo.runtime.Closure.executeCmd at org.apache.felix.gogo.runtime.Closure.executeStatement
Along with being able to assign variables from literal values on the command line, it is also possible to capture the output of a command and assign that to a variable. While the command cat
copies output from a source to the console, tac
works in the opposite direction:
osgi> contents = (felix:ls | tac) /Applications/Eclipse.app/Contents/MacOS/eclipse /Applications/Eclipse.app/Contents/MacOS/eclipse.ini osgi> echo $contents /Applications/Eclipse.app/Contents/MacOS/eclipse /Applications/Eclipse.app/Contents/MacOS/eclipse.ini
It is possible to pipe the content through other commands; the most useful one is grep
, which can be used to search for specific patterns:
osgi> lb -s | grep osgi 0|Active |0|org.eclipse.osgi (3.9.1.v20140110-1610) 185|Resolved|4|org.eclipse.osgi.services (3.3.100.v20130513-1956) 186|Resolved|4|org.eclipse.osgi.util (3.2.300.v20130513-1956) 1103|Resolved|4|osgi.enterprise (4.2.0.v201108120515)
The grep
command doesn't support a full set of POSIX arguments, but it does provide some options. The built-in help documentation does not show it, but if run without arguments, it provides more useful output:
osgi> help grep grep scope: gogo parameters: CommandSession String[] osgi> grep grep: no pattern supplied. Usage: grep [OPTIONS] PATTERN [FILES] -? --help show help -i --ignore-case ignore case distinctions -n --line-number prefix each line with line number -q --quiet, --silent suppress all normal output -v --invert-match select non-matching lines gogo: IllegalArgumentException: grep: no pattern supplied.
Besides providing an interactive Read Evaluate Print Loop (REPL), the console also permits the creation of functions and scripts. This allows common functions to be defined in a persistent file and then be reused between sessions.
A function is defined in curly braces, and it can use the special variables $args
or $argv
to refer to arguments, just like the Unix shell's $*
or Windows' %*
. It is possible to refer to the first nine arguments with $1
to $9
, or $it
as an alias for the first argument; $it
is commonly used in each
with an anonymous function, covered in the Processing a list with each section later in this chapter.
osgi> pwd gogo: CommandNotFoundException: Command not found: pwd osgi> pwd = {getprop user.dir} getprop user.dir osgi> pwd user.dir=/Applications/Eclipse.app/Contents/MacOS osgi> greeting = {echo Hello $args} echo Hello $args osgi> greeting World Hello World
Functions can be saved in an external file and then loaded into a Gogo shell session. Create a file called fns
in the temp directory (/tmp
on Unix/OS X and c:TEMP
on Windows) as follows:
# Lines beginning with # are comments # Blank lines are also permitted # v curly braces v pwd = {getprop user.dir} greeting = {echo Hello $args}
From the Gogo shell, run the following:
osgi> source /tmp/fns # source c:TEMPfns on Windows Loaded file successfully osgi> pwd user.dir=/Applications/Eclipse.app/Contents/MacOS osgi> greeting Alex Hello Alex
Strings that are passed in are interpreted as string literals. Strings surrounded with double quotes allow replacement of variables with $
, whereas those with single quotes do not perform replacement:
osgi> name=Alex Alex osgi> 'Hello $name' Hello $name osgi> "Hello $name" Hello Alex
Numbers are available as both floating point and integers. By default, they are represented as Double
and Long
instances respectively. They can be used to pass into methods that expect smaller types (such as float
and int
) and are cast down automatically. Suffix flags of f
and d
are used to denote floating point values, but both are converted to Double
:
osgi> lightspeed = 299792458 299792458 osgi> ncc = 1701d 1701.0
It is possible for lists and maps to be entered in literal form in the console. They are separated by spaces rather than commas, and the syntax for maps is almost the same, with the addition of keys:
osgi> numbers = [ 1 2 3 ] 1 2 3 osgi> words = [ one=1 two=2 three=3 ] one 1 two 2 three 3 osgi> echo $one null
There are explicit literals for boolean values true
and false
.
The console allows objects to be instantiated with the new
command. The fully qualified name of the class is passed in along with any arguments:
osgi> new java.util.ArrayList osgi> random = new java.util.Random java.util.Random@768c5708
The values displayed on the console are actually Java objects, so true
is a literal that maps to Boolean.TRUE
and false
maps to Boolean.FALSE
. Similarly, integral values are represented under the covers as Long
instances and strings are all instances of String
.
The Gogo shell can invoke arbitrary methods on instance methods using the dot (.
) operator. It's possible to chain more than one method call by using one dot operator after another:
osgi> "hello" . length 5 osgi> "hello" . getClass . getName java.lang.String
Since the Gogo shell is dynamic and the methods are looked up dynamically, the methods can be specified in a case-insensitive manner:
osgi> "hello" . getclass . getname java.lang.String
Although methods can be called in a case-insensitive manner, variable names are case sensitive. Note that the dot (.
) operator can be left out for the first method call, as everything else will be interpreted as arguments:
osgi> $numbers . get 0 1 osgi> $words get one 1
Parentheses can be used to evaluate nested expressions, as in other languages:
osgi> ("hello" getClass) getName java.lang.String osgi> (("hello" getClass) getPackage) getName java.lang
The Gogo shell supports basic control flow, including if
and each
:
osgi> if {true} {echo Yes} Yes osgi> if {false} {echo Yes} osgi> if {false} {echo Yes} {echo No} No
Multiple commands can be put inside the braces, separated by semicolons:
osgi> if {true} {echo Yes; echo Still yes} Yes Still yes
There are also other functions such as not
, which can be used to negate the result of a boolean expression:
osgi> if {not {true}} {echo Yes} {echo No} No
Although there aren't built-in functions for logical operators such as and
and or
, it's possible to create functions to do this fairly simply:
osgi> and = { if {$1} {if {$2} {true} {false}} {false}} osgi> or = { if {$1} {true} {if {$2} {true} {false}}} osgi> or true false true osgi> and true false false
Finally, the each
command allows iteration over an array of elements:
osgi> directions = [ "Up" "Down" ] osgi> each $directions { echo $it } Up Down osgi> each $directions { echo "->$it<-" } ->Up<- ->Down<-
The each
command actually provides a map function (which takes an array of values), invokes a function on each element, and returns an array of results. This allows operations to be nested:
osgi> (each [ "" 1 true ] { ($it getClass) getName }) get 0 java.lang.String
To launch Equinox as a standalone OSGi application with a Gogo shell, the minimal dependencies are as follows:
org.apache.felix.gogo.shell
(provides the I/O processing and parser)org.apache.felix.gogo.runtime
(provides the language runtime)org.eclipse.osgi
(the Equinox kernel)To run Equinox as a launch in Eclipse, go to the Run menu and then choose Run Configurations... after which a dialog will appear. Choose OSGi Framework and set it up with the mentioned bundles (a quick way is to add the org.apache.felix.gogo.shell
bundle, then deselect the Include optional dependencies option and click on Add Required Bundles). The resulting launch configuration now looks like the following screenshot:
Click on Run and a console will be launched.
An exception may be thrown at start-up if the org.eclipse.equinox.console
bundle is not found:
org.osgi.framework.BundleException: Could not find: org.eclipse.equinox.console at org.eclipse.osgi.framework.internal.core.ConsoleManager .checkForConsoleBundle(ConsoleManager.java:211) at org.eclipse.core.runtime.adaptor.EclipseStarter .startup(EclipseStarter.java:298)
To resolve the problem, add the org.eclipse.equinox.console
bundle to the runtime.
To run from a command line instead of an Eclipse launch configuration, the bundles need to be specified as either relative files or URLs. Equinox supports the osgi.bundles
system property, which provides a comma-separated list of the JARs that the framework should attempt to bring up at boot. Note that @start
is required to bring the console up when the org.eclipse.equinox.console
bundle isn't present:
$ java -Dosgi.bundles= org.apache.felix.gogo.runtime_0.10.0.v201209301036.jar@start, org.apache.felix.gogo.shell_0.10.0.v201212101605.jar@start -jar org.eclipse.osgi_3.9.1.v20140110-1610.jar -console osgi> bundles 0|Active|0|org.eclipse.osgi (3.9.1.v20140110-1610) 1|Active|4|org.apache.felix.gogo.shell (0.10.0.v201212101605) 2|Active|4|org.apache.felix.gogo.runtime (0.10.0.v201209301036)
Relative paths may be used by starting with ./
or file:./
and absolute paths may be used with /
or file:///
.
The -jar
argument runs the org.eclipse.osgi
JAR using Main-Class
from the manifest (which is org.eclipse.core.runtime.adaptor.EclipseStarter
in Equinox).
Finally, the -console
argument is passed to the running Eclipse instance inside String[] args
, which indicates that Equinox should start up the console.
The Equinox commands (those in scope equinox:
) are provided by the org.eclipse.osgi.console
bundle. Adding this removes the exception highlighted previously and supplies some of the commands such as ss
(short status) and b
(bundle).
The Equinox runtime can be configured in a couple of different ways. One way is to specify system properties on the command line with the -Dosgi.*
parameters. (Despite being prefixed with osgi
, they aren't standardized by an OSGi specification; they're all specific to Equinox.)
To prevent large command-line arguments, properties may instead be specified in a file called config.ini
, which is stored in the configuration area of Eclipse. The configuration area is a directory that stores Equinox runtime information, and it is typically referred to as the configuration
directory, since that is the default value. Running Equinox with -config
can specify a different directory to be used.
One advantage of the config.ini
file is that it can be updated by installers. P2 has a means to amend the contents of this file, which is used when updating between releases of Eclipse and in which the filenames (which have embedded version numbers) can be modified. Equinox reads the config.ini
file and sets lines as system properties for the application.
When a stock Eclipse application runs, either the Equinox framework (if launched via the -jar
option) or the eclipse.exe
executable boots the JVM with the framework on the classpath, and then Equinox reads the osgi.bundles
property (potentially set from the config.ini
file) to bring it into a started state.
In the case of Eclipse, the org.eclipse.equinox.simpleconfigurator
bundle is started, which reads a file named bundles.info
, containing a list of bundles that need to be installed. Each line represents a single bundle, which is comma-separated and contains the following parts:
For example, the Gogo shell is installed with the following (as a single line):
org.apache.felix.gogo.shell, 0.10.0.v201212101605, plugins/org.apache.felix.gogo.shell_0.10.0.v201212101605.jar, 4, false
When bundles are installed via P2, the bundles.info
file is updated to reflect the new state of the system. Upon restart, the new set of bundles are used. The file is written by the utilities in org.eclipse.equinox.simpleconfigurator.manipulator
, and it is sorted alphabetically by the bundle identifier and then in reverse version order. When changes are made, the entries are updated and the sorting ensures minimal changes to the content.
The Gogo shell has a telnet daemon that can be used to listen for network connections. This can be started interactively from the console with telnetd
, or it can be run from the command line with the -console
argument and an associated port:
$ java -Dosgi.bundles=… -jar org.eclipse.osgi_*.jar -console osgi> telnetd --port=1234 start $ java -Dosgi.bundles=… -jar org.eclipse.osgi_*.jar -console 1234
Note that the console-with-port from the command line requires the org.eclipse.equinox.console
bundle to be installed in addition to the Gogo shell. Alternatively, the system property -Dosgi.console=1234
can be specified at the command line or via the config.ini
file.
Once the daemon is running, the Equinox process can be connected to via telnet
:
$ telnet localhost 1234 Trying ::1... telnet: connect to address ::1: Connection refused Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. osgi> bundles org.eclipse.osgi_3.9.1.v20140110-1610 [0] Id=0, Status=ACTIVE …
While telnet is good for debugging, it is not a secure way of connecting to a networked machine. SSH provides a way of connecting securely to remote machines.
Equinox can start an SSH daemon, but it requires more bundles to be added as well as an appropriate means to verify users and passwords. Unlike the command-line console or the telnet daemon, the SSH service requires that the Equinox console implementation be available. The full set of bundles is as follows:
org.apache.felix.gogo.shell
(provides the I/O processing and parser)org.apache.felix.gogo.runtime
(provides the language runtime)org.eclipse.osgi
(the Equinox kernel)org.eclipse.equinox.console
(the Equinox console service)org.eclipse.equinox.console.jaas.fragment
(adds JAAS support to the SSHD server)org.eclipse.equinox.console.ssh
(the SSHD server support)org.apache.sshd.core
(the SSHD server libraries)org.apache.mina.core
(needed by the SSH server libraries)slf4j-api
(the logging framework used by the libraries)These bundles are available from the Equinox downloads page at http://download.eclipse.org/equinox/ and the Orbit downloads page at http://download.eclipse.org/tools/orbit/downloads/. The GitHub repository, https://github.com/alblue/com.packtpub.e4.advanced, associated with this book has a set of the required bundles along with a demonstration runtime in the com.packtpub.e4.advanced.console.ssh
directory.
To start an SSHD server in Equinox, it is easier to use a config.ini
file instead of passing in many arguments via the command line. (However, either approach will still work, so use whichever is more convenient.)
JAAS is used to provide user ID/password authentication. To do this, a JAAS configuration file needs to be created with an equinox_console
entry. As with other Java programs, this login module is set with the java.security.auth.login
system property.
Create a file called jaas.config
in the configuration
directory with the following content:
equinox_console { org.eclipse.equinox.console.jaas.SecureStorageLoginModule REQUIRED; };
The java.security.auth.login
property can be set in the config.ini
file that lists the bundles required:
osgi.console.ssh=1234 osgi.console.ssh.useDefaultSecureStorage=true org.eclipse.equinox.console.jaas.file=configuration/store ssh.server.keystore=configuration/hostkey.ser java.security.auth.login.config=configuration/jaas.config osgi.bundles= ./org.apache.felix.gogo.runtime_0.10.0.v201209301036.jar@start, ./org.apache.felix.gogo.shell_0.10.0.v201212101605.jar@start, ./org.apache.mina.core_2.0.2.v201108120515.jar, ./org.apache.sshd.core_0.7.0.v201303101611.jar, ./org.eclipse.equinox.console.ssh_1.0.0...jar@start, ./org.eclipse.equinox.console.jaas.fragment_1.0.0...jar, ./org.slf4j.api_1.7.2.v20121108-1250, ./org.eclipse.equinox.console_1.0.100.v20130429-0953.jar
The osgi.console.ssh
port 1234
is used to start up the SSH server. If this configuration line is missed out, the SSH server won't be started.
The osgi.console.ssh.useDefaultSecureStorage
property is required if the SecureStorageLoginModule
is used. It is possible to use alternative LoginModules
here, but this is not covered in this book. See the tutorials on JAAS on the Java home page for more information.
The org.eclipse.equinox.console.jaas.file
property specifies where the SecureStorageLoginModule
writes the user/password values. If not specified, it uses configuration/store
as default.
The secure storage login module uses a fairly simple means to store hashed passwords. It first generates an MD5 hash of the password, concatenates the password with this hash, and then stores the resulting SHA1 hash. So, password
becomes password5f4dcc3b5aa765d61d8327deb882cf99
and then ends up as 0d85584b3529eaac630d1b7ddde2418308d56317
.
The ssh.server.keystore
file contains a serialized Java object (java.security.KeyPair
) of the host's SSH key, which is automatically generated and persisted on first run. It defaults to hostkey.ser
.
Finally, java.security.auth.login.config
is the standard JAAS property that refers to a configuration file that defines the JAAS modules. The final property, osgi.bundles
, lists the bundles that are required and the ones that should be started.
Now a console can be accessed via SSH:
$ ssh -p 1234 equinox@localhost The authenticity of host '[localhost]:1234 ([::1]:1234)' can't be established. DSA key fingerprint is 0c:40:ff:ba:0a:c8:bc:3d:a9:72:9f:05:5f:c6:96:35. Are you sure you want to continue connecting (yes/no)? Yes Warning: Permanently added '[localhost]:1234' (DSA) to the list of known hosts. equinox@localhost's password: Currently the default user is the only one; since it will be deleted after first login, create a new user: username: alex password: Confirm password: roles: osgi> ss "Framework is launched." id State Bundle 0 ACTIVE org.eclipse.osgi_3.9.1.v20140110-1610 1 ACTIVE org.apache.felix.gogo.runtime_0.10.0.v201209301036 2 ACTIVE org.apache.felix.gogo.shell_0.10.0.v201212101605 3 RESOLVED org.apache.mina.core_2.0.2.v201108120515 4 RESOLVED org.apache.sshd.core_0.7.0.v201303101611 Fragments=6 5 ACTIVE org.eclipse.equinox.console.ssh_1.0.0... 6 RESOLVED org.eclipse.equinox.console.jaas.fragment_1.0.0... Master=4 7 RESOLVED org.slf4j.api_1.7.2.v20121108-1250 8 ACTIVE org.eclipse.equinox.console_1.0.100.v20130429-0953
Note that the jaas.fragment
bundle has been wired to the org.apache.sshd.core
bundle, which allows the sshd.core
bundle to connect to the SecureStorageLoginModule
. In fact, an investigation of the jaas.fragment
bundle shows that it is almost empty; the only thing it has is a manifest file with the following content:
DynamicImport-Package: org.eclipse.equinox.console.jaas Fragment-Host: org.apache.sshd.core;bundle-version="0.5.0"
The preceding snippet says that the fragment's host is the sshd.core
bundle, and it should add a DynamicImport-Package
of the org.eclipse.equinox.console.jaas
package. As a result, although the org.apache.sshd.core
bundle doesn't know anything about the Equinox secure storage module, when the fragment is injected, it permits the bundle to be wired up to the Equinox bundle:
osgi> bundle 4 | grep equinox org.eclipse.equinox.console.jaas; version="0.0.0" <org.eclipse.equinox.console.ssh_1.0.0.v20130515-2026 [5]> org.eclipse.equinox.console.jaas.fragment_1.0.0.v20130327-1442 [6]
Fragments are covered in more detail in Chapter 5, Native Code and Fragment Bundles.