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;
/**
* 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<String>();
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,
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 aQute.lib.osgi.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();
}
}