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:generate \
  -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.core</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 8

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.8</target>
        <source>1.8</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 org.apache.karaf.shell.api.action.Action;
import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.lifecycle.Service;

@Command(scope = "test", name = "hello", description="Says hello")
@Service
public class HelloShellCommand implements Action {

    @Override
    public Object execute() throws Exception {
        System.out.println("Executing Hello command");
        return null;
    }
}
Manifest

In order for Karaf to find your command, you need to add the Karaf-Commands=* manifest header.

This is usually done by modifying the maven bundle plugin configuration

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <configuration>
        <instructions>
            <Karaf-Commands>*</Karaf-Commands>
        </instructions>
    </configuration>
</plugin>
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 org.apache.karaf.shell.api.action.Action;
import org.apache.karaf.shell.api.action.Argument;
import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.Completion;
import org.apache.karaf.shell.api.action.lifecycle.Service;

@Command(scope = "test", name = "hello", description="Says hello")
@Service
public class HelloShellCommand implements Action {

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

    @Override
    public Object execute() throws Exception {
        System.out.println("Hello " + name);
        return null;
    }
}
Completer bean

A completer is a bean which implements the Completer interface:

package org.apache.karaf.shell.samples;

import org.apache.karaf.shell.api.action.lifecycle.Service;
import org.apache.karaf.shell.api.console.CommandLine;
import org.apache.karaf.shell.api.console.Completer;
import org.apache.karaf.shell.api.console.Session;
import org.apache.karaf.shell.support.completers.StringsCompleter;

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

    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
        StringsCompleter delegate = new StringsCompleter();
        delegate.getStrings().add("Mike");
        delegate.getStrings().add("Eric");
        delegate.getStrings().add("Jenny");
        return delegate.complete(session, commandLine, candidates);
    }

}
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 org.apache.karaf.shell.api.action.Action;
import org.apache.karaf.shell.api.action.Argument;
import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.Completion;
import org.apache.karaf.shell.api.action.Option;
import org.apache.karaf.shell.api.action.lifecycle.Service;

@Command(scope = "test", name = "hello", description="Says hello")
@Service
public class HelloShellCommand implements Action {

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

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

    @Override
    public Object execute() throws Exception {
        System.out.println(greet + " " + name);
        return null;
    }
}
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.

The Session object provides map like methods for storing key/value pairs and can be used to put/get the state. The pre-parsed CommandLine objects allows you to check the previous arguments and options on the command line and to fine tune the behavior of the Completer. Those two objects are given to the Completer when calling the complete method.

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.

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