Extending

Apache Karaf is a very flexible container that you can extend very easily.

Console

In this section, you will see how to extend the console by adding your own command.

We will leverage Apache Maven to create and build the OSGi bundle. This OSGi bundle will use Blueprint. We don’t cover the details of OSGi bundle and Blueprint, see the specific sections for details.

Create the Maven project

To create the Maven project, we can:

  • use a Maven archetype

  • create by hand

Using archetype

The Maven Quickstart archetype can create an empty Maven project where you can put your project definition.

You can directly use:

mvn archetype:create \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DgroupId=org.apache.karaf.shell.samples \
  -DartifactId=shell-sample-commands \
  -Dversion=1.0-SNAPSHOT

It results to a ready to use project, including a pom.xml.

You can also use Maven archetype in interactive mode. You will have to answer to some questions used to generate the project with the pom.xml:

mvn archetype:generate
Choose a number:  (1/2/3/4/5/6/7/.../32/33/34/35/36) 15: : 15
Define value for groupId: : org.apache.karaf.shell.samples
Define value for artifactId: : shell-sample-commands
Define value for version:  1.0-SNAPSHOT: :
Define value for package: : org.apache.karaf.shell.samples
By hand

Alternatively, you can simply create the directory shell-sample-commands and create the pom.xml file inside it:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>org.apache.karaf.shell.samples</groupId>
  <artifactId>shell-sample-commands<artifactId>
  <packaging>bundle</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>shell-sample-commmands</name>


  <dependencies>
    <dependency>
      <groupId>org.apache.karaf.shell</groupId>
      <artifactId>org.apache.karaf.shell.console</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <version>${felix.plugin.version}</version>
        <configuration>
          <instructions>
            <Import-Package>
              org.apache.felix.service.command,
              org.apache.karaf.shell.commands,
              org.apache.karaf.shell.console,
              *
            </Import-Package>
          </instructions>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>
Configuring for Java 6/7

We are using annotations to define commands, so we need to ensure Maven will actually use JDK 1.6 or 1.7 to compile the jar. Just add the following snippet after the dependencies section.

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <target>1.6</target>
        <source>1.6</source>
      </configuration>
    </plugin>
  </plugins>
</build>
Loading the project in your IDE

We can use Maven to generate the needed files for your IDE:

Inside the project, run the following command

mvn eclipse:eclipse

or

mvn idea:idea

The project files for your IDE should now be created. Just open the IDE and load the project.

Creating a basic command class

We can now create the command class HelloShellCommand.java

package org.apache.karaf.shell.samples;

import Command;
import org.apache.karaf.shell.console.OsgiCommandSupport;

@Command(scope = "test", name = "hello", description="Says hello")
public class HelloShellCommand extends OsgiCommandSupport {

    @Override
    protected Object doExecute() throws Exception {
        System.out.println("Executing Hello command");
        return null;
    }
}
Blueprint definition

Blueprint is an injection framework for OSGi. It allows you to declare beans in a XML file and contains specific statement for OSGi services.

For the command, we use Blueprint to create the command bean and register as an OSGi service.

The blueprint definition file is located in the OSGI-INF/blueprint folder of our bundle.

If you don’t have the src/main/resources directory yet, create it.

mkdir src/main/resources

Then, re-generate the IDE project files and reload it so that this folder is now recognized as a source folder.

Inside this directory, create the OSGI-INF/blueprint/ directory and put the following file inside (the name of this file has no impact at all):

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">

    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
        <command>
            <action class="org.apache.karaf.shell.samples.HelloShellCommand"/>
        </command>
    </command-bundle>

</blueprint>
Compile

Let’s try to build the jar. Remove the test classes and sample classes if you used the artifact, then from the command line, run:

mvn install

The end of the maven output should look like:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Test

Launch Apache Karaf and install your bundle:

karaf@root()> bundle:install -s mvn:org.apache.karaf.shell.samples/shell-sample-commands/1.0-SNAPSHOT

Let’s try running the command:

karaf@root()> test:hello
Executing Hello command
Command completer

A completer allows you to automatically complete a command argument using <tab>. A completer is simply a bean which is injected to a command.

Of course to be able to complete it, the command should require an argument.

Command argument

We add an argument to the HelloCommand:

package org.apache.karaf.shell.samples;

import Command;
import Argument;
import org.apache.karaf.shell.console.OsgiCommandSupport;

@Command(scope = "test", name = "hello", description="Says hello")
public class HelloShellCommand extends OsgiCommandSupport {

