content: package org.apache.aries.plugin.esa;
  
  /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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.
   */
  
  import static org.apache.aries.util.manifest.BundleManifest.fromBundle;
  
  import java.io.File;
  import java.io.IOException;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.LinkedHashMap;
  import java.util.LinkedHashSet;
  import java.util.Map;
  import java.util.Set;
  
  import org.apache.aries.util.manifest.BundleManifest;
  import org.apache.maven.archiver.PomPropertiesUtil;
  import org.apache.maven.artifact.Artifact;
  import org.apache.maven.plugin.AbstractMojo;
  import org.apache.maven.plugin.MojoExecutionException;
  import org.apache.maven.project.MavenProject;
  import org.codehaus.plexus.archiver.ArchiverException;
  import org.codehaus.plexus.archiver.zip.ZipArchiver;
  import org.codehaus.plexus.util.DirectoryScanner;
  import org.codehaus.plexus.util.FileUtils;
  
  import aQute.bnd.osgi.Analyzer;
  
  /**
   * Builds OSGi Enterprise Subsystem Archive (esa) files.
   *
   * @version $Id: $
   * @goal esa
   * @phase package
   * @requiresDependencyResolution test
   */
  public class EsaMojo extends AbstractMojo {
  
      public enum EsaContent {none, all, content}
  
      public enum EsaManifestContent {all, content}
  
      public static final String SUBSYSTEM_MF_URI = "OSGI-INF/SUBSYSTEM.MF";
  
      private static final String[] DEFAULT_INCLUDES = {"**/**"};
  
      private static final Set<String> SKIP_INSTRUCTIONS = new HashSet<>();
  
      static {
          SKIP_INSTRUCTIONS.add(Constants.SUBSYSTEM_MANIFESTVERSION);
          SKIP_INSTRUCTIONS.add(Constants.SUBSYSTEM_SYMBOLICNAME);
          SKIP_INSTRUCTIONS.add(Constants.SUBSYSTEM_VERSION);
          SKIP_INSTRUCTIONS.add(Constants.SUBSYSTEM_NAME);
          SKIP_INSTRUCTIONS.add(Constants.SUBSYSTEM_DESCRIPTION);
          SKIP_INSTRUCTIONS.add(Constants.SUBSYSTEM_CONTENT);
      }
  
      /**
       * Single directory for extra files to include in the esa.
       *
       * @parameter expression="${basedir}/src/main/esa"
       * @required
       */
      private File esaSourceDirectory;
  
      /**
       * The location of the SUBSYSTEM.MF file to be used within the esa file.
       *
       * @parameter expression="${basedir}/src/main/esa/OSGI-INF/SUBSYSTEM.MF"
       */
      private File subsystemManifestFile;
  
  
      /**
       * The location of the manifest file to be used within the esa file.
       *
       * @parameter expression="${basedir}/src/main/esa/META-INF/MANIFEST.MF"
       */
      private File manifestFile;
  
      /**
       * Directory that resources are copied to during the build.
       *
       * @parameter expression="${project.build.directory}/${project.build.finalName}"
       * @required
       */
      private String workDirectory;
  
      /**
       * Directory that remote-resources puts legal files.
       *
       * @parameter expression="${project.build.directory}/maven-shared-archive-resources"
       * @required
       */
      private String sharedResources;
  
      /**
       * The directory for the generated esa.
       *
       * @parameter expression="${project.build.directory}"
       * @required
       */
      private String outputDirectory;
  
      /**
       * The name of the esa file to generate.
       *
       * @parameter alias="esaName" expression="${project.build.finalName}"
       * @required
       */
      private String finalName;
  
      /**
       * The maven project.
       *
       * @parameter expression="${project}"
       * @required
       * @readonly
       */
      private MavenProject project;
  
      /**
       * The Jar archiver.
       *
       * @component role="org.codehaus.plexus.archiver.Archiver" roleHint="zip"
       * @required
       */
      private ZipArchiver zipArchiver;
  
      /**
       * Whether to generate a manifest based on maven configuration.
       *
       * @parameter expression="${generateManifest}" default-value="false"
       */
      private boolean generateManifest;
  
      /**
       * Configuration for the plugin.
       *
       * @parameter
       */
      private Map instructions = new LinkedHashMap();
  
      /**
       * Adding pom.xml and pom.properties to the archive.
       *
       * @parameter expression="${addMavenDescriptor}" default-value="true"
       */
      private boolean addMavenDescriptor;
  
      /**
       * Include or not empty directories
       *
       * @parameter expression="${includeEmptyDirs}" default-value="true"
       */
      private boolean includeEmptyDirs;
  
      /**
       * Whether creating the archive should be forced.
       *
       * @parameter expression="${forceCreation}" default-value="false"
       */
      private boolean forceCreation;
  
      /**
       * Define which bundles to include in the archive.
       *   none - no bundles are included
       *   content - direct dependencies go into the content
       *   all - direct and transitive dependencies go into the content
       *
       * @parameter expression="${archiveContent}" default-value="content"
       */
      private String archiveContent;
  
      /**
       * Define which bundles to include in the manifest Subsystem-Content header.
       *  all - direct and transitive dependencies go into the Subsystem-Content header
       *  content - direct dependencies go into the Subsystem-Content header
       *
       * @parameter expression="${manifestContent}" default-value="content"
       */
      private String manifestContent;
  
      /**
       * Define the start order for content bundles.
       *   none - no start orders are added
       *   dependencies - start order based on pom dependency order
       *
       * @parameter expression="${startOrder}" default-value="none"
       */
      private String startOrder;
  
      private File buildDir;
  
      /**
       * add the dependencies to the archive depending on the configuration of <archiveContent />
       */
      private void addDependenciesToArchive() throws MojoExecutionException {
          try
          {
              Set<Artifact> artifacts = null;
              switch (EsaContent.valueOf(archiveContent)) {
              case none:
                  getLog().info("archiveContent=none: subsystem archive will not contain any bundles.");
                  break;
              case content:
                  // only include the direct dependencies in the archive
                  artifacts = project.getDependencyArtifacts();
                  break;
              case all:
                  // include direct and transitive dependencies in the archive
                  artifacts = project.getArtifacts();
                  break;
              default:
                  throw new MojoExecutionException("Invalid configuration for <archiveContent/>.  Valid values are none, content and all." );
              }
  
              if (artifacts != null) {
                  // Explicitly add self to bundle set (used when pom packaging
                  // type != "esa" AND a file is present (no point to add to
                  // zip archive without file)
                  final Artifact selfArtifact = project.getArtifact();
                  if (!"esa".equals(selfArtifact.getType()) && selfArtifact.getFile() != null) {
                      getLog().info("Explicitly adding artifact[" + selfArtifact.getGroupId() + ", " + selfArtifact.getId() + ", " + selfArtifact.getScope() + "]");
                      artifacts.add(project.getArtifact());
                  }
  
                  artifacts = selectArtifactsInCompileOrRuntimeScope(artifacts);
                  artifacts = selectNonJarArtifactsAndBundles(artifacts);
  
                  int cnt = 0;
                  for (Artifact artifact : artifacts) {
                      if (!artifact.isOptional() /*&& filter.include(artifact)*/) {
                          getLog().info("Copying artifact[" + artifact.getGroupId() + ", " + artifact.getId() + ", " +
                                  artifact.getScope() + "]");
                          zipArchiver.addFile(artifact.getFile(), artifact.getArtifactId() + "-" + artifact.getVersion() + "." + (artifact.getType() == null ? "jar" : artifact.getType()));
                          cnt++;
                      }
                  }
                  getLog().info(String.format("Added %s artifacts to subsystem subsystem archive.", cnt));
              }
          }
          catch ( ArchiverException e )
          {
              throw new MojoExecutionException( "Error copying esa dependencies", e );
          }
  
      }
  
      /**
       *
       * Copies source files to the esa
       *
       * @throws MojoExecutionException
       */
      private void copyEsaSourceFiles() throws MojoExecutionException {
          try
          {
              File esaSourceDir = esaSourceDirectory;
              if ( esaSourceDir.exists() )
              {
                  getLog().info( "Copy esa resources to " + getBuildDir().getAbsolutePath() );
  
                  DirectoryScanner scanner = new DirectoryScanner();
                  scanner.setBasedir( esaSourceDir.getAbsolutePath() );
                  scanner.setIncludes( DEFAULT_INCLUDES );
                  scanner.addDefaultExcludes();
                  scanner.scan();
  
                  String[] dirs = scanner.getIncludedDirectories();
  
                  for ( int j = 0; j < dirs.length; j++ )
                  {
                      new File( getBuildDir(), dirs[j] ).mkdirs();
                  }
  
                  String[] files = scanner.getIncludedFiles();
  
                  for ( int j = 0; j < files.length; j++ )
                  {
                      File targetFile = new File( getBuildDir(), files[j] );
  
                      targetFile.getParentFile().mkdirs();
  
                      File file = new File( esaSourceDir, files[j] );
                      FileUtils.copyFileToDirectory( file, targetFile.getParentFile() );
                  }
              }
          }
          catch ( Exception e )
          {
              throw new MojoExecutionException( "Error copying esa resources", e );
          }
      }
  
      private void includeCustomManifest() throws MojoExecutionException {
          try
          {
              if (!generateManifest) {
                  includeCustomSubsystemManifestFile();
              }
          }
          catch ( IOException e )
          {
              throw new MojoExecutionException( "Error copying SUBSYSTEM.MF file", e );
          }
      }
  
      private void generateSubsystemManifest() throws MojoExecutionException {
          if (generateManifest) {
              String fileName = new String(getBuildDir() + "/"
                      + SUBSYSTEM_MF_URI);
              File appMfFile = new File(fileName);
  
              try {
                  // Delete any old manifest
                  if (appMfFile.exists()) {
                      FileUtils.fileDelete(fileName);
                  }
  
                  appMfFile.getParentFile().mkdirs();
                  if (appMfFile.createNewFile()) {
                      writeSubsystemManifest(fileName);
                  }
              } catch (java.io.IOException e) {
                  throw new MojoExecutionException(
                          "Error generating SUBSYSTEM.MF file: " + fileName, e);
              }
          }
  
          // Check the manifest exists
          File ddFile = new File( getBuildDir(), SUBSYSTEM_MF_URI);
          if ( !ddFile.exists() )
          {
              getLog().warn(
                  "Subsystem manifest: " + ddFile.getAbsolutePath() + " does not exist." );
          }
  
      }
  
      private void addMavenDescriptor() throws MojoExecutionException {
          try {
  
              if (addMavenDescriptor) {
                  if (project.getArtifact().isSnapshot()) {
                      project.setVersion(project.getArtifact().getVersion());
                  }
  
                  String groupId = project.getGroupId();
  
                  String artifactId = project.getArtifactId();
  
                  zipArchiver.addFile(project.getFile(), "META-INF/maven/"
                          + groupId + "/" + artifactId + "/pom.xml");
                  PomPropertiesUtil pomPropertiesUtil = new PomPropertiesUtil();
                  File dir = new File(project.getBuild().getDirectory(),
                          "maven-zip-plugin");
                  File pomPropertiesFile = new File(dir, "pom.properties");
                  pomPropertiesUtil.createPomProperties(project, zipArchiver, null,
                          pomPropertiesFile, forceCreation);
              }
          } catch (Exception e) {
              throw new MojoExecutionException("Error assembling esa", e);
          }
      }
  
  
      private void init() {
          getLog().debug( " ======= esaMojo settings =======" );
          getLog().debug( "esaSourceDirectory[" + esaSourceDirectory + "]" );
          getLog().debug( "manifestFile[" + manifestFile + "]" );
          getLog().debug( "subsystemManifestFile[" + subsystemManifestFile + "]" );
          getLog().debug( "workDirectory[" + workDirectory + "]" );
          getLog().debug( "outputDirectory[" + outputDirectory + "]" );
          getLog().debug( "finalName[" + finalName + "]" );
          getLog().debug( "generateManifest[" + generateManifest + "]" );
  
          if (archiveContent == null) {
              archiveContent = new String("content");
          }
  
          getLog().debug( "archiveContent[" + archiveContent + "]" );
          getLog().info( "archiveContent[" + archiveContent + "]" );
  
          if (manifestContent == null) {
              manifestContent = new String("content");
          }
  
          getLog().debug( "manifestContent[" + archiveContent + "]" );
          getLog().info( "manifestContent[" + archiveContent + "]" );
  
          zipArchiver.setIncludeEmptyDirs( includeEmptyDirs );
          zipArchiver.setCompress( true );
          zipArchiver.setForced( forceCreation );
      }
  
  
      private void writeSubsystemManifest(String fileName)
              throws MojoExecutionException {
          try {
              // TODO: add support for dependency version ranges. Need to pick
              // them up from the pom and convert them to OSGi version ranges.
              FileUtils.fileAppend(fileName, Constants.SUBSYSTEM_MANIFESTVERSION + ": " + "1" + "\n");
              FileUtils.fileAppend(fileName, Constants.SUBSYSTEM_SYMBOLICNAME + ": "
                      + getSubsystemSymbolicName(project.getArtifact()) + "\n");
              FileUtils.fileAppend(fileName, Constants.SUBSYSTEM_VERSION + ": "
                      + getSubsystemVersion() + "\n");
              FileUtils.fileAppend(fileName, Constants.SUBSYSTEM_NAME + ": " + getSubsystemName() + "\n");
              String description = getSubsystemDescription();
              if (description != null) {
                  FileUtils.fileAppend(fileName, Constants.SUBSYSTEM_DESCRIPTION + ": " + description + "\n");
              }
  
              // Write the SUBSYSTEM-CONTENT
              Set<Artifact> artifacts = null;
  
              switch (EsaManifestContent.valueOf(manifestContent)) {
                  case content:
                      // only include the direct dependencies in the content
                      artifacts = project.getDependencyArtifacts();
                      break;
                  case all:
                      // include direct and transitive dependencies in content
                      artifacts = project.getArtifacts();
                      break;
                  default:
                      throw new MojoExecutionException("Invalid configuration for <manifestContent/>.  Valid values are content and all." );
              }
  
              artifacts = selectArtifactsInCompileOrRuntimeScope(artifacts);
              artifacts = selectNonJarArtifactsAndBundles(artifacts);
  
              Iterator<Artifact> iter = artifacts.iterator();
  
              FileUtils.fileAppend(fileName, Constants.SUBSYSTEM_CONTENT + ": ");
              int order = 0;
              int nbInSubsystemContent = 0;
              while (iter.hasNext()) {
                  Artifact artifact = iter.next();
                  order++;
                  ContentInfo info = ContentInfo.create(artifact, getLog());
                  if (info == null) {
                      continue;
                  }
                  String entry = info.getContentLine();
                  if ("dependencies".equals(startOrder)) {
                      entry += ";start-order:=\"" + order + "\"";
                  }
                  if (iter.hasNext()) {
                      entry += ",\n ";
                  }
                  nbInSubsystemContent++;
                  FileUtils.fileAppend(fileName, entry);
              }
              getLog().info("Added '" + nbInSubsystemContent + "' artefacts to the Subsystem-Content header");
  
              FileUtils.fileAppend(fileName, "\n");
  
              Iterator<Map.Entry<?, ?>> instructionIter = instructions.entrySet().iterator();
              while(instructionIter.hasNext()) {
                  Map.Entry<?, ?> entry = instructionIter.next();
                  String header = entry.getKey().toString();
                  if (SKIP_INSTRUCTIONS.contains(header)) {
                      continue;
                  }
                  getLog().debug("Adding header: " + header);
                  FileUtils.fileAppend(fileName, header + ": " + entry.getValue() + "\n");
              }
  
          } catch (Exception e) {
              throw new MojoExecutionException(
                      "Error writing dependencies into SUBSYSTEM.MF", e);
          }
  
      }
  
      // The maven2OsgiConverter assumes the artifact is a jar so we need our own
      // This uses the same fallback scheme as the converter
      private String getSubsystemSymbolicName(Artifact artifact) {
          if (instructions.containsKey(Constants.SUBSYSTEM_SYMBOLICNAME)) {
              return instructions.get(Constants.SUBSYSTEM_SYMBOLICNAME).toString();
          }
          return artifact.getGroupId() + "." + artifact.getArtifactId();
      }
  
      private String getSubsystemVersion() {
          if (instructions.containsKey(Constants.SUBSYSTEM_VERSION)) {
              return instructions.get(Constants.SUBSYSTEM_VERSION).toString();
          }
          return Analyzer.cleanupVersion(project.getVersion());
      }
  
      private String getSubsystemName() {
          if (instructions.containsKey(Constants.SUBSYSTEM_NAME)) {
              return instructions.get(Constants.SUBSYSTEM_NAME).toString();
          }
          return project.getName();
      }
  
      private String getSubsystemDescription() {
          if (instructions.containsKey(Constants.SUBSYSTEM_DESCRIPTION)) {
              return instructions.get(Constants.SUBSYSTEM_DESCRIPTION).toString();
          }
          return project.getDescription();
      }
  
      private File getBuildDir() {
          if (buildDir == null) {
              buildDir = new File(workDirectory);
          }
          return buildDir;
      }
  
      private void addBuildDir() throws MojoExecutionException {
          try {
              if (buildDir.isDirectory()) {
                  zipArchiver.addDirectory(buildDir);
              }
  
          } catch (Exception e) {
              throw new MojoExecutionException(
                      "Error writing dependencies into SUBSYSTEM.MF", e);
          }
      }
  
      private void includeCustomSubsystemManifestFile()
          throws IOException
      {
          if (subsystemManifestFile == null) {
              throw new NullPointerException("Subsystem manifest file location not set.  Use <generateManifest>true</generateManifest> if you want it to be generated.");
          }
          File appMfFile = subsystemManifestFile;
          if (appMfFile.exists()) {
              getLog().info( "Using SUBSYSTEM.MF "+ subsystemManifestFile);
              File osgiInfDir = new File(getBuildDir(), "OSGI-INF");
              FileUtils.copyFileToDirectory( appMfFile, osgiInfDir);
          }
      }
  
      /**
       * Return non-pom artifacts in 'compile' or 'runtime' scope only.
       */
      private Set<Artifact> selectArtifactsInCompileOrRuntimeScope(Set<Artifact> artifacts)
      {
          Set<Artifact> selected = new LinkedHashSet<Artifact>();
          for (Artifact artifact : artifacts) {
              String scope = artifact.getScope();
              if (scope == null
                  || Artifact.SCOPE_COMPILE.equals(scope)
                  || Artifact.SCOPE_RUNTIME.equals(scope)) {
                  if (artifact.getType() == null || !artifact.getType().equals("pom")) {
                      selected.add(artifact);
                  }
              }
          }
          return selected;
      }
  
      /**
       * Returns bundles and artifacts that aren't JARs
       */
      private Set<Artifact> selectNonJarArtifactsAndBundles(Set<Artifact> artifacts)
      {
          Set<Artifact> selected = new LinkedHashSet<Artifact>();
          for (Artifact artifact : artifacts) {
              if (isNonJarOrOSGiBundle(artifact)) {
                  selected.add(artifact);
              } else {
                  getLog().warn("Skipping dependency on non-bundle JAR! groupId: " + artifact.getGroupId() + " artifactId:" + artifact.getArtifactId());
              }
          }
          return selected;
      }
  
      private boolean isNonJarOrOSGiBundle(Artifact artifact) {
          if(!artifact.getFile().getName().endsWith(".jar")){
              return true;
          } else {
              BundleManifest manifest = fromBundle(artifact.getFile());
              return manifest != null && manifest.getSymbolicName() != null;
          }
      }
  
      private void includeSharedResources() throws MojoExecutionException {
          try
          {
              //include legal files if any
              File sharedResourcesDir = new File(sharedResources);
              if (sharedResourcesDir.isDirectory()) {
                  zipArchiver.addDirectory(sharedResourcesDir);
              }
          }
          catch ( Exception e )
          {
              throw new MojoExecutionException( "Error assembling esa", e );
          }
      }
  
      /**
       * Creates the final archive.
       *
       * @throws MojoExecutionException
       */
      private void createEsaFile() throws MojoExecutionException {
          try
          {
              File esaFile = new File( outputDirectory, finalName + ".esa" );
              zipArchiver.setDestFile(esaFile);
  
  
              zipArchiver.createArchive();
  
              project.getArtifact().setFile( esaFile );
          }
          catch ( Exception e )
          {
              throw new MojoExecutionException( "Error assembling esa", e );
          }
  
      }
  
      public void execute()
          throws MojoExecutionException
      {
          init();
  
          addDependenciesToArchive();
  
          copyEsaSourceFiles();
  
          includeCustomManifest();
  
          generateSubsystemManifest();
  
          addMavenDescriptor();
  
          addBuildDir();
  
          includeSharedResources();
  
          createEsaFile();
      }
  }
  