Tutorial: An own Gogo Shell Command

Hi folks,

welcome to my first tutorial. It gives a short overview of “How to create my own Apache Felix Gogo shell command”.

I choose a bulk install for this guide, because, during some evaluation of OSGi and JPA I sorely missed such a command.

Preface

What should this command do?

  1. It should let us install a bulk of bundle archives via a Gogo shell command.
  2. We want to specify a parent directory path, so that we didn’t must copy the files into a specific one
  3. We want to choose the files to install via a regular expression pattern
  4. Additionally it would be nice, if we can use a basic file name pattern and a list of possible file endings. This is also for convenience.

So, what does we have to do:

  1. Implementing the service functionality
  2. Registering the service at the osgi service registry

Requirements

This tutorial uses Maven and Netbeans, so if you want to use Ant you need the Gogo runtime jar and the Apache Felix OSGi Framework implementation.

Preparation

  1. Create a new project in Netbeans as a Maven -> OSGi Bundle.
    • Project Name : BulkInstaller
    • Group Id : de.betaphitheta.tutorials
    • Version : No Change needed
    • Package : de.betaphitheta.tutorials.bulkinstaller
  2. Add
    <dependency>
    <groupId>org.apache.felix</groupId>
    <artifactId>org.apache.felix.gogo.runtime</artifactId>
    <version>0.10.0</version>
    </dependency>

     

    as a dependency.
    This allows us to use a simple annotation syntax for documentation

Let’s get ready to rumble

Implementing the service

  1. Add the class BulkInstaller to the bulkinstaller package. This class contains our command implementation.
  2. Create a constructor with the BundleContext as a parameter and assign this to a field
    BundleContext bundleContext;
    public BulkInstaller(BundleContext bundleContext) {
    this.bundleContext = bundleContext;
    }

    This is required because we need the BundleContext.install method.

  3. Create two variants of the method bulkInstall.
    One with a String parameter for the directory and a String parameter for the filePattern. The other one has additionally a String array parameter for the file endings.
    The first one calls the second one with a null as third parameter, to avoid duplicate code. 

    public void bulkInstall(String directory, String filePatter) {
    bulkInstall(directory, filePatter, null);
    }
    public void bulkInstall(String directory, String filePatter, String[] suffixes) {
    // More to do
    }
  4. At least, in this class, we need to implement our business logic.
    So we must implement 

    • Accessing the directory given by the user
      File f = new File(directory);
      if (!f.exists()) {
      System.err.println("Directory " + directory + " not found!");
      return;
      }
    • Creating, if required, the regular expression for matching the requested file suffixes
      String suffixesPattern = "";
      if (suffixes != null) {
      for (String suffix : suffixes) {
      if (suffixesPattern.length() != 0) {
      suffixesPattern += "|";
      }
      suffixesPattern += suffix;
      }
      }
      if (suffixesPattern.length() != 0) {
      suffixesPattern = ".?(" + suffixesPattern + ")";
      }
    • Getting all files from the directory matching the user expression + the suffix expression
       list = f.list(new PatternFilter(filePatter + suffixesPattern));
      if (list == null || list.length == 0) {
      System.err.println("No matching files found in " + directory);
      return;
      }

       

      with PatternFilter as inner class

      private class PatternFilter implements FilenameFilter {
      private final String pattern;PatternFilter(String pattern) {
      this.pattern = pattern;
      }
      public boolean accept(File dir, String name) {
      return name.matches(pattern);
      }
      }
    • For each file using BundleContext.install to try to install it
      for (String file : list) {
      try {
      Bundle installed = bundleContext.installBundle("file:" + directory + File.separatorChar + file);
      System.out.println(installed.getSymbolicName() + " with version " + installed.getVersion().toString() + " installed under id " + installed.getBundleId());
      } catch (BundleException ex) {
      ex.printStackTrace(System.err);
      }
      }

    As you may noticed, I used System.out / System.err for printing messages. This is because, the Gogo shell runs in on the commandline where we have no other possibilities to notificate.

  5. What’s left at this point? The registration of the implementation.
    For that OSGi provides two service properties: 

    • osgi.command.scope for the general scope or namespace for the command. For example, the Gogo commands have the scope gogo.
      We choose tutorial as the scope.
    • osgi.command.function for the name of the registered commands as an array of strings. Important: The functionname provided here must match the method name in the registered service implementation.
      So we have here bulkInstall as the only value.

    Putting things together we came to the following:

    Hashtable props = new Hashtable();
    props.put("osgi.command.scope", "tutorial");
    props.put("osgi.command.function", new String[] {"bulkInstall});
    context.registerService(BulkInstaller.class.getName(), new BulkInstaller(context), props);

    This code must be placed in a Activator class in the method start.

    public class Activator implements BundleActivator {
    
    public void start(BundleContext context) throws Exception {
    Hashtable props = new Hashtable();
    props.put("osgi.command.scope", "tutorial");
    props.put("osgi.command.function", new String[] {
    "bulkInstal", "bukInstall" });
    context.registerService(BulkInstaller.class.getName(), new BulkInstaller(context), props);
    }
    
    public void stop(BundleContext context) throws Exception {
    }
    }

Ensure that the maven-bundle-plugin contains in the configuration->instructions section the following line: <Bundle-Activator>de.betaphitheta.bulkinstaller.Activator</Bundle-Activator>
Without this line the MANIFEST.MF doesn’t containt the Bundle-Activator property which means, that after the bundle is started the Activator is not executed and the BulkInstaller is not registered.

The last thing we have to do is build the bundle and install and start it. Voila, we have our own command for the Gogo command shell for Apache Felix.

What’s left? A documentation like the one you see, if you use help <command>. For this you must the @Descriptor annotation from the org.apache.felix.service.command package which is located in the org.apache.felix.gogo.runtime bundle.

You can find the complete Netbeans project at GitHub. In this project the @Descriptor annotation is already used.
For executing the tests you must also download this project (and build it), because I used a JUnit Rule for setup and teardown the required directory structure. For this project I will provide during the next weeks a new tutorial.

I hope you enjoyed it.

Cheers

Peter

 
7 Kudos
Don't
move!

5 thoughts on “Tutorial: An own Gogo Shell Command


  1. Pingback: Tutorial: An own Gogo Shell Command « Beta Phi Theta

  2. Pingback: Tutorial: JUnit-Rule « Beta Phi Theta

  3. Pingback: Trik blogging


Leave a Reply

Your email address will not be published. Required fields are marked *