    @Argument(index = 0, name = "name", description = "The name that sends the greet.", required = true, multiValued = false)
    String name = null;

    @Override
    protected Object doExecute() throws Exception {
        System.out.println("Hello " + name);
        return null;
    }
}

The Blueprint configuration file is the same as previously.

Completer bean

A completer is a bean which implements the Completer interface:

package org.apache.karaf.shell.samples;

import org.apache.karaf.shell.console.completer.StringsCompleter;
import org.apache.karaf.shell.console.Completer;

/**
 * <p>
 * A very simple completer.
 * </p>
 */
public class SimpleNameCompleter implements Completer {

 /**
  * @param buffer the beginning string typed by the user
  * @param cursor the position of the cursor
  * @param candidates the list of completions proposed to the user
  */
 public int complete(String buffer, int cursor, List candidates) {
   StringsCompleter delegate = new StringsCompleter();
   delegate.getStrings().add("Mike");
   delegate.getStrings().add("Eric");
   delegate.getStrings().add("Jenny");
   return delegate.complete(buffer, cursor, candidates);
 }

}
Blueprint with completer

Using Blueprint, you can "inject" the completer linked to your command. The same completer could be used for several commands and a command can have several completers:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">

    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
        <command>
            <action class="org.apache.karaf.shell.samples.HelloShellCommand"/>
            <completers>
                <ref component-id="simpleNameCompleter"/>
                <null/>
            </completers>
        </command>
    </command-bundle>

    <bean id="simpleNameCompleter" class="org.apache.karaf.shell.samples.SimpleNameCompleter"/>

</blueprint>

You can have multiple completers for a single class, each matching a command argument. The order of the completers must match the order of the arguments, in order to have the desirable results.

Completers for option values

Quite often your commands will not have just arguments, but also options. You can provide completers for option values. The snippet below shows the HelloShellCommand with an option to specify what the greet message will be.

package org.apache.karaf.shell.samples;

import Command;
import Argument;
import org.apache.karaf.shell.console.OsgiCommandSupport;

@Command(scope = "test", name = "hello", description="Says hello")
public class HelloShellCommand extends OsgiCommandSupport {

    @Argument(index = 0, name = "name", description = "The name that sends the greet.", required = true, multiValued = false)
    String name = null;

    @Option(name = "-g", aliases = "--greet", description = "The configuration pid", required = false, multiValued = false)
    String greet = "Hello;

    @Override
    protected Object doExecute() throws Exception {
        System.out.println(greet + " " + name);
        return null;
    }
}

We can now specify a completer for the greet option. All that is required is to add an optional-completer element in the blueprint configuration that will associate a completer with the -g option or its --greet alias.

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">

    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
        <command>
            <action class="org.apache.karaf.shell.samples.HelloShellCommand"/>
            <completers>
                <ref component-id="simpleNameCompleter"/>
                <null/>
            </completers>
            <optional-completers>
                <entry key="-g" value-ref="greetCompleter"/>
            </optional-completers>
        </command>
    </command-bundle>

    <bean id="simpleNameCompleter" class="org.apache.karaf.shell.samples.SimpleNameCompleter"/>
    <bean id="greetCompleter" class="org.apache.karaf.shell.samples.GreetCompleter"/>

</blueprint>
Completers with state

Some times we want to tune the behavior of the completer depending on the commands already executed, in the current shell or even the rest of the arguments that have been already passed to the command. Such example is the config:set-property command which will provide auto completion for only for the properties of the pid specified by a previously issued config:edit command or by the option --pid.

This is done by accessing the CommandSession object which holds the state of the console. To get access to the CommandSession:

CommandSession session = CommandSessionHolder.getSession();

CommandSession provides map like methods for storing key/value pairs and can be used to put/get the state.

If you want to get access to the list of arguments that is already passed to the command, you can simply:

ArgumentCompleter.ArgumentList list = (ArgumentCompleter.ArgumentList) commandSession.get(ArgumentCompleter.ARGUMENTS_LIST);
Test

Launch a Karaf instance and run the following command to install the newly created bundle:

karaf@root()> bundle:install -s mvn:org.apache.karaf.shell.samples/shell-sample-commands/1.0-SNAPSHOT

Let’s try running the command:

karaf@root> test:hello <tab>
 one    two    three

WebConsole

You can also extend the Apache Karaf WebConsole by providing and installing a webconsole plugin.o

A plugin is an OSGi bundle that register a Servlet as an OSGi service with some webconsole properties.