All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.felix.bundleplugin.BundlePlugin Maven / Gradle / Ivy

Go to download

Provides a maven plugin that supports creating an OSGi bundle from the contents of the compilation classpath along with its resources and dependencies. Plus a zillion other features. The plugin uses the Bnd tool (http://www.aqute.biz/Code/Bnd)

There is a newer version: 5.1.9
Show newest version
/*
 * 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.
 */
package org.apache.felix.bundleplugin;


import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import org.apache.maven.archiver.ManifestSection;
import org.apache.maven.archiver.MavenArchiveConfiguration;
import org.apache.maven.archiver.MavenArchiver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactCollector;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Exclusion;
import org.apache.maven.model.License;
import org.apache.maven.model.Model;
import org.apache.maven.model.Resource;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
import org.apache.maven.shared.dependency.graph.DependencyNode;
import org.apache.maven.shared.osgi.DefaultMaven2OsgiConverter;
import org.apache.maven.shared.osgi.Maven2OsgiConverter;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.PropertyUtils;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.WriterFactory;
import org.sonatype.plexus.build.incremental.BuildContext;

import aQute.bnd.header.Attrs;
import aQute.bnd.header.OSGiHeader;
import aQute.bnd.header.Parameters;
import aQute.bnd.osgi.Analyzer;
import aQute.bnd.osgi.Builder;
import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Descriptors.PackageRef;
import aQute.bnd.osgi.EmbeddedResource;
import aQute.bnd.osgi.FileResource;
import aQute.bnd.osgi.Instruction;
import aQute.bnd.osgi.Instructions;
import aQute.bnd.osgi.Jar;
import aQute.bnd.osgi.Packages;
import aQute.bnd.osgi.Processor;
import aQute.lib.collections.ExtList;
import aQute.lib.spring.SpringXMLType;
import aQute.libg.generics.Create;


/**
 * Create an OSGi bundle from Maven project
 *
 */
@Mojo( name = "bundle", requiresDependencyResolution = ResolutionScope.TEST,
       threadSafe = true,
       defaultPhase = LifecyclePhase.PACKAGE )
public class BundlePlugin extends AbstractMojo
{
    /**
     * Directory where the manifest will be written
     */
    @Parameter( property = "manifestLocation", defaultValue = "${project.build.outputDirectory}/META-INF" )
    protected File manifestLocation;

    /**
     * Output a nicely formatted manifest that still respects the 72 character line limit.
     */
    @Parameter( property = "niceManifest", defaultValue = "false" )
    protected boolean niceManifest;
    /**
     * File where the BND instructions will be dumped
     */
    @Parameter( property = "dumpInstructions" )
    protected File dumpInstructions;

    /**
     * File where the BND class-path will be dumped
     */
    @Parameter( property = "dumpClasspath" )
    protected File dumpClasspath;

    /**
     * When true, unpack the bundle contents to the outputDirectory
     */
    @Parameter( property = "unpackBundle" )
    protected boolean unpackBundle;

    /**
     * Comma separated list of artifactIds to exclude from the dependency classpath passed to BND (use "true" to exclude everything)
     */
    @Parameter( property = "excludeDependencies" )
    protected String excludeDependencies;

    /**
     * Final name of the bundle (without classifier or extension)
     */
    @Parameter( defaultValue = "${project.build.finalName}")
    private String finalName;

    /**
     * Classifier type of the bundle to be installed.  For example, "jdk14".
     * Defaults to none which means this is the project's main bundle.
     */
    @Parameter
    protected String classifier;

    /**
     * Packaging type of the bundle to be installed.  For example, "jar".
     * Defaults to none which means use the same packaging as the project.
     */
    @Parameter
    protected String packaging;

    /**
     * If true, remove any inlined or embedded dependencies from the resulting pom.
     */
    @Parameter
    protected boolean createDependencyReducedPom;

    /**
     * Where to put the dependency reduced pom. Note: setting a value for this parameter with a directory other than
     * ${basedir} will change the value of ${basedir} for all executions that come after the shade execution. This is
     * often not what you want. This is considered an open issue with this plugin.
     */
    @Parameter( defaultValue = "${basedir}/dependency-reduced-pom.xml" )
    protected File dependencyReducedPomLocation;

    /**
     * Directory where the SCR files will be written
     */
    @Parameter(defaultValue="${project.build.outputDirectory}")
    protected File scrLocation;

    /**
     * When true, dump the generated SCR files
     */
    @Parameter
    protected boolean exportScr;
    
    @Component
    private MavenProjectHelper m_projectHelper;

    @Component
    private ArchiverManager m_archiverManager;

    @Component
    private ArtifactHandlerManager m_artifactHandlerManager;

    /* The current Maven session.  */
    @Parameter( defaultValue = "${session}", readonly = true )
    protected MavenSession session;


    /**
     * ProjectBuilder, needed to create projects from the artifacts.
     */
    @Component
    protected MavenProjectBuilder mavenProjectBuilder;

    @Component
    protected DependencyGraphBuilder dependencyGraphBuilder;

    @Component
    private ArtifactMetadataSource artifactMetadataSource;

    @Component
    private ArtifactCollector artifactCollector;

    @Component
    protected ArtifactFactory artifactFactory;

    /**
     * Artifact resolver, needed to download source jars for inclusion in classpath.
     */
    @Component
    protected ArtifactResolver artifactResolver;


    /**
     * Local maven repository.
     */
    @Parameter( readonly = true, required = true, defaultValue = "${localRepository}" )
    protected ArtifactRepository localRepository;

    /**
     * Remote repositories which will be searched for source attachments.
     */
    @Parameter( readonly = true, required = true, defaultValue = "${project.remoteArtifactRepositories}" )
    protected List remoteArtifactRepositories;



    /**
     * Project types which this plugin supports.
     */
    @Parameter
    protected List supportedProjectTypes = Arrays.asList("jar", "bundle");

    /**
     * Project types which are not supported, but silently ignored.
     */
    @Parameter
    protected List noWarningProjectTypes = Collections.emptyList();

    /**
     * The directory for the generated bundles.
     */
    @Parameter( defaultValue = "${project.build.outputDirectory}" )
    private File outputDirectory;

    /**
     * The directory for the generated JAR.
     */
    @Parameter( defaultValue = "${project.build.directory}" )
    private String buildDirectory;

    /**
     * The Maven project.
     */
    @Parameter( defaultValue = "${project}", readonly = true, required = true )
    protected MavenProject project;

    /**
     * The BND instructions for the bundle.
     * Maven will expand property macros in these values. If you want to use a BND macro, you must double the dollar sign
     * for the plugin to pass it to BND correctly. For example: 
* {@code <_consumer-policy>$${range;[===,+)}}{@code } */ @Parameter private Map instructions = new LinkedHashMap(); /** * Use locally patched version for now. */ private final Maven2OsgiConverter m_maven2OsgiConverter = new DefaultMaven2OsgiConverter(); /** * The archive configuration to use. */ @Parameter private MavenArchiveConfiguration archive; // accessed indirectly in JarPluginConfiguration @Parameter( defaultValue = "${session}", readonly = true, required = true ) private MavenSession m_mavenSession; @Component protected BuildContext buildContext; private static final String MAVEN_SYMBOLICNAME = "maven-symbolicname"; private static final String MAVEN_RESOURCES = "{maven-resources}"; private static final String MAVEN_TEST_RESOURCES = "{maven-test-resources}"; private static final String LOCAL_PACKAGES = "{local-packages}"; private static final String MAVEN_SOURCES = "{maven-sources}"; private static final String MAVEN_TEST_SOURCES = "{maven-test-sources}"; private static final String BUNDLE_PLUGIN_EXTENSION = "BNDExtension-"; private static final String BUNDLE_PLUGIN_PREPEND_EXTENSION = "BNDPrependExtension-"; private static final String[] EMPTY_STRING_ARRAY = {}; private static final String[] DEFAULT_INCLUDES = { "**/**" }; private static final String NL = System.getProperty( "line.separator" ); protected Maven2OsgiConverter getMaven2OsgiConverter() { return m_maven2OsgiConverter; } protected MavenProject getProject() { return project; } protected Map getInstructions() { return instructions; } /** * @see org.apache.maven.plugin.AbstractMojo#execute() */ public void execute() throws MojoExecutionException { String projectType = getProject().getArtifact().getType(); // ignore unsupported project types, useful when bundleplugin is configured in parent pom if ( !supportedProjectTypes.contains( projectType ) ) { if (!noWarningProjectTypes.contains( projectType ) ) { getLog().warn( "Ignoring project type " + projectType + " - supportedProjectTypes = " + supportedProjectTypes); } return; } try { execute( instructions, getClasspath( project) ); } catch ( IOException e ) { throw new MojoExecutionException( "Error calculating classpath for project " + project, e ); } } /* transform directives from their XML form to the expected BND syntax (eg. _include becomes -include) */ protected static Map transformDirectives( Map originalInstructions ) { Map transformedInstructions = new LinkedHashMap(); for ( Map.Entry e : originalInstructions.entrySet() ) { String key = e.getKey(); if ( key.startsWith( "_" ) ) { key = "-" + key.substring( 1 ); } String value = e.getValue(); if ( null == value ) { value = ""; } else { value = value.replaceAll( "\\p{Blank}*[\r\n]\\p{Blank}*", "" ); } if ( Analyzer.WAB.equals( key ) && value.length() == 0 ) { // provide useful default value = "src/main/webapp/"; } transformedInstructions.put( key, value ); } return transformedInstructions; } protected boolean reportErrors( String prefix, Analyzer analyzer ) { List errors = analyzer.getErrors(); List warnings = analyzer.getWarnings(); for ( String msg : warnings ) { getLog().warn( prefix + " : " + msg ); } boolean hasErrors = false; String fileNotFound = "Input file does not exist: "; for ( String msg : errors ) { if ( msg.startsWith(fileNotFound) && msg.endsWith( "~" ) ) { // treat as warning; this error happens when you have duplicate entries in Include-Resource String duplicate = Processor.removeDuplicateMarker( msg.substring( fileNotFound.length() ) ); getLog().warn( prefix + " : Duplicate path '" + duplicate + "' in Include-Resource" ); } else { getLog().error( prefix + " : " + msg ); hasErrors = true; } } return hasErrors; } protected void execute(Map originalInstructions, ClassPathItem[] classpath) throws MojoExecutionException { try { File jarFile = new File( getBuildDirectory(), getBundleName( project ) ); Builder builder = buildOSGiBundle( project, originalInstructions, classpath ); boolean hasErrors = reportErrors( "Bundle " + project.getArtifact(), builder ); if ( hasErrors ) { String failok = builder.getProperty( "-failok" ); if ( null == failok || "false".equalsIgnoreCase( failok ) ) { jarFile.delete(); throw new MojoFailureException( "Error(s) found in bundle configuration" ); } } // attach bundle to maven project jarFile.getParentFile().mkdirs(); builder.getJar().write( jarFile ); Artifact mainArtifact = project.getArtifact(); if ( "bundle".equals( mainArtifact.getType() ) ) { // workaround for MNG-1682: force maven to install artifact using the "jar" handler mainArtifact.setArtifactHandler( m_artifactHandlerManager.getArtifactHandler( "jar" ) ); } boolean customClassifier = null != classifier && classifier.trim().length() > 0; boolean customPackaging = null != packaging && packaging.trim().length() > 0; if ( customClassifier && customPackaging ) { m_projectHelper.attachArtifact( project, packaging, classifier, jarFile ); } else if ( customClassifier ) { m_projectHelper.attachArtifact( project, jarFile, classifier ); } else if ( customPackaging ) { m_projectHelper.attachArtifact( project, packaging, jarFile ); } else { mainArtifact.setFile( jarFile ); } if ( unpackBundle ) { unpackBundle( jarFile ); } if ( manifestLocation != null ) { File outputFile = new File( manifestLocation, "MANIFEST.MF" ); try { ManifestPlugin.writeManifest( builder, outputFile, niceManifest, exportScr, scrLocation, buildContext, getLog() ); } catch ( IOException e ) { getLog().error( "Error trying to write Manifest to file " + outputFile, e ); } } // cleanup... builder.close(); } catch ( MojoFailureException e ) { getLog().error( e.getLocalizedMessage() ); throw new MojoExecutionException( "Error(s) found in bundle configuration", e ); } catch ( Exception e ) { getLog().error( "An internal error occurred", e ); throw new MojoExecutionException( "Internal error in maven-bundle-plugin", e ); } } protected Builder getOSGiBuilder( MavenProject currentProject, Map originalInstructions, ClassPathItem[] classpath ) throws Exception { Properties properties = new Properties(); properties.putAll( getDefaultProperties( currentProject ) ); properties.putAll( transformDirectives( originalInstructions ) ); // process overrides from project final Map addProps = new HashMap<>(); for ( Entry entry : currentProject.getProperties().entrySet() ) { final String key = entry.getKey().toString(); if ( key.startsWith(BUNDLE_PLUGIN_EXTENSION) ) { final String oKey = key.substring(BUNDLE_PLUGIN_EXTENSION.length()); final String currentValue = properties.getProperty(oKey); if ( currentValue == null ) { addProps.put(oKey, entry.getValue().toString()); } else { addProps.put(oKey, currentValue + ',' + entry.getValue()); } } if ( key.startsWith(BUNDLE_PLUGIN_PREPEND_EXTENSION) ) { final String oKey = key.substring(BUNDLE_PLUGIN_PREPEND_EXTENSION.length()); final String currentValue = properties.getProperty(oKey); if ( currentValue == null ) { addProps.put(oKey, entry.getValue().toString()); } else { addProps.put(oKey, entry.getValue() + "," + currentValue); } } } properties.putAll( addProps ); for ( String key : addProps.keySet() ) { properties.remove(BUNDLE_PLUGIN_EXTENSION + key); properties.remove(BUNDLE_PLUGIN_PREPEND_EXTENSION + key); } if (properties.getProperty("Bundle-Activator") != null && properties.getProperty("Bundle-Activator").isEmpty()) { properties.remove("Bundle-Activator"); } if (properties.containsKey("-disable-plugin")) { String[] disabled = properties.remove("-disable-plugin").toString().replaceAll(" ", "").split(","); String[] enabled = properties.getProperty(Analyzer.PLUGIN, "").replaceAll(" ", "").split(","); Set plugin = new LinkedHashSet<>(); plugin.addAll(Arrays.asList(enabled)); plugin.removeAll(Arrays.asList(disabled)); StringBuilder sb = new StringBuilder(); for (String s : plugin) { if (sb.length() > 0) { sb.append(","); } sb.append(s); } properties.setProperty(Analyzer.PLUGIN, sb.toString()); } Builder builder = new Builder(); synchronized ( BundlePlugin.class ) // protect setBase...getBndLastModified which uses static DateFormat { builder.setBase( getBase( currentProject ) ); } builder.setProperties( sanitize( properties ) ); if ( classpath != null ) { List jars = new ArrayList<>(); for ( int i = 0; i < classpath.length; i++ ) { if ( classpath[i].file.exists() ) { jars.add( new Jar( classpath[i].id, classpath[i].file ) ); } } builder.setClasspath( jars ); } return builder; } protected static Properties sanitize( Properties properties ) { // convert any non-String keys/values to Strings Properties sanitizedEntries = new Properties(); for ( Iterator> itr = properties.entrySet().iterator(); itr.hasNext(); ) { Map.Entry entry = itr.next(); if ( !(entry.getKey() instanceof String) ) { String key = sanitize(entry.getKey()); if ( !properties.containsKey( key ) ) { sanitizedEntries.setProperty( key, sanitize( entry.getValue() ) ); } itr.remove(); } else if ( !(entry.getValue() instanceof String) ) { entry.setValue( sanitize( entry.getValue() ) ); } } properties.putAll( sanitizedEntries ); return properties; } protected static String sanitize( Object value ) { if ( value instanceof String ) { return ( String ) value; } else if ( value instanceof Iterable ) { String delim = ""; StringBuilder buf = new StringBuilder(); for ( Object i : ( Iterable ) value ) { buf.append( delim ).append( i ); delim = ", "; } return buf.toString(); } else if ( value.getClass().isArray() ) { String delim = ""; StringBuilder buf = new StringBuilder(); for ( int i = 0, len = Array.getLength( value ); i < len; i++ ) { buf.append( delim ).append( Array.get( value, i ) ); delim = ", "; } return buf.toString(); } else { return String.valueOf( value ); } } protected void addMavenInstructions(MavenProject currentProject, Builder builder) throws Exception { if ( currentProject.getBasedir() != null ) { // update BND instructions to add included Maven resources includeMavenResources(currentProject, builder, getLog()); // Fixup error messages includeJava9Fixups(currentProject, builder); // calculate default export/private settings based on sources addLocalPackages(outputDirectory, builder); // tell BND where the current project source resides addMavenSourcePath(currentProject, builder, getLog()); } // update BND instructions to embed selected Maven dependencies Collection embeddableArtifacts = getEmbeddableArtifacts( currentProject, builder ); DependencyEmbedder dependencyEmbedder = new DependencyEmbedder(getLog(), embeddableArtifacts); dependencyEmbedder.processHeaders(builder); Collection embeddedArtifacts = dependencyEmbedder.getEmbeddedArtifacts(); if ( !embeddedArtifacts.isEmpty() && createDependencyReducedPom ) { Set embeddedIds = new HashSet(); for ( Artifact artifact : embeddedArtifacts ) { embeddedIds.add( getId( artifact ) ); } createDependencyReducedPom( embeddedIds ); } if ( dumpInstructions != null || getLog().isDebugEnabled() ) { StringBuilder buf = new StringBuilder(); getLog().debug( "BND Instructions:" + NL + dumpInstructions( builder.getProperties(), buf ) ); if ( dumpInstructions != null ) { getLog().info( "Writing BND instructions to " + dumpInstructions ); dumpInstructions.getParentFile().mkdirs(); FileUtils.fileWrite( dumpInstructions, "# BND instructions" + NL + buf ); } } if ( dumpClasspath != null || getLog().isDebugEnabled() ) { StringBuilder buf = new StringBuilder(); getLog().debug("BND Classpath:" + NL + dumpClasspath(builder.getClasspath(), buf)); if ( dumpClasspath != null ) { getLog().info( "Writing BND classpath to " + dumpClasspath ); dumpClasspath.getParentFile().mkdirs(); FileUtils.fileWrite( dumpClasspath, "# BND classpath" + NL + buf ); } } } // We need to find the direct dependencies that have been included in the uber JAR so that we can modify the // POM accordingly. private void createDependencyReducedPom( Set artifactsToRemove ) throws IOException, ProjectBuildingException, DependencyGraphBuilderException { Model model = project.getOriginalModel(); List dependencies = new ArrayList(); boolean modified = false; List transitiveDeps = new ArrayList(); for ( Artifact artifact : project.getArtifacts() ) { if ( "pom".equals( artifact.getType() ) ) { // don't include pom type dependencies in dependency reduced pom continue; } //promote Dependency dep = new Dependency(); dep.setArtifactId( artifact.getArtifactId() ); if ( artifact.hasClassifier() ) { dep.setClassifier( artifact.getClassifier() ); } dep.setGroupId( artifact.getGroupId() ); dep.setOptional( artifact.isOptional() ); dep.setScope( artifact.getScope() ); dep.setType( artifact.getType() ); dep.setVersion( artifact.getVersion() ); //we'll figure out the exclusions in a bit. transitiveDeps.add( dep ); } List origDeps = project.getDependencies(); for (Dependency d : origDeps) { dependencies.add(d); String id = getId(d); if (artifactsToRemove.contains(id)) { modified = true; dependencies.remove(d); } } // Check to see if we have a reduction and if so rewrite the POM. if ( modified ) { while ( modified ) { model.setDependencies( dependencies ); if ( dependencyReducedPomLocation == null ) { // MSHADE-123: We can't default to 'target' because it messes up uses of ${project.basedir} dependencyReducedPomLocation = new File ( project.getBasedir(), "dependency-reduced-pom.xml" ); } File f = dependencyReducedPomLocation; if ( f.exists() ) { f.delete(); } Writer w = WriterFactory.newXmlWriter( f ); String origRelativePath = null; String replaceRelativePath = null; if ( model.getParent() != null) { origRelativePath = model.getParent().getRelativePath(); } replaceRelativePath = origRelativePath; if ( origRelativePath == null ) { origRelativePath = "../pom.xml"; } if ( model.getParent() != null ) { File parentFile = new File( project.getBasedir(), model.getParent().getRelativePath() ).getCanonicalFile(); if ( !parentFile.isFile() ) { parentFile = new File( parentFile, "pom.xml"); } parentFile = parentFile.getCanonicalFile(); String relPath = RelativizePath.convertToRelativePath( parentFile, f ); model.getParent().setRelativePath( relPath ); } try { new MavenXpp3Writer().write( w, model ); } finally { if ( model.getParent() != null ) { model.getParent().setRelativePath( replaceRelativePath ); } w.close(); } MavenProject p2 = mavenProjectBuilder.build( f, localRepository, null ); modified = updateExcludesInDeps( p2, dependencies, transitiveDeps ); } project.setFile( dependencyReducedPomLocation ); } } private String getId( Artifact artifact ) { return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getType(), artifact.getClassifier() ); } private String getId( Dependency dependency ) { return getId( dependency.getGroupId(), dependency.getArtifactId(), dependency.getType(), dependency.getClassifier() ); } private String getId( String groupId, String artifactId, String type, String classifier ) { return groupId + ":" + artifactId + ":" + type + ":" + ( ( classifier != null ) ? classifier : "" ); } public boolean updateExcludesInDeps( MavenProject project, List dependencies, List transitiveDeps ) throws DependencyGraphBuilderException { ProjectBuildingRequest request = new DefaultProjectBuildingRequest(); request.setProject(project); request.setRepositorySession(session.getRepositorySession()); DependencyNode node = dependencyGraphBuilder.buildDependencyGraph(request, null); boolean modified = false; for (DependencyNode n2 : node.getChildren()) { for (DependencyNode n3 : n2.getChildren()) { //anything two levels deep that is marked "included" //is stuff that was excluded by the original poms, make sure it //remains excluded IF promoting transitives. if (true) { //check if it really isn't in the list of original dependencies. Maven //prior to 2.0.8 may grab versions from transients instead of //from the direct deps in which case they would be marked included //instead of OMITTED_FOR_DUPLICATE //also, if not promoting the transitives, level 2's would be included boolean found = false; for (Dependency dep : transitiveDeps) { if (dep.getArtifactId().equals(n3.getArtifact().getArtifactId()) && dep.getGroupId().equals(n3.getArtifact().getGroupId())) { found = true; } } if (!found) { for (Dependency dep : dependencies) { if (dep.getArtifactId().equals(n2.getArtifact().getArtifactId()) && dep.getGroupId().equals(n2.getArtifact().getGroupId())) { Exclusion exclusion = new Exclusion(); exclusion.setArtifactId(n3.getArtifact().getArtifactId()); exclusion.setGroupId(n3.getArtifact().getGroupId()); dep.addExclusion(exclusion); modified = true; break; } } } } } } return modified; } protected Builder buildOSGiBundle(MavenProject currentProject, Map originalInstructions, ClassPathItem[] classpath) throws Exception { Builder builder = getOSGiBuilder( currentProject, originalInstructions, classpath ); addMavenInstructions( currentProject, builder ); builder.build(); mergeMavenManifest(currentProject, builder); return builder; } protected static StringBuilder dumpInstructions( Properties properties, StringBuilder buf ) { try { buf.append( "#-----------------------------------------------------------------------" + NL ); Properties stringProperties = new Properties(); for ( Enumeration e = (Enumeration) properties.propertyNames(); e.hasMoreElements(); ) { // we can only store String properties String key = e.nextElement(); String value = properties.getProperty( key ); if ( value != null ) { stringProperties.setProperty( key, value ); } } ByteArrayOutputStream out = new ByteArrayOutputStream(); stringProperties.store( out, null ); // properties encoding is 8859_1 buf.append( out.toString( "8859_1" ) ); buf.append("#-----------------------------------------------------------------------").append(NL); } catch ( Throwable e ) { // ignore... } return buf; } protected static StringBuilder dumpClasspath( List classpath, StringBuilder buf ) { try { buf.append("#-----------------------------------------------------------------------").append(NL); buf.append("-classpath:\\").append(NL); for ( Iterator i = classpath.iterator(); i.hasNext(); ) { File path = i.next().getSource(); if ( path != null ) { buf.append(' ').append(path.toString()).append(i.hasNext() ? ",\\" : "").append(NL); } } buf.append("#-----------------------------------------------------------------------").append(NL); } catch ( Throwable e ) { // ignore... } return buf; } protected static StringBuilder dumpManifest( Manifest manifest, StringBuilder buf ) { try { buf.append("#-----------------------------------------------------------------------").append(NL); ByteArrayOutputStream out = new ByteArrayOutputStream(); ManifestWriter.outputManifest(manifest, out, true); // manifest encoding is UTF8 buf.append( out.toString( "UTF8" ) ); buf.append("#-----------------------------------------------------------------------").append(NL); } catch ( Throwable e ) { // ignore... } return buf; } protected static void includeMavenResources( MavenProject currentProject, Analyzer analyzer, Log log ) { // pass maven resource paths onto BND analyzer final String mavenResourcePaths = getMavenResourcePaths( currentProject, false ); final String mavenTestResourcePaths = getMavenResourcePaths( currentProject, true ); final String includeResource = analyzer.getProperty( Analyzer.INCLUDE_RESOURCE ); if ( includeResource != null ) { if ( includeResource.contains( MAVEN_RESOURCES ) || includeResource.contains( MAVEN_TEST_RESOURCES ) ) { String combinedResource = StringUtils.replace( includeResource, MAVEN_RESOURCES, mavenResourcePaths ); combinedResource = StringUtils.replace( combinedResource, MAVEN_TEST_RESOURCES, mavenTestResourcePaths ); if ( combinedResource.length() > 0 ) { analyzer.setProperty( Analyzer.INCLUDE_RESOURCE, combinedResource ); } else { analyzer.unsetProperty( Analyzer.INCLUDE_RESOURCE ); } } else if ( mavenResourcePaths.length() > 0 ) { log.warn( Analyzer.INCLUDE_RESOURCE + ": overriding " + mavenResourcePaths + " with " + includeResource + " (add " + MAVEN_RESOURCES + " if you want to include the maven resources)" ); } } else if ( mavenResourcePaths.length() > 0 ) { analyzer.setProperty( Analyzer.INCLUDE_RESOURCE, mavenResourcePaths ); } } protected void mergeMavenManifest(MavenProject currentProject, Builder builder) throws Exception { Jar jar = builder.getJar(); if ( getLog().isDebugEnabled() ) { getLog().debug( "BND Manifest:" + NL + dumpManifest( jar.getManifest(), new StringBuilder() ) ); } boolean addMavenDescriptor = currentProject.getBasedir() != null; try { /* * Grab customized manifest entries from the maven-jar-plugin configuration */ MavenArchiveConfiguration archiveConfig = JarPluginConfiguration.getArchiveConfiguration( currentProject ); String mavenManifestText = new MavenArchiver().getManifest( currentProject, archiveConfig.getManifest() ).toString(); addMavenDescriptor = addMavenDescriptor && archiveConfig.isAddMavenDescriptor(); Manifest mavenManifest = new Manifest(); // First grab the external manifest file (if specified and different to target location) File externalManifestFile = archiveConfig.getManifestFile(); if ( null != externalManifestFile ) { if ( !externalManifestFile.isAbsolute() ) { externalManifestFile = new File( currentProject.getBasedir(), externalManifestFile.getPath() ); } if ( externalManifestFile.exists() && !externalManifestFile.equals( new File( manifestLocation, "MANIFEST.MF" ) ) ) { InputStream mis = new FileInputStream( externalManifestFile ); mavenManifest.read( mis ); mis.close(); } } // Then apply customized entries from the jar plugin; note: manifest encoding is UTF8 mavenManifest.read( new ByteArrayInputStream( mavenManifestText.getBytes( "UTF8" ) ) ); if ( !archiveConfig.isManifestSectionsEmpty() ) { /* * Add customized manifest sections (for some reason MavenArchiver doesn't do this for us) */ List sections = archiveConfig.getManifestSections(); for ( Iterator i = sections.iterator(); i.hasNext(); ) { ManifestSection section = i.next(); Attributes attributes = new Attributes(); if ( !section.isManifestEntriesEmpty() ) { Map entries = section.getManifestEntries(); for ( Iterator> j = entries.entrySet().iterator(); j.hasNext(); ) { Map.Entry entry = j.next(); attributes.putValue( entry.getKey(), entry.getValue() ); } } mavenManifest.getEntries().put( section.getName(), attributes ); } } Attributes mainMavenAttributes = mavenManifest.getMainAttributes(); mainMavenAttributes.putValue( "Created-By", "Apache Maven Bundle Plugin" ); String[] removeHeaders = builder.getProperty( Constants.REMOVEHEADERS, "" ).split( "," ); // apply -removeheaders to the custom manifest for ( int i = 0; i < removeHeaders.length; i++ ) { for ( Iterator j = mainMavenAttributes.keySet().iterator(); j.hasNext(); ) { if ( j.next().toString().matches( removeHeaders[i].trim() ) ) { j.remove(); } } } /* * Overlay generated bundle manifest with customized entries */ Properties properties = builder.getProperties(); Manifest bundleManifest = jar.getManifest(); if ( properties.containsKey( "Merge-Headers" ) ) { Instructions instructions = new Instructions( ExtList.from(builder.getProperty("Merge-Headers")) ); mergeManifest( instructions, bundleManifest, mavenManifest ); } else { bundleManifest.getMainAttributes().putAll( mainMavenAttributes ); bundleManifest.getEntries().putAll( mavenManifest.getEntries() ); } // adjust the import package attributes so that optional dependencies use // optional resolution. String importPackages = bundleManifest.getMainAttributes().getValue( "Import-Package" ); if ( importPackages != null ) { Set optionalPackages = getOptionalPackages( currentProject); Map> values; try (Analyzer analyzer = new Analyzer()) { values = analyzer.parseHeader( importPackages ); } for ( Map.Entry> entry : values.entrySet() ) { String pkg = entry.getKey(); Map options = entry.getValue(); if ( !options.containsKey( "resolution:" ) && optionalPackages.contains( pkg ) ) { options.put( "resolution:", "optional" ); } } String result = Processor.printClauses( values ); bundleManifest.getMainAttributes().putValue( "Import-Package", result ); } for ( String header : Arrays.asList( Constants.IMPORT_PACKAGE, Constants.DYNAMICIMPORT_PACKAGE, Constants.EXPORT_PACKAGE, Constants.PRIVATE_PACKAGE, Constants.PROVIDE_CAPABILITY, Constants.REQUIRE_CAPABILITY, "Bundle-Blueprint", "Service-Component" ) ) { reformatClauses( bundleManifest.getMainAttributes(), header ); } jar.setManifest( bundleManifest ); } catch ( Exception e ) { getLog().warn( "Unable to merge Maven manifest: " + e.getLocalizedMessage() ); } if ( addMavenDescriptor ) { doMavenMetadata( currentProject, jar ); } if ( getLog().isDebugEnabled() ) { getLog().debug( "Final Manifest:" + NL + dumpManifest( jar.getManifest(), new StringBuilder() ) ); } builder.setJar( jar ); } protected static void reformatClauses( Attributes attributes, String name ) { String header = attributes.getValue( name ); if ( header != null ) { Map> params = OSGiHeader.parseHeader( header, null ).toBasic(); Map> sorted = new TreeMap<>(); for ( Map.Entry> entry : params.entrySet() ) { String key = entry.getKey(); Map attrs = entry.getValue(); Map newAttrs = new TreeMap<>( Comparator.comparing( s -> !s.endsWith( ":" ) ).thenComparing( s -> s ) ); newAttrs.putAll( attrs ); sorted.put( key, newAttrs ); } String nh = new Parameters( sorted ).toString(); attributes.putValue( name, nh ); } } protected static void mergeManifest( Instructions instructions, Manifest... manifests ) throws IOException { for ( int i = manifests.length - 2; i >= 0; i-- ) { Manifest mergedManifest = manifests[i]; Manifest manifest = manifests[i + 1]; Attributes mergedMainAttributes = mergedManifest.getMainAttributes(); Attributes mainAttributes = manifest.getMainAttributes(); Attributes filteredMainAttributes = filterAttributes( instructions, mainAttributes, null ); if ( !filteredMainAttributes.isEmpty() ) { mergeAttributes( mergedMainAttributes, filteredMainAttributes ); } Map mergedEntries = mergedManifest.getEntries(); Map entries = manifest.getEntries(); for ( Map.Entry entry : entries.entrySet() ) { String name = entry.getKey(); Attributes attributes = entry.getValue(); Attributes filteredAttributes = filterAttributes( instructions, attributes, null ); if ( !filteredAttributes.isEmpty() ) { Attributes mergedAttributes = mergedManifest.getAttributes( name ); if ( mergedAttributes != null) { mergeAttributes(mergedAttributes, filteredAttributes); } else { mergedEntries.put(name, filteredAttributes); } } } } } /** * @see Analyzer#filter */ private static Attributes filterAttributes(Instructions instructions, Attributes source, Set nomatch) { Attributes result = new Attributes(); Map keys = new TreeMap(); for ( Object key : source.keySet() ) { keys.put( key.toString(), key ); } List filters = new ArrayList( instructions.keySet() ); if (nomatch == null) { nomatch = Create.set(); } for ( Instruction instruction : filters ) { boolean match = false; for (Iterator> i = keys.entrySet().iterator(); i.hasNext();) { Map.Entry entry = i.next(); String key = entry.getKey(); if ( instruction.matches( key ) ) { match = true; if (!instruction.isNegated()) { Object name = entry.getValue(); Object value = source.get( name ); result.put( name, value ); } i.remove(); // Can never match again for another pattern } } if (!match && !instruction.isAny()) nomatch.add(instruction); } /* * Tricky. If we have umatched instructions they might indicate that we * want to have multiple decorators for the same package. So we check * the unmatched against the result list. If then then match and have * actually interesting properties then we merge them */ for (Iterator i = nomatch.iterator(); i.hasNext();) { Instruction instruction = i.next(); // We assume the user knows what he is // doing and inserted a literal. So // we ignore any not matched literals // #252, we should not be negated to make it a constant if (instruction.isLiteral() && !instruction.isNegated()) { Object key = keys.get( instruction.getLiteral() ); if ( key != null ) { Object value = source.get( key ); result.put( key, value ); } i.remove(); continue; } // Not matching a negated instruction looks // like an error ... Though so, but // in the second phase of Export-Package // the !package will never match anymore. if (instruction.isNegated()) { i.remove(); continue; } // An optional instruction should not generate // an error if (instruction.isOptional()) { i.remove(); continue; } } return result; } private static void mergeAttributes( Attributes... attributesArray ) throws IOException { for ( int i = attributesArray.length - 2; i >= 0; i-- ) { Attributes mergedAttributes = attributesArray[i]; Attributes attributes = attributesArray[i + 1]; for ( Map.Entry entry : attributes.entrySet() ) { Object name = entry.getKey(); String value = (String) entry.getValue(); String oldValue = (String) mergedAttributes.put( name, value ); if ( oldValue != null ) { Parameters mergedClauses = OSGiHeader.parseHeader(oldValue); Parameters clauses = OSGiHeader.parseHeader( value ); if ( !mergedClauses.isEqual( clauses) ) { for ( Map.Entry clauseEntry : clauses.entrySet() ) { String clause = clauseEntry.getKey(); Attrs attrs = clauseEntry.getValue(); Attrs mergedAttrs = mergedClauses.get( clause ); if ( mergedAttrs == null) { mergedClauses.put( clause, attrs ); } else if ( !mergedAttrs.isEqual(attrs) ) { for ( Map.Entry adentry : attrs.entrySet() ) { String adname = adentry.getKey(); String ad = adentry.getValue(); if ( mergedAttrs.containsKey( adname ) ) { Attrs.Type type = attrs.getType( adname ); switch (type) { case VERSIONS: case STRINGS: case LONGS: case DOUBLES: ExtList mergedAd = ExtList.from( mergedAttrs.get( adname ) ); ExtList.from( ad ).addAll( ExtList.from( ad ) ); mergedAttrs.put(adname, mergedAd.join() ); break; } } else { mergedAttrs.put( adname, ad ); } } } } mergedAttributes.put( name, Processor.printClauses( mergedClauses ) ); } } } } } protected Set getOptionalPackages(MavenProject currentProject) throws IOException, MojoExecutionException { ArrayList inscope = new ArrayList(); final Collection artifacts = getSelectedDependencies(currentProject.getArtifacts() ); for ( Iterator it = artifacts.iterator(); it.hasNext(); ) { Artifact artifact = it.next(); if ( artifact.getArtifactHandler().isAddedToClasspath() ) { inscope.add( artifact ); } } HashSet optionalArtifactIds = new HashSet(); for ( Iterator it = inscope.iterator(); it.hasNext(); ) { Artifact artifact = it.next(); if ( artifact.isOptional() ) { String id = artifact.toString(); if ( artifact.getScope() != null ) { // strip the scope... id = id.replaceFirst( ":[^:]*$", "" ); } optionalArtifactIds.add( id ); } } HashSet required = new HashSet(); HashSet optional = new HashSet(); for ( Iterator it = inscope.iterator(); it.hasNext(); ) { Artifact artifact = it.next(); File file = getFile( artifact ); if ( file == null ) { continue; } Jar jar = new Jar( artifact.getArtifactId(), file ); if ( isTransitivelyOptional( optionalArtifactIds, artifact ) ) { optional.addAll( jar.getPackages() ); } else { required.addAll( jar.getPackages() ); } jar.close(); } optional.removeAll( required ); return optional; } /** * Check to see if any dependency along the dependency trail of * the artifact is optional. * * @param artifact */ protected boolean isTransitivelyOptional( HashSet optionalArtifactIds, Artifact artifact ) { List trail = artifact.getDependencyTrail(); for ( Iterator iterator = trail.iterator(); iterator.hasNext(); ) { String next = iterator.next(); if ( optionalArtifactIds.contains( next ) ) { return true; } } return false; } private void unpackBundle( File jarFile ) { File outputDir = getOutputDirectory(); if ( null == outputDir ) { outputDir = new File( getBuildDirectory(), "classes" ); } try { /* * this directory must exist before unpacking, otherwise the plexus * unarchiver decides to use the current working directory instead! */ if ( !outputDir.exists() ) { outputDir.mkdirs(); } UnArchiver unArchiver = m_archiverManager.getUnArchiver( "jar" ); unArchiver.setDestDirectory( outputDir ); unArchiver.setSourceFile( jarFile ); unArchiver.extract(); } catch ( Exception e ) { getLog().error( "Problem unpacking " + jarFile + " to " + outputDir, e ); } } protected static String removeTagFromInstruction( String instruction, String tag ) { StringBuffer buf = new StringBuffer(); String[] clauses = instruction.split( "," ); for ( int i = 0; i < clauses.length; i++ ) { String clause = clauses[i].trim(); if ( !tag.equals( clause ) ) { if ( buf.length() > 0 ) { buf.append( ',' ); } buf.append( clause ); } } return buf.toString(); } private static Map getProperties( Model projectModel, String prefix ) { Map properties = new LinkedHashMap(); Method methods[] = Model.class.getDeclaredMethods(); for ( int i = 0; i < methods.length; i++ ) { String name = methods[i].getName(); if ( name.startsWith( "get" ) ) { try { Object v = methods[i].invoke( projectModel, null ); if ( v != null ) { name = prefix + Character.toLowerCase( name.charAt( 3 ) ) + name.substring( 4 ); if ( v.getClass().isArray() ) properties.put( name, Arrays.asList( ( Object[] ) v ).toString() ); else properties.put( name, v.toString() ); } } catch ( Exception e ) { // too bad } } } return properties; } private static StringBuffer printLicenses( List licenses ) { if ( licenses == null || licenses.size() == 0 ) return null; StringBuffer sb = new StringBuffer(); String del = ""; for ( Iterator i = licenses.iterator(); i.hasNext(); ) { License l = i.next(); String url = l.getUrl(); if ( url == null ) continue; sb.append( del ); sb.append( url ); del = ", "; } if ( sb.length() == 0 ) return null; return sb; } /** * @param jar * @throws IOException */ private void doMavenMetadata( MavenProject currentProject, Jar jar ) throws IOException { String path = "META-INF/maven/" + currentProject.getGroupId() + "/" + currentProject.getArtifactId(); File pomFile = currentProject.getFile(); if ( pomFile == null || !pomFile.exists() ) { pomFile = new File( currentProject.getBasedir(), "pom.xml" ); } if ( pomFile.exists() ) { jar.putResource( path + "/pom.xml", new FileResource( pomFile ) ); } Properties p = new Properties(); p.put( "version", currentProject.getVersion() ); p.put( "groupId", currentProject.getGroupId() ); p.put( "artifactId", currentProject.getArtifactId() ); jar.putResource( path + "/pom.properties", new EmbeddedResource( toFileContentAsBytes( p ), System.currentTimeMillis() ) ); } private byte[] toFileContentAsBytes( Properties properties ) { byte[] bytes; try { StringWriter sw = new StringWriter(); properties.store( sw, null ); BufferedReader r = new BufferedReader( new StringReader( sw.toString() ) ); StringWriter stringWriter = new StringWriter(); String line; while ( ( line = r.readLine() ) != null ) { if ( !line.startsWith( "#" ) ) { stringWriter.append( line ).append( NL ); } } bytes = stringWriter.getBuffer().toString().getBytes( StandardCharsets.ISO_8859_1 ); r.close(); sw.close(); stringWriter.close(); } catch ( IOException e ) { getLog().error( "Error while converting properties to file content. Returning empty array.", e ); bytes = new byte[] {}; } return bytes; } protected ClassPathItem[] getClasspath(MavenProject currentProject) throws IOException, MojoExecutionException { List list = new ArrayList( currentProject.getArtifacts().size() + 1 ); String d = currentProject.getBuild() != null ? currentProject.getBuild().getOutputDirectory() : null; if ( d != null ) { list.add( new ClassPathItem( ".", new File( d ) ) ); } final Collection artifacts = getSelectedDependencies(currentProject.getArtifacts() ); for ( Artifact artifact : artifacts ) { if ( artifact.getArtifactHandler().isAddedToClasspath() && !Artifact.SCOPE_TEST.equals( artifact.getScope() ) ) { File file = getFile( artifact ); if ( file == null ) { getLog().warn( "File is not available for artifact " + artifact + " in project " + currentProject.getArtifact() ); continue; } ClassPathItem jar = new ClassPathItem( artifact.getArtifactId(), file ); list.add( jar ); } } ClassPathItem[] cp = new ClassPathItem[list.size()]; list.toArray( cp ); return cp; } private Collection getSelectedDependencies(Collection artifacts) throws MojoExecutionException { if ( null == excludeDependencies || excludeDependencies.length() == 0 ) { return artifacts; } else if ( "true".equalsIgnoreCase( excludeDependencies ) ) { return Collections.emptyList(); } Collection selectedDependencies = new LinkedHashSet( artifacts ); DependencyExcluder excluder = new DependencyExcluder(artifacts ); excluder.processHeaders( excludeDependencies ); selectedDependencies.removeAll( excluder.getExcludedArtifacts() ); return selectedDependencies; } /** * Get the file for an Artifact * * @param artifact */ protected File getFile( Artifact artifact ) { return artifact.getFile(); } private static void header( Properties properties, String key, Object value ) { if ( value == null ) return; if ( value instanceof Collection && ( ( Collection ) value ).isEmpty() ) return; properties.put( key, value.toString().replaceAll( "[\r\n]", "" ) ); } /** * Convert a Maven version into an OSGi compliant version * * @param version Maven version * @return the OSGi version */ protected String convertVersionToOsgi( String version ) { return getMaven2OsgiConverter().getVersion( version ); } /** * TODO this should return getMaven2Osgi().getBundleFileName( project.getArtifact() ) */ protected String getBundleName( MavenProject currentProject ) { String extension; try { extension = currentProject.getArtifact().getArtifactHandler().getExtension(); } catch ( Throwable e ) { extension = currentProject.getArtifact().getType(); } if ( StringUtils.isEmpty( extension ) || "bundle".equals( extension ) || "pom".equals( extension ) ) { extension = "jar"; // just in case maven gets confused } if ( null != classifier && classifier.trim().length() > 0 ) { return finalName + '-' + classifier + '.' + extension; } return finalName + '.' + extension; } protected String getBuildDirectory() { return buildDirectory; } protected void setBuildDirectory( String _buildirectory ) { buildDirectory = _buildirectory; } protected Properties getDefaultProperties( MavenProject currentProject ) { Properties properties = new Properties(); String bsn; try { bsn = getMaven2OsgiConverter().getBundleSymbolicName( currentProject.getArtifact() ); } catch ( Exception e ) { bsn = currentProject.getGroupId() + "." + currentProject.getArtifactId(); } // Setup defaults properties.put( MAVEN_SYMBOLICNAME, bsn ); properties.put( Analyzer.BUNDLE_SYMBOLICNAME, bsn ); properties.put( Analyzer.IMPORT_PACKAGE, "*" ); properties.put( Analyzer.BUNDLE_VERSION, getMaven2OsgiConverter().getVersion( currentProject.getVersion() ) ); // remove the extraneous Include-Resource and Private-Package entries from generated manifest properties.put( Constants.REMOVEHEADERS, Analyzer.INCLUDE_RESOURCE + ',' + Analyzer.PRIVATE_PACKAGE ); header( properties, Analyzer.BUNDLE_DESCRIPTION, currentProject.getDescription() ); StringBuffer licenseText = printLicenses( currentProject.getLicenses() ); if ( licenseText != null ) { header( properties, Analyzer.BUNDLE_LICENSE, licenseText ); } header( properties, Analyzer.BUNDLE_NAME, currentProject.getName() ); if ( currentProject.getOrganization() != null ) { if ( currentProject.getOrganization().getName() != null ) { String organizationName = currentProject.getOrganization().getName(); header( properties, Analyzer.BUNDLE_VENDOR, organizationName ); properties.put( "project.organization.name", organizationName ); properties.put( "pom.organization.name", organizationName ); } if ( currentProject.getOrganization().getUrl() != null ) { String organizationUrl = currentProject.getOrganization().getUrl(); header( properties, Analyzer.BUNDLE_DOCURL, organizationUrl ); properties.put( "project.organization.url", organizationUrl ); properties.put( "pom.organization.url", organizationUrl ); } } properties.putAll( currentProject.getProperties() ); properties.putAll( currentProject.getModel().getProperties() ); for ( Iterator i = currentProject.getFilters().iterator(); i.hasNext(); ) { File filterFile = new File( i.next() ); if ( filterFile.isFile() ) { try { properties.putAll( PropertyUtils.loadProperties( filterFile ) ); } catch ( IOException e ) { // Ignore } } } if ( m_mavenSession != null ) { try { // don't pass upper-case session settings to bnd as they end up in the manifest Properties sessionProperties = m_mavenSession.getExecutionProperties(); for ( Enumeration e = (Enumeration) sessionProperties.propertyNames(); e.hasMoreElements(); ) { String key = e.nextElement(); if ( key.length() > 0 && !Character.isUpperCase( key.charAt( 0 ) ) ) { properties.put( key, sessionProperties.getProperty( key ) ); } } } catch ( Exception e ) { getLog().warn( "Problem with Maven session properties: " + e.getLocalizedMessage() ); } } properties.putAll( getProperties( currentProject.getModel(), "project.build." ) ); properties.putAll( getProperties( currentProject.getModel(), "pom." ) ); properties.putAll( getProperties( currentProject.getModel(), "project." ) ); properties.put( "project.baseDir", getBase( currentProject ) ); properties.put( "project.build.directory", getBuildDirectory() ); properties.put( "project.build.outputdirectory", getOutputDirectory() ); properties.put( "classifier", classifier == null ? "" : classifier ); // Add default plugins header( properties, Analyzer.PLUGIN, BlueprintPlugin.class.getName() + "," + SpringXMLType.class.getName() + "," + JpaPlugin.class.getName() ); return properties; } protected static File getBase( MavenProject currentProject ) { return currentProject.getBasedir() != null ? currentProject.getBasedir() : new File( "" ); } protected File getOutputDirectory() { return outputDirectory; } protected void setOutputDirectory( File _outputDirectory ) { outputDirectory = _outputDirectory; } private static void addLocalPackages( File outputDirectory, Analyzer analyzer ) throws IOException { Packages packages = new Packages(); if ( outputDirectory != null && outputDirectory.isDirectory() ) { // scan classes directory for potential packages DirectoryScanner scanner = new DirectoryScanner(); scanner.setBasedir( outputDirectory ); scanner.setIncludes( new String[] { "**/*.class" } ); scanner.addDefaultExcludes(); scanner.scan(); String[] paths = scanner.getIncludedFiles(); for ( int i = 0; i < paths.length; i++ ) { packages.put( analyzer.getPackageRef( getPackageName( paths[i] ) ) ); } } Packages exportedPkgs = new Packages(); Packages privatePkgs = new Packages(); boolean noprivatePackages = "!*".equals( analyzer.getProperty( Analyzer.PRIVATE_PACKAGE ) ); for ( PackageRef pkg : packages.keySet() ) { // mark all source packages as private by default (can be overridden by export list) privatePkgs.put( pkg ); // we can't export the default package (".") and we shouldn't export internal packages String fqn = pkg.getFQN(); if ( noprivatePackages || !( ".".equals( fqn ) || fqn.contains( ".internal" ) || fqn.contains( ".impl" ) ) ) { exportedPkgs.put( pkg ); } } Properties properties = analyzer.getProperties(); String exported = properties.getProperty( Analyzer.EXPORT_PACKAGE ); if ( exported == null ) { if ( !properties.containsKey( Analyzer.EXPORT_CONTENTS ) ) { // no -exportcontents overriding the exports, so use our computed list for ( Attrs attrs : exportedPkgs.values() ) { attrs.put( Constants.SPLIT_PACKAGE_DIRECTIVE, "merge-first" ); } properties.setProperty( Analyzer.EXPORT_PACKAGE, Processor.printClauses( exportedPkgs ) ); } else { // leave Export-Package empty (but non-null) as we have -exportcontents properties.setProperty( Analyzer.EXPORT_PACKAGE, "" ); } } else if ( exported.indexOf( LOCAL_PACKAGES ) >= 0 ) { String newExported = StringUtils.replace( exported, LOCAL_PACKAGES, Processor.printClauses( exportedPkgs ) ); properties.setProperty( Analyzer.EXPORT_PACKAGE, newExported ); } String internal = properties.getProperty( Analyzer.PRIVATE_PACKAGE ); if ( internal == null ) { if ( !privatePkgs.isEmpty() ) { for ( Attrs attrs : privatePkgs.values() ) { attrs.put( Constants.SPLIT_PACKAGE_DIRECTIVE, "merge-first" ); } properties.setProperty( Analyzer.PRIVATE_PACKAGE, Processor.printClauses( privatePkgs ) ); } else { // if there are really no private packages then use "!*" as this will keep the Bnd Tool happy properties.setProperty( Analyzer.PRIVATE_PACKAGE, "!*" ); } } else if ( internal.indexOf( LOCAL_PACKAGES ) >= 0 ) { String newInternal = StringUtils.replace( internal, LOCAL_PACKAGES, Processor.printClauses( privatePkgs ) ); properties.setProperty( Analyzer.PRIVATE_PACKAGE, newInternal ); } } private static String getPackageName( String filename ) { int n = filename.lastIndexOf( File.separatorChar ); return n < 0 ? "." : filename.substring( 0, n ).replace( File.separatorChar, '.' ); } private static List getMavenResources( MavenProject currentProject, boolean test ) { List resources = new ArrayList( test ? currentProject.getTestResources() : currentProject.getResources() ); if ( currentProject.getCompileSourceRoots() != null ) { // also scan for any "packageinfo" files lurking in the source folders final List packageInfoIncludes = Collections.singletonList( "**/packageinfo" ); for ( Iterator i = currentProject.getCompileSourceRoots().iterator(); i.hasNext(); ) { String sourceRoot = i.next(); Resource packageInfoResource = new Resource(); packageInfoResource.setDirectory( sourceRoot ); packageInfoResource.setIncludes( packageInfoIncludes ); resources.add( packageInfoResource ); } } return resources; } protected static String getMavenResourcePaths( MavenProject currentProject, boolean test ) { final String basePath = currentProject.getBasedir().getAbsolutePath(); Set pathSet = new LinkedHashSet(); for ( Iterator i = getMavenResources( currentProject, test ).iterator(); i.hasNext(); ) { Resource resource = i.next(); final String sourcePath = resource.getDirectory(); final String targetPath = resource.getTargetPath(); // ignore empty or non-local resources if ( new File( sourcePath ).exists() && ( ( targetPath == null ) || ( targetPath.indexOf( ".." ) < 0 ) ) ) { DirectoryScanner scanner = new DirectoryScanner(); scanner.setBasedir( sourcePath ); if ( resource.getIncludes() != null && !resource.getIncludes().isEmpty() ) { scanner.setIncludes( resource.getIncludes().toArray( EMPTY_STRING_ARRAY ) ); } else { scanner.setIncludes( DEFAULT_INCLUDES ); } if ( resource.getExcludes() != null && !resource.getExcludes().isEmpty() ) { scanner.setExcludes( resource.getExcludes().toArray( EMPTY_STRING_ARRAY ) ); } scanner.addDefaultExcludes(); scanner.scan(); List includedFiles = Arrays.asList( scanner.getIncludedFiles() ); for ( Iterator j = includedFiles.iterator(); j.hasNext(); ) { String name = j.next(); String path = sourcePath + '/' + name; // make relative to project if ( path.startsWith( basePath ) ) { if ( path.length() == basePath.length() ) { path = "."; } else { path = path.substring( basePath.length() + 1 ); } } // replace windows backslash with a slash // this is a workaround for a problem with bnd 0.0.189 if ( File.separatorChar != '/' ) { name = name.replace( File.separatorChar, '/' ); path = path.replace( File.separatorChar, '/' ); } // copy to correct place path = name + '=' + path; if ( targetPath != null ) { path = targetPath + '/' + path; } // use Bnd filtering? if ( resource.isFiltering() ) { path = '{' + path + '}'; } pathSet.add( path ); } } } StringBuffer resourcePaths = new StringBuffer(); for ( Iterator i = pathSet.iterator(); i.hasNext(); ) { resourcePaths.append( i.next() ); if ( i.hasNext() ) { resourcePaths.append( ',' ); } } return resourcePaths.toString(); } protected Collection getEmbeddableArtifacts(MavenProject currentProject, Analyzer analyzer) throws MojoExecutionException { final Collection artifacts; String embedTransitive = analyzer.getProperty( DependencyEmbedder.EMBED_TRANSITIVE ); if (Boolean.valueOf(embedTransitive)) { // includes transitive dependencies artifacts = currentProject.getArtifacts(); } else { // only includes direct dependencies artifacts = currentProject.getDependencyArtifacts(); } return getSelectedDependencies(artifacts ); } protected static void addMavenSourcePath( MavenProject currentProject, Analyzer analyzer, Log log ) { // pass maven source paths onto BND analyzer StringBuilder mavenSourcePaths = new StringBuilder(); StringBuilder mavenTestSourcePaths = new StringBuilder(); Map> map = new HashMap>(2); map.put(mavenSourcePaths, currentProject.getCompileSourceRoots() ); map.put(mavenTestSourcePaths, currentProject.getTestCompileSourceRoots() ); for ( Map.Entry> entry : map.entrySet() ) { List compileSourceRoots = entry.getValue(); if ( compileSourceRoots != null ) { StringBuilder sourcePaths = entry.getKey(); for ( Iterator i = compileSourceRoots.iterator(); i.hasNext(); ) { if ( sourcePaths.length() > 0 ) { sourcePaths.append( ',' ); } sourcePaths.append( i.next() ); } } } final String sourcePath = analyzer.getProperty( Analyzer.SOURCEPATH ); if ( sourcePath != null ) { if ( sourcePath.contains(MAVEN_SOURCES) || sourcePath.contains(MAVEN_TEST_RESOURCES) ) { String combinedSource = StringUtils.replace( sourcePath, MAVEN_SOURCES, mavenSourcePaths.toString() ); combinedSource = StringUtils.replace( combinedSource, MAVEN_TEST_SOURCES, mavenTestSourcePaths.toString() ); if ( combinedSource.length() > 0 ) { analyzer.setProperty( Analyzer.SOURCEPATH, combinedSource ); } else { analyzer.unsetProperty( Analyzer.SOURCEPATH ); } } else if ( mavenSourcePaths.length() > 0 ) { log.warn( Analyzer.SOURCEPATH + ": overriding " + mavenSourcePaths + " with " + sourcePath + " (add " + MAVEN_SOURCES + " if you want to include the maven sources)" ); } else if ( mavenTestSourcePaths.length() > 0 ) { log.warn( Analyzer.SOURCEPATH + ": overriding " + mavenTestSourcePaths + " with " + sourcePath + " (add " + MAVEN_TEST_SOURCES + " if you want to include the maven test sources)" ); } } else if ( mavenSourcePaths.length() > 0 ) { analyzer.setProperty( Analyzer.SOURCEPATH, mavenSourcePaths.toString() ); } } /** * Downgrade the message "Classes found in the wrong directory" to a warning. This allows the plugin * to process a multi-release JAR (see JEP 238, http://openjdk.java.net/jeps/238). * * Note that the version-specific paths will NOT be visible at runtime nor processed by bnd for * imported packages etc. This will not be possible until a runtime solution for multi-release * JARs exists in OSGi. This fix only allows these JARs to be processed at all and to be usable on * Java 8 (and below), and also on Java 9 where the version-specific customizations are optional. */ protected static void includeJava9Fixups(MavenProject currentProject, Analyzer analyzer) { final String classesInWrongDirError = "Classes found in the wrong directory"; final String newFixup = "Classes found in the wrong directory;" + Analyzer.FIXUPMESSAGES_IS_DIRECTIVE + "=" + Analyzer.FIXUPMESSAGES_IS_WARNING; String fixups = analyzer.getProperty(Analyzer.FIXUPMESSAGES); if (fixups != null && !fixups.isEmpty()) { if (!fixups.contains(classesInWrongDirError)) { fixups += "," + newFixup; } } else { fixups = newFixup; } analyzer.setProperty(Analyzer.FIXUPMESSAGES, fixups); } static class ClassPathItem { final String id; final File file; public ClassPathItem(String id, File file) { this.id = id; this.file = file; } } }