            -----------------------------------------
               How to create an extension for XMvn
            -----------------------------------------
                        Mikolaj Izdebski
            -----------------------------------------
                           2013-03-12


How to create an extension for XMvn

* Abstract

  In this short tutorial I am going to show you how you can extend
  functionality provided by XMvn.  As an example I will create a very
  simple extension that allows XMvn to be used to install Eclipse
  artifacts.

* Tutorial

  First we'll start with creating a skeleton POM file.  XMvn
  extensions don't use separate packaging type, you need to use
  packaging <<<jar>>> (which is default, so you can omit it).
  Obviously we need to add a dependency on XMvn Core as this module
  provides functionality we depend on.  We'll use dependency scope
  <<<provided>>> as we need XMvn API to compile our extension, but
  during runtime XMvn will be provided for us (whatever loads our
  extension it will load XMvn first).

------------------------------------------------------------------------
  <dependency>
    <groupId>org.fedoraproject.xmvn</groupId>
    <artifactId>xmvn-core</artifactId>
    <version>0.4.0</version>
    <scope>provided</scope>
  </dependency>
------------------------------------------------------------------------

  Now we can start writing some code.  To create an installer for
  Eclipse artifacts we need to provide implementation for interface
  <<<org.fedoraproject.xmvn.installer.ProjectInstaller>>>.  In order
  for XMvn to be able to find and load our extension we need to
  implement is as a Plexus component.

------------------------------------------------------------------------
@Component( role = ProjectInstaller.class )
public class EclipseInstaller
    implements ProjectInstaller
{
}
------------------------------------------------------------------------

  <<<ProjectInstaller>>> interface requires us to implement two
  abstract methods: <<<getSupportedPackagingTypes()>>> and
  <<<installProject()>>>.

  The first method simply returns a <<<List>>> of packaging types
  supported by our extension.  There are several Eclipse packaging
  types, but in this example I'll cover only three:
  <<<eclipse-plugin>>>, <<<eclipse-test-plugin>>> and
  <<<eclipse-feature>>>.  In its simplest form the implementation of
  <<<getSupportedPackagingTypes()>>> could look like:

------------------------------------------------------------------------
    public List<String> getSupportedPackagingTypes()
    {
        // Return list of packaging types we support.
        return Arrays.asList( "eclipse-plugin", "eclipse-test-plugin", "eclipse-feature" );
    }
------------------------------------------------------------------------

  Now it's time to implement the second method.  Bundles are normal
  JAR files so we want to them into standard JAR location.
  Additionally we need to create additional symlinks in Eclipse
  directory so that Equinox can find and load these bundles.

  Because we don't want to hardcode the location of JAR directory (and
  to show how to obtain and use configuration objects) we'll query
  standard XMvn configurator to give us the directory path.  To obtain
  a reference to the configurator (or any other service provided by
  XMvn) it's enough to create a field of appropriate type (in this
  case <<<org.fedoraproject.xmvn.config.Configurator>>>) and annotate
  it with <<<org.codehaus.plexus.component.annotations.Requirement>>>.
  This way Plexus container will inject the dependency automatically.

------------------------------------------------------------------------
    // Configurator is dynamic dependency. This private field will be injected by Sisu during runtime.
    @Requirement
    private Configurator configurator;
------------------------------------------------------------------------

  Once we have a reference to an instance of configurator we can ask
  it to give us the location of JAR directory and use it to create a
  <<<Path>>> object pointing to the target JAR file.

------------------------------------------------------------------------
        // Install all JARs into /usr/share/java (or whatever is configured as JAR dir)
        // FIXME: in JAR is platform-dependent install in into JNI dir instead
        Path javaDir = Paths.get( configurator.getConfiguration().getInstallerSettings().getJarDir() );
        Path target = javaDir.resolve( project.getArtifactId() );
------------------------------------------------------------------------

  In a similar way we can decide where to place the symlink to the
  bundle (either in <<<features>>> or <<<plugins>>> directory).  Of
  course this example is oversimplified, in real extension the path
  should not be hardcoded and there the code may a bit be more
  complicated.

------------------------------------------------------------------------
        // Create symlink to the JAR somewhere under /usr/lib64/eclipse/. In real plugin the location should be
        // configurable.
        Path eclipseDir = Paths.get( "/usr/lib64/eclipse" );
        eclipseDir = eclipseDir.resolve( project.getPackaging().contains( "feature" ) ? "features" : "plugins" );
        Path symlink = eclipseDir.resolve( project.getArtifactId() );
------------------------------------------------------------------------

  After we decided where base JAR and the symlink should go we can
  just call <<<addJarFile()>>> method to perform the installation.

------------------------------------------------------------------------
        // So far we just determined paths to different files. Now it's time to call XMvn to do the real work.
        targetPackage.addJarFile( source, target, Collections.singletonList( symlink ) );
------------------------------------------------------------------------

  We have used Maven API here so of course we need to add dependency on Maven Core to our POM.

------------------------------------------------------------------------
  <dependency>
    <groupId>org.apache.maven</groupId>
    <artifactId>maven-core</artifactId>
    <version>3.2.1</version>
    <scope>provided</scope>
  </dependency>
------------------------------------------------------------------------

  To generate all the metadata necessary for XMvn to recognize our
  extension we must run <<<generate-metadata>>> goal of
  <<<plexus-component-metadata>>> plugin.  This is done by adding
  appropriate entry in the POM.

------------------------------------------------------------------------
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.plexus</groupId>
        <artifactId>plexus-component-metadata</artifactId>
        <version>1.5.5</version>
        <executions>
          <execution>
            <id>process-classes</id>
            <goals>
              <goal>generate-metadata</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
------------------------------------------------------------------------

  After building the extension with famous <<<mvn clean install>>> we
  can place it in <<<lib/ext/>>> directory of XMvn installation.  The
  extension will be automatically loaded by Plexus Classworlds during
  XMvn boot-up and implemented functionality will be available to
  packages built with XMvn out of the box.

  Note that in this example I completely ignored POM installation.
  This is because this tutorial is supposed to be just a starting
  point to creating extensions and not complete documentation on this
  subject and I wanted to keep this it short and simple.

  For more information refer to API documentation and source code for
  installers for standard packaging types (like <<<pom>>> or
  <<<jar>>>), which are implemented in XMvn itself.

  Snippets of the complete source code for this example follow.

    * <<<pom.xml>>>:

------------------------------------------------------------------------
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>eclipse-xmvn-extension</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.fedoraproject.xmvn</groupId>
      <artifactId>xmvn-core</artifactId>
      <version>0.4.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-core</artifactId>
      <version>3.2.1</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.plexus</groupId>
        <artifactId>plexus-component-metadata</artifactId>
        <version>1.5.5</version>
        <executions>
          <execution>
            <id>process-classes</id>
            <goals>
              <goal>generate-metadata</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
------------------------------------------------------------------------

    * <<<src/main/java/EclipseInstaller.java>>>:

------------------------------------------------------------------------
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.fedoraproject.xmvn.config.Configurator;
import org.fedoraproject.xmvn.config.PackagingRule;
import org.fedoraproject.xmvn.installer.Package;
import org.fedoraproject.xmvn.installer.ProjectInstallationException;
import org.fedoraproject.xmvn.installer.ProjectInstaller;

@Component( role = ProjectInstaller.class )
public class EclipseInstaller
    implements ProjectInstaller
{
    // Configurator is dynamic dependency. This private field will be injected by Sisu during runtime.
    @Requirement
    private Configurator configurator;

    public List<String> getSupportedPackagingTypes()
    {
        // Return list of packaging types we support.
        return Arrays.asList( "eclipse-plugin", "eclipse-test-plugin", "eclipse-feature" );
    }

    public void installProject( MavenProject project, Package targetPackage, PackagingRule rule )
        throws ProjectInstallationException, IOException
    {
        Path source = project.getArtifact().getFile().toPath();

        // Install all JARs into /usr/share/java (or whatever is configured as JAR dir)
        // FIXME: in JAR is platform-dependent install in into JNI dir instead
        Path javaDir = Paths.get( configurator.getConfiguration().getInstallerSettings().getJarDir() );
        Path target = javaDir.resolve( project.getArtifactId() );

        // Create symlink to the JAR somewhere under /usr/lib64/eclipse/. In real plugin the location should be
        // configurable.
        Path eclipseDir = Paths.get( "/usr/lib64/eclipse" );
        eclipseDir = eclipseDir.resolve( project.getPackaging().contains( "feature" ) ? "features" : "plugins" );
        Path symlink = eclipseDir.resolve( project.getArtifactId() );

        // So far we just determined paths to different files. Now it's time to call XMvn to do the real work.
        targetPackage.addJarFile( source, target, Collections.singletonList( symlink ) );

        // TODO: install POM file as well
    }
}
------------------------------------------------------------------------
