package org.apache.aries.plugin.eba; /* * 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 org.apache.maven.archiver.PomPropertiesUtil; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter; 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 org.apache.maven.shared.osgi.DefaultMaven2OsgiConverter; import org.apache.maven.shared.osgi.Maven2OsgiConverter; import aQute.lib.osgi.Analyzer; import java.io.File; import java.io.IOException; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; /** * Builds Aries Enterprise Bundle Archive (eba) files. * * @version $Id: $ * @goal eba * @phase package * @requiresDependencyResolution test */ public class EbaMojo extends AbstractMojo { public static final String APPLICATION_MF_URI = "META-INF/APPLICATION.MF"; private static final String[] DEFAULT_INCLUDES = {"**/**"}; /** * Application manifest headers */ private static final String MANIFEST_VERSION = "Manifest-Version"; private static final String APPLICATION_MANIFESTVERSION = "Application-ManifestVersion"; private static final String APPLICATION_SYMBOLICNAME = "Application-SymbolicName"; private static final String APPLICATION_VERSION = "Application-Version"; private static final String APPLICATION_NAME = "Application-Name"; private static final String APPLICATION_DESCRIPTION = "Application-Description"; private static final String APPLICATION_CONTENT = "Application-Content"; private static final String APPLICATION_EXPORTSERVICE = "Application-ExportService"; private static final String APPLICATION_IMPORTSERVICE = "Application-ImportService"; private static final String APPLICATION_USEBUNDLE = "Use-Bundle"; /** * Coverter for maven pom values to OSGi manifest values (pulled in from the maven-bundle-plugin) */ private Maven2OsgiConverter maven2OsgiConverter = new DefaultMaven2OsgiConverter(); /** * Single directory for extra files to include in the eba. * * @parameter expression="${basedir}/src/main/eba" * @required */ private File ebaSourceDirectory; /** * The location of the APPLICATION.MF file to be used within the eba file. * * @parameter expression="${basedir}/src/main/eba/META-INF/APPLICATION.MF" */ private File applicationManifestFile; /** * Specify if the generated jar file of this project should be * included in the eba file ; default is true. * * @parameter */ private Boolean includeJar = Boolean.TRUE; /** * The location of the manifest file to be used within the eba file. * * @parameter expression="${basedir}/src/main/eba/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 eba. * * @parameter expression="${project.build.directory}" * @required */ private String outputDirectory; /** * The name of the eba file to generate. * * @parameter alias="ebaName" 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; /** * Whether to follow transitive dependencies or use explicit dependencies. * * @parameter expression="${useTransitiveDependencies}" default-value="false" */ private boolean useTransitiveDependencies; /** * Define which bundles to include in the archive. * none - no bundles are included * applicationContent - direct dependencies go into the content * all - direct and transitive dependencies go into the content * * @parameter expression="${archiveContent}" default-value="applicationContent" */ private String archiveContent; private File buildDir; public void execute() throws MojoExecutionException { getLog().debug( " ======= EbaMojo settings =======" ); getLog().debug( "ebaSourceDirectory[" + ebaSourceDirectory + "]" ); getLog().debug( "manifestFile[" + manifestFile + "]" ); getLog().debug( "applicationManifestFile[" + applicationManifestFile + "]" ); getLog().debug( "workDirectory[" + workDirectory + "]" ); getLog().debug( "outputDirectory[" + outputDirectory + "]" ); getLog().debug( "finalName[" + finalName + "]" ); getLog().debug( "generateManifest[" + generateManifest + "]" ); if (archiveContent == null) { archiveContent = new String("applicationContent"); } getLog().debug( "archiveContent[" + archiveContent + "]" ); getLog().info( "archiveContent[" + archiveContent + "]" ); zipArchiver.setIncludeEmptyDirs( includeEmptyDirs ); zipArchiver.setCompress( true ); zipArchiver.setForced( forceCreation ); // Check if jar file is there and if requested, copy it try { if (includeJar.booleanValue()) { File generatedJarFile = new File( outputDirectory, finalName + ".jar" ); if (generatedJarFile.exists()) { getLog().info( "Including generated jar file["+generatedJarFile.getName()+"]"); zipArchiver.addFile(generatedJarFile, finalName + ".jar"); } } } catch ( ArchiverException e ) { throw new MojoExecutionException( "Error adding generated Jar file", e ); } // Copy dependencies try { Set<Artifact> artifacts = null; if (useTransitiveDependencies || "all".equals(archiveContent)) { // if use transitive is set (i.e. true) then we need to make sure archiveContent does not contradict (i.e. is set // to the same compatible value or is the default). if ("none".equals(archiveContent)) { throw new MojoExecutionException("<useTransitiveDependencies/> and <archiveContent/> incompatibly configured. <useTransitiveDependencies/> is deprecated in favor of <archiveContent/>." ); } else { artifacts = project.getArtifacts(); } } else { // check that archiveContent is compatible if ("applicationContent".equals(archiveContent)) { artifacts = project.getDependencyArtifacts(); } else { // the only remaining options should be applicationContent="none" getLog().info("archiveContent=none: application arvhive will not contain any bundles."); } } if (artifacts != null) { for (Artifact artifact : artifacts) { ScopeArtifactFilter filter = new ScopeArtifactFilter(Artifact.SCOPE_RUNTIME); 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())); } } } } catch ( ArchiverException e ) { throw new MojoExecutionException( "Error copying EBA dependencies", e ); } // Copy source files try { File ebaSourceDir = ebaSourceDirectory; if ( ebaSourceDir.exists() ) { getLog().info( "Copy eba resources to " + getBuildDir().getAbsolutePath() ); DirectoryScanner scanner = new DirectoryScanner(); scanner.setBasedir( ebaSourceDir.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( ebaSourceDir, files[j] ); FileUtils.copyFileToDirectory( file, targetFile.getParentFile() ); } } } catch ( Exception e ) { throw new MojoExecutionException( "Error copying EBA resources", e ); } // Include custom manifest if necessary try { if (!generateManifest) { includeCustomApplicationManifestFile(); } } catch ( IOException e ) { throw new MojoExecutionException( "Error copying APPLICATION.MF file", e ); } // Generate application manifest if requested if (generateManifest) { String fileName = new String(getBuildDir() + "/" + APPLICATION_MF_URI); File appMfFile = new File(fileName); try { // Delete any old manifest if (appMfFile.exists()) { FileUtils.fileDelete(fileName); } appMfFile.getParentFile().mkdirs(); if (appMfFile.createNewFile()) { writeApplicationManifest(fileName); } } catch (java.io.IOException e) { throw new MojoExecutionException( "Error generating APPLICATION.MF file: " + fileName, e); } } // Check if connector deployment descriptor is there File ddFile = new File( getBuildDir(), APPLICATION_MF_URI); if ( !ddFile.exists() ) { getLog().warn( "Application manifest: " + ddFile.getAbsolutePath() + " does not exist." ); } 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); } File ebaFile = new File( outputDirectory, finalName + ".eba" ); zipArchiver.setDestFile(ebaFile); File buildDir = getBuildDir(); if (buildDir.isDirectory()) { zipArchiver.addDirectory(buildDir); } //include legal files if any File sharedResourcesDir = new File(sharedResources); if (sharedResourcesDir.isDirectory()) { zipArchiver.addDirectory(sharedResourcesDir); } zipArchiver.createArchive(); project.getArtifact().setFile( ebaFile ); } catch ( Exception e ) { throw new MojoExecutionException( "Error assembling eba", e ); } } private void writeApplicationManifest(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, MANIFEST_VERSION + ": " + "1" + "\n"); FileUtils.fileAppend(fileName, APPLICATION_MANIFESTVERSION + ": " + "1" + "\n"); FileUtils.fileAppend(fileName, APPLICATION_SYMBOLICNAME + ": " + getApplicationSymbolicName(project.getArtifact()) + "\n"); FileUtils.fileAppend(fileName, APPLICATION_VERSION + ": " + getApplicationVersion() + "\n"); FileUtils.fileAppend(fileName, APPLICATION_NAME + ": " + project.getName() + "\n"); FileUtils.fileAppend(fileName, APPLICATION_DESCRIPTION + ": " + project.getDescription() + "\n"); // Write the APPLICATION-CONTENT // TODO: check that the dependencies are bundles (currently, the converter // will throw an exception) Set<Artifact> artifacts; if (useTransitiveDependencies) { artifacts = project.getArtifacts(); } else { artifacts = project.getDependencyArtifacts(); } artifacts = selectArtifacts(artifacts); Iterator<Artifact> iter = artifacts.iterator(); FileUtils.fileAppend(fileName, APPLICATION_CONTENT + ": "); if (iter.hasNext()) { Artifact artifact = iter.next(); FileUtils.fileAppend(fileName, maven2OsgiConverter .getBundleSymbolicName(artifact) + ";version=\"" + Analyzer.cleanupVersion(artifact.getVersion()) // + maven2OsgiConverter.getVersion(artifact.getVersion()) + "\""); } while (iter.hasNext()) { Artifact artifact = iter.next(); FileUtils.fileAppend(fileName, ",\n " + maven2OsgiConverter.getBundleSymbolicName(artifact) + ";version=\"" + Analyzer.cleanupVersion(artifact.getVersion()) // + maven2OsgiConverter.getVersion(artifact.getVersion()) + "\""); } FileUtils.fileAppend(fileName, "\n"); // Add any service imports or exports if (instructions.containsKey(APPLICATION_EXPORTSERVICE)) { FileUtils.fileAppend(fileName, APPLICATION_EXPORTSERVICE + ": " + instructions.get(APPLICATION_EXPORTSERVICE) + "\n"); } if (instructions.containsKey(APPLICATION_IMPORTSERVICE)) { FileUtils.fileAppend(fileName, APPLICATION_IMPORTSERVICE + ": " + instructions.get(APPLICATION_IMPORTSERVICE) + "\n"); } if (instructions.containsKey(APPLICATION_USEBUNDLE)) { FileUtils.fileAppend(fileName, APPLICATION_USEBUNDLE + ": " + instructions.get(APPLICATION_USEBUNDLE) + "\n"); } // Add any use bundle entry } catch (Exception e) { throw new MojoExecutionException( "Error writing dependencies into APPLICATION.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 getApplicationSymbolicName(Artifact artifact) { if (instructions.containsKey(APPLICATION_SYMBOLICNAME)) { return instructions.get(APPLICATION_SYMBOLICNAME).toString(); } return artifact.getGroupId() + "." + artifact.getArtifactId(); } private String getApplicationVersion() { if (instructions.containsKey(APPLICATION_VERSION)) { return instructions.get(APPLICATION_VERSION).toString(); } return aQute.lib.osgi.Analyzer.cleanupVersion(project.getVersion()); } protected File getBuildDir() { if ( buildDir == null ) { buildDir = new File( workDirectory ); } return buildDir; } private void includeCustomApplicationManifestFile() throws IOException { if (applicationManifestFile == null) { throw new NullPointerException("Application manifest file location not set. Use <generateManifest>true</generateManifest> if you want it to be generated."); } File appMfFile = applicationManifestFile; if (appMfFile.exists()) { getLog().info( "Using APPLICATION.MF "+ applicationManifestFile); File metaInfDir = new File(getBuildDir(), "META-INF"); FileUtils.copyFileToDirectory( appMfFile, metaInfDir); } } /** * Return artifacts in 'compile' or 'runtime' scope only. */ private Set<Artifact> selectArtifacts(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)) { selected.add(artifact); } } return selected; } }