Skip to main content

Jigsaw launcher for Felix

This post will show a simple launcher module for Felix. This launcher takes advantage of the exported packages list that can be obtained from Jigsaw’s modules to fill in the list of JRE’s packages, thus eliminating the need to hard code that list in Felix defaults.

Setting up the environement:

  1. Build Jigsaw

  2. Build the Maven plugin for Jigsaw 

  3. Download and extract Maven 3(.0.5) in ~/dev

  4. Export the variables:

    export M2_HOME=/dev/apache-maven-3.0.5/
    export MAVEN_HOME=
    /dev/apache-maven-3.0.5/
    export JAVA_HOME=~/dev/jigsaw/build/linux-x86_64-normal-server-release/images/jdk-module-image
    export PATH=$JAVA_HOME/bin:$M2_HOME/bin:$PATH

Create the launcher

  1.  Create the directories

    mkdir -p ~/dev/felix/felix-launcher/src/main/java/lh.jigsaw.felix.launcher/lh/jigsaw/felix/launcher mkdir ~/dev/felix/bundles mkdir ~/dev/felix/nonbundles mkdir ~/dev/felix/modules

  2. Create the modules library

    jmod create -L ~/dev/felix/modules

  3. Download Felix

    wget -O ~/dev/felix/nonbundles/org.apache.felix.framework-4.2.1.jar http://mirror.tcpdiag.net/apache//felix/org.apache.felix.framework-4.2.1.jar

    (although Felix framework is a bundle it is used as a non bundle)

  4. Add the pom

    gedit ~/dev/felix/felix-launcher/pom.xml &

    <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/maven-v4_0_0.xsd”>
        <modelVersion>4.0.0</modelVersion>
        <groupId>lh.jigsaw</groupId>
        <artifactId>jigsaw-felix-launcher</artifactId>
        <version>0.1-SNAPSHOT</version>
        <name>Jigsaw Felix launcher</name>
        <packaging>jmod</packaging>
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
      </properties>
        <build>     
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>2.3.2</version>
                </plugin>
                <plugin>
                    <groupId>lh.jigsaw</groupId>
                    <artifactId>jigsaw-maven-plugin</artifactId>
                    <version>0.1-SNAPSHOT</version>
                    <extensions>true</extensions>
                    <configuration>
                      <libraryDirectory>/home/ludovic/dev/felix/modules</libraryDirectory>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>

    change the libraryDirectory to your appropriate path

  5. Add the module-info

    gedit ~/dev/felix/felix-launcher/src/main/java/lh.jigsaw.felix.launcher/module-info.java &

    module lh.jigsaw.felix.launcher @ 0.1
    {
      requires jdk.jre;
      class lh.jigsaw.felix.launcher.Main;
    }

    (depends on jdk.jre to expose the JRE)

  6. Add the Main class

    gedit ~/dev/felix/felix-launcher/src/main/java/lh.jigsaw.felix.launcher/lh/jigsaw/felix/launcher/Main.java &

    package lh.jigsaw.felix.launcher;

    import java.io.File;
    import java.io.IOException;
    import java.lang.module.ModuleId;
    import java.lang.module.ModuleInfo;
    import java.lang.module.ModuleView;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.net.URLClassLoader;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.ServiceLoader;
    import org.openjdk.jigsaw.SimpleLibrary;

    /**
     *
     * @author ludovic
     */
    public class Main
    {
      public static void main(final String[] args)
      {
        try
        {    
          // Extract information from Jigsaw       //
          final String libraryPath = System.getProperty(“sun.java.launcher.module.library”);
         
          final SimpleLibrary library = SimpleLibrary.open(new File(libraryPath));
         
          final StringBuilder packages = new StringBuilder();
          final List<URL> urls = new ArrayList<>(); // ‘classpath’ for launching Felix
         
          for (ModuleId id:  library.listModuleIds())
          {
            if (isJreModule(id.name()))
            {
              urls.add(library.classPath(id).toURI().toURL());
             
              final ModuleInfo info = library.readModuleInfo(id);
              appendExportedPackages(info, packages);
            }
          }
         
          packages.deleteCharAt(packages.length()-1);
         
          final File userDir = new File(System.getProperty(“user.dir”));
         
          // Additional “classpath” (Felix’s jar is added there)
          //     
          addNonOsgiJars(userDir, urls);
         
          // Initialise and start OSGi
          //     
          startOsgi(userDir, urls, packages.toString());
               
        }
        catch (IOException | ClassNotFoundException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | IllegalAccessException ex)
        {
          throw new RuntimeException(“error initialising”, ex);
        }
       
        System.out.println(“started.”);
       
      }

      private static boolean isJreModule(final String moduleName)
      {
        return moduleName.startsWith(“java”) || moduleName.startsWith(“javax”);
      }
     
      private static void appendExportedPackages(final ModuleInfo info, final StringBuilder packages)
      {
        final ModuleView view = info.defaultView();
        for (String exportedPackage: view.exports())
        {
          if (!isJavaPackage(exportedPackage))
          {
            packages.append(exportedPackage).append(’;’);
          }
        }
      } 
     
      private static boolean isJavaPackage(final String packageName)
      {
        // java.* are always available to bundles
        return packageName.startsWith(“java.”);
      }
     
      private static void addNonOsgiJars(final File userDir, final List<URL> urls) throws MalformedURLException
      {   
        final File bundlesDir = new File(userDir, “nonbundles”);
        for (File file : bundlesDir.listFiles())
        {
          urls.add(file.toURI().toURL());
        }
      }
     
      // As Jigsaw currently identificate itself as Java 1.8, and as Felix already has Java 1.8 package,   // override the full system packages, including Felix’s own   private static final String FELIX_SYSTEM_PACKAGES =
        “org.osgi.framework; version=1.7.0,” +
        " org.osgi.framework.hooks.bundle; version=1.1.0,” +
        " org.osgi.framework.hooks.resolver; version=1.0.0,” +
        " org.osgi.framework.hooks.service; version=1.1.0," +
        " org.osgi.framework.hooks.weaving; version=1.0.0," +
        " org.osgi.framework.launch; version=1.1.0," +
        " org.osgi.framework.namespace; version=1.0.0," +
        " org.osgi.framework.startlevel; version=1.0.0," +
        " org.osgi.framework.wiring; version=1.1.0," +
        " org.osgi.resource; version=1.0.0," +
        " org.osgi.service.packageadmin; version=1.2.0," +
        " org.osgi.service.startlevel; version=1.1.0," +
        " org.osgi.service.url; version=1.0.0," +
        " org.osgi.util.tracker; version=1.5.1 “;
     
      private static Map<String, Object> getFrameworkProperties(final String packages)
      {
        final Map config = new HashMap<>();
        config.put(“org.osgi.framework.system.packages”, FELIX_SYSTEM_PACKAGES + “,” + packages);
    //    config.put(“org.osgi.framework.system.packages.extra”, packages);
        return config;
      }
     
     
      // we use reflection to not depend on OSGi core (and to not make a module of it)  
      private static final Class<?>[] NO_ARG_SIG = null;
      private static final Object[] NO_ARG_INV = null;
     
      private static void startOsgi(final File userDir, final List<URL> urls, final String packages) throws ClassNotFoundException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, IllegalAccessException
      {     
          final URLClassLoader cl = getClassLoaderFor(urls);
         
          final Class<?> bundleClass = cl.loadClass(“org.osgi.framework.Bundle”);     
          final Class<?> bundleContextClass = cl.loadClass(“org.osgi.framework.BundleContext”);     
          final Class<?> frameworkFactoryClass = cl.loadClass(“org.osgi.framework.launch.FrameworkFactory”);
         
          final Map<String, Object> properties = getFrameworkProperties(packages);
         
          final Object framework = getFrameworkFor(frameworkFactoryClass, cl, properties);     
          final Class<?> frameworkClass = framework.getClass();
         
          initialiseFramework(frameworkClass, framework);
         
          final Object bundleContext =  getBundleContextFor(frameworkClass, framework);
         
          final List<Object> installedBundles = new ArrayList<>();     
          installBundles(installedBundles, userDir, bundleContextClass, bundleContext);      
          startBundles(installedBundles, bundleClass);
         
          startFramework(frameworkClass, framework);
         
      }

      private static URLClassLoader getClassLoaderFor(final List<URL> urls)
      {
        final URL[] urlsArray = urls.toArray(new URL[urls.size()]);
       
        return new URLClassLoader(urlsArray);
      }

      private static Object getFrameworkFor(final Class<?> frameworkFactoryClass, final URLClassLoader cl, final Map<String, Object> properties) throws IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, IllegalAccessException
      {
        final ServiceLoader<?> sl = ServiceLoader.load(frameworkFactoryClass, cl);
        final Object factory = sl.iterator().next();
       
        final Method newFramework = factory.getClass().getMethod(“newFramework”, Map.class);
        return newFramework.invoke(factory, properties);
      }

      private static Object getBundleContextFor(final Class<?> frameworkClass, final Object framework) throws NoSuchMethodException, IllegalAccessException, SecurityException, IllegalArgumentException, InvocationTargetException
      {
        final Method getBundleContext = frameworkClass.getDeclaredMethod(“getBundleContext”, NO_ARG_SIG);
        final Object bundleContext = getBundleContext.invoke(framework, NO_ARG_INV);
        return bundleContext;
      }

      private static void initialiseFramework(final Class<?> frameworkClass, final Object framework) throws InvocationTargetException, SecurityException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException
      {
        final Method init = frameworkClass.getDeclaredMethod(“init”, NO_ARG_SIG);
        init.invoke(framework, NO_ARG_INV);
      }
     
      private static void installBundles(final List<Object> installedBundles, final File userDir, final Class<?> bundleContextClass, final Object bundleContext) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
      {   
        final Method installBundle = bundleContextClass.getDeclaredMethod(“installBundle”, String.class);
       
        final File bundlesDir = new File(userDir, “bundles”);
        for (File file : bundlesDir.listFiles())
        {
          final String name = file.toURI().toString();
          final Object bundle = installBundle.invoke(bundleContext, name);
          installedBundles.add(bundle);
        }
      }

      private static void startBundles(final List<Object> installedBundles, final Class<?> bundleClass) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
      {  
        final Method start = bundleClass.getDeclaredMethod(“start”, NO_ARG_SIG);
       
        for (Object bundle: installedBundles)
        {
          start.invoke(bundle, NO_ARG_INV);
        }
      }

      private static void startFramework(final Class<?> frameworkClass, final Object framework) throws IllegalAccessException, SecurityException, NoSuchMethodException, InvocationTargetException, IllegalArgumentException
      {
        final Method start = frameworkClass.getDeclaredMethod(“start”,  NO_ARG_SIG);
        start.invoke(framework, NO_ARG_INV);
      }
    }

  7. Make

    cd ~/dev/felix/felix-launcher/
    mvn install

