//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

=== 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.