Test with a sample bundle

  1. Create the directories

    mkdir -p ~/dev/felix/simplebundle/src/main/java/lh/jigsaw/test/simplebundle

  2.  Add the pom

    gedit ~/dev/felix/simplebundle/pom.xml &

    <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>lh.jigsaw.test</groupId>
        <artifactId>simplebundle</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>bundle</packaging>

        <name>simplebundle OSGi Bundle</name>

        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>

        <dependencies>
            <dependency>
                <groupId>org.osgi</groupId>
                <artifactId>org.osgi.core</artifactId>
                <version>4.3.0</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>

        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.felix</groupId>
                    <artifactId>maven-bundle-plugin</artifactId>
                    <version>2.3.7</version>
                    <extensions>true</extensions>
                    <configuration>
                        <instructions>
                            <Bundle-Activator>lh.jigsaw.test.simplebundle.Activator</Bundle-Activator>
                            <Export-Package/>
                        </instructions>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>

  3. Add the bundle activator

    gedit ~/dev/felix/simplebundle/src/main/java/lh/jigsaw/test/simplebundle/Activator.java &

    package lh.jigsaw.test.simplebundle;

    import javax.swing.JOptionPane;
    import org.osgi.framework.BundleActivator;
    import org.osgi.framework.BundleContext;

    public class Activator implements BundleActivator
    {
      public void start(BundleContext context) throws Exception
      {
        System.out.println(“Hello Jigsaw/Felix!”);
        JOptionPane.showMessageDialog(null, “Hello Jigsaw/Felix!”);
      }

      public void stop(BundleContext context) throws Exception
      {
      }
    }

  4. Make
    (as the bundle is compiled with a compiler that does not know about Jigsaw, we must use the standard Java 7)

    export JAVA_HOME=’/usr/lib/jvm/java-7-openjdk-amd64’
    cd ~/dev/felix/simplebundle/
    mvn install

  5. Copy to the bundle directory

    cp ~/dev/felix/simplebundle/target/simplebundle-1.0-SNAPSHOT.jar ~/dev/felix/bundles

Run

cd ~/dev/felix/
java -L ~/dev/felix/modules -m lh.jigsaw.felix.launcher