| /* |
| * 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.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Method; |
| import java.util.*; |
| import java.util.jar.Attributes; |
| import java.util.jar.Manifest; |
| |
| import aQute.bnd.header.OSGiHeader; |
| import aQute.bnd.header.Parameters; |
| import aQute.bnd.osgi.Instruction; |
| import aQute.bnd.osgi.Instructions; |
| import aQute.lib.collections.ExtList; |
| import aQute.libg.generics.Create; |
| 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.handler.manager.ArtifactHandlerManager; |
| import org.apache.maven.execution.MavenSession; |
| import org.apache.maven.model.License; |
| import org.apache.maven.model.Model; |
| import org.apache.maven.model.Resource; |
| 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.MavenProject; |
| import org.apache.maven.project.MavenProjectHelper; |
| 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 aQute.bnd.header.Attrs; |
| 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.Jar; |
| import aQute.bnd.osgi.Packages; |
| import aQute.bnd.osgi.Processor; |
| import aQute.lib.spring.SpringXMLType; |
| |
| |
| /** |
| * 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; |
| |
| @Component |
| private MavenProjectHelper m_projectHelper; |
| |
| @Component |
| private ArchiverManager m_archiverManager; |
| |
| @Component |
| private ArtifactHandlerManager m_artifactHandlerManager; |
| |
| @Component |
| private DependencyGraphBuilder m_dependencyGraphBuilder; |
| |
| /** |
| * Project types which this plugin supports. |
| */ |
| @Parameter |
| protected List<String> supportedProjectTypes = Arrays.asList( new String[] |
| { "jar", "bundle" } ); |
| |
| /** |
| * 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 ) |
| private MavenProject project; |
| |
| /** |
| * The BND instructions for the bundle. |
| */ |
| @Parameter |
| private Map<String, String> instructions = new LinkedHashMap<String, String>(); |
| |
| /** |
| * 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; |
| |
| 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 DependencyNode buildDependencyGraph( MavenProject mavenProject ) throws MojoExecutionException |
| { |
| DependencyNode dependencyGraph; |
| try |
| { |
| dependencyGraph = m_dependencyGraphBuilder.buildDependencyGraph( mavenProject, null ); |
| } |
| catch ( DependencyGraphBuilderException e ) |
| { |
| throw new MojoExecutionException( e.getMessage(), e ); |
| } |
| return dependencyGraph; |
| } |
| |
| /** |
| * @see org.apache.maven.plugin.AbstractMojo#execute() |
| */ |
| public void execute() throws MojoExecutionException |
| { |
| Properties properties = new Properties(); |
| String projectType = getProject().getArtifact().getType(); |
| |
| // ignore unsupported project types, useful when bundleplugin is configured in parent pom |
| if ( !supportedProjectTypes.contains( projectType ) ) |
| { |
| getLog().warn( |
| "Ignoring project type " + projectType + " - supportedProjectTypes = " + supportedProjectTypes ); |
| return; |
| } |
| |
| execute( getProject(), buildDependencyGraph(getProject()), instructions, properties ); |
| } |
| |
| |
| protected void execute( MavenProject currentProject, DependencyNode dependencyGraph, Map<String, String> originalInstructions, Properties properties ) |
| throws MojoExecutionException |
| { |
| try |
| { |
| execute( currentProject, dependencyGraph, originalInstructions, properties, getClasspath( currentProject, dependencyGraph ) ); |
| } |
| catch ( IOException e ) |
| { |
| throw new MojoExecutionException( "Error calculating classpath for project " + currentProject, e ); |
| } |
| } |
| |
| |
| /* transform directives from their XML form to the expected BND syntax (eg. _include becomes -include) */ |
| protected static Map<String, String> transformDirectives( Map<String, String> originalInstructions ) |
| { |
| Map<String, String> transformedInstructions = new LinkedHashMap<String, String>(); |
| for ( Iterator<Map.Entry<String, String>> i = originalInstructions.entrySet().iterator(); i.hasNext(); ) |
| { |
| Map.Entry<String, String> e = i.next(); |
| |
| 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<String> errors = analyzer.getErrors(); |
| List<String> warnings = analyzer.getWarnings(); |
| |
| for ( Iterator<String> w = warnings.iterator(); w.hasNext(); ) |
| { |
| String msg = w.next(); |
| getLog().warn( prefix + " : " + msg ); |
| } |
| |
| boolean hasErrors = false; |
| String fileNotFound = "Input file does not exist: "; |
| for ( Iterator<String> e = errors.iterator(); e.hasNext(); ) |
| { |
| String msg = e.next(); |
| 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( MavenProject currentProject, DependencyNode dependencyGraph, Map<String, String> originalInstructions, Properties properties, |
| Jar[] classpath ) throws MojoExecutionException |
| { |
| try |
| { |
| File jarFile = new File( getBuildDirectory(), getBundleName( currentProject ) ); |
| Builder builder = buildOSGiBundle( currentProject, dependencyGraph, originalInstructions, properties, classpath ); |
| boolean hasErrors = reportErrors( "Bundle " + currentProject.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 = currentProject.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( currentProject, packaging, classifier, jarFile ); |
| } |
| else if ( customClassifier ) |
| { |
| m_projectHelper.attachArtifact( currentProject, jarFile, classifier ); |
| } |
| else if ( customPackaging ) |
| { |
| m_projectHelper.attachArtifact( currentProject, 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 ); |
| } |
| 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<String, String> originalInstructions, Properties properties, |
| Jar[] classpath ) throws Exception |
| { |
| properties.putAll( getDefaultProperties( currentProject ) ); |
| properties.putAll( transformDirectives( originalInstructions ) ); |
| |
| // process overrides from project |
| final Map<String, String> addProps = new HashMap<String, String>(); |
| final Iterator<Map.Entry<Object, Object>> iter = currentProject.getProperties().entrySet().iterator(); |
| while ( iter.hasNext() ) |
| { |
| final Map.Entry<Object, Object> entry = iter.next(); |
| 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 ); |
| final Iterator<String> keyIter = addProps.keySet().iterator(); |
| while ( keyIter.hasNext() ) |
| { |
| Object key = keyIter.next(); |
| 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<String> plugin = new LinkedHashSet<String>(); |
| 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 ) |
| { |
| builder.setClasspath( classpath ); |
| } |
| |
| return builder; |
| } |
| |
| |
| protected static Properties sanitize( Properties properties ) |
| { |
| // convert any non-String keys/values to Strings |
| Properties sanitizedEntries = new Properties(); |
| for ( Iterator<Map.Entry<Object,Object>> itr = properties.entrySet().iterator(); itr.hasNext(); ) |
| { |
| Map.Entry<Object,Object> entry = itr.next(); |
| if ( entry.getKey() instanceof String == false ) |
| { |
| String key = sanitize(entry.getKey()); |
| if ( !properties.containsKey( key ) ) |
| { |
| sanitizedEntries.setProperty( key, sanitize( entry.getValue() ) ); |
| } |
| itr.remove(); |
| } |
| else if ( entry.getValue() instanceof String == false ) |
| { |
| 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, DependencyNode dependencyGraph, Builder builder ) throws Exception |
| { |
| if ( currentProject.getBasedir() != null ) |
| { |
| // update BND instructions to add included Maven resources |
| includeMavenResources(currentProject, builder, getLog()); |
| |
| // 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<Artifact> embeddableArtifacts = getEmbeddableArtifacts( currentProject, dependencyGraph, builder ); |
| new DependencyEmbedder( getLog(), dependencyGraph, embeddableArtifacts ).processHeaders(builder); |
| |
| 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 ); |
| } |
| } |
| } |
| |
| |
| protected Builder buildOSGiBundle( MavenProject currentProject, DependencyNode dependencyGraph, Map<String, String> originalInstructions, Properties properties, |
| Jar[] classpath ) throws Exception |
| { |
| Builder builder = getOSGiBuilder( currentProject, originalInstructions, properties, classpath ); |
| |
| addMavenInstructions( currentProject, dependencyGraph, builder ); |
| |
| builder.build(); |
| |
| mergeMavenManifest(currentProject, dependencyGraph, builder); |
| |
| return builder; |
| } |
| |
| |
| protected static StringBuilder dumpInstructions( Properties properties, StringBuilder buf ) |
| { |
| try |
| { |
| buf.append( "#-----------------------------------------------------------------------" + NL ); |
| Properties stringProperties = new Properties(); |
| for ( Enumeration<String> e = (Enumeration<String>) 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( "#-----------------------------------------------------------------------" + NL ); |
| } |
| catch ( Throwable e ) |
| { |
| // ignore... |
| } |
| return buf; |
| } |
| |
| |
| protected static StringBuilder dumpClasspath( List<Jar> classpath, StringBuilder buf ) |
| { |
| try |
| { |
| buf.append("#-----------------------------------------------------------------------" + NL); |
| buf.append( "-classpath:\\" + NL ); |
| for ( Iterator<Jar> i = classpath.iterator(); i.hasNext(); ) |
| { |
| File path = i.next().getSource(); |
| if ( path != null ) |
| { |
| buf.append( ' ' + path.toString() + ( i.hasNext() ? ",\\" : "" ) + NL ); |
| } |
| } |
| buf.append( "#-----------------------------------------------------------------------" + NL ); |
| } |
| catch ( Throwable e ) |
| { |
| // ignore... |
| } |
| return buf; |
| } |
| |
| |
| protected static StringBuilder dumpManifest( Manifest manifest, StringBuilder buf ) |
| { |
| try |
| { |
| buf.append( "#-----------------------------------------------------------------------" + NL ); |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| ManifestWriter.outputManifest(manifest, out, true); // manifest encoding is UTF8 |
| buf.append( out.toString( "UTF8" ) ); |
| buf.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, DependencyNode dependencyGraph, 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 ).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<ManifestSection> sections = archiveConfig.getManifestSections(); |
| for ( Iterator<ManifestSection> i = sections.iterator(); i.hasNext(); ) |
| { |
| ManifestSection section = i.next(); |
| Attributes attributes = new Attributes(); |
| |
| if ( !section.isManifestEntriesEmpty() ) |
| { |
| Map<String, String> entries = section.getManifestEntries(); |
| for ( Iterator<Map.Entry<String, String>> j = entries.entrySet().iterator(); j.hasNext(); ) |
| { |
| Map.Entry<String, String> 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<Object> 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, dependencyGraph ); |
| |
| Map<String, ? extends Map<String, String>> values = new Analyzer().parseHeader( importPackages ); |
| for ( Map.Entry<String, ? extends Map<String, String>> entry : values.entrySet() ) |
| { |
| String pkg = entry.getKey(); |
| Map<String, String> 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 ); |
| } |
| |
| 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 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<String, Attributes> mergedEntries = mergedManifest.getEntries(); |
| Map<String, Attributes> entries = manifest.getEntries(); |
| for ( Map.Entry<String, Attributes> 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<Instruction> nomatch) { |
| Attributes result = new Attributes(); |
| Map<String, Object> keys = new TreeMap<String, Object>(); |
| for ( Object key : source.keySet() ) |
| { |
| keys.put( key.toString(), key ); |
| } |
| |
| List<Instruction> filters = new ArrayList<Instruction>( instructions.keySet() ); |
| if (nomatch == null) |
| { |
| nomatch = Create.set(); |
| } |
| for ( Instruction instruction : filters ) { |
| boolean match = false; |
| for (Iterator<Map.Entry<String, Object>> i = keys.entrySet().iterator(); i.hasNext();) |
| { |
| Map.Entry<String, Object> 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<Instruction> 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<Object, Object> 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<String, Attrs> 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<String,String> 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<String> 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<String> getOptionalPackages( MavenProject currentProject, DependencyNode dependencyGraph ) throws IOException, MojoExecutionException |
| { |
| ArrayList<Artifact> inscope = new ArrayList<Artifact>(); |
| final Collection<Artifact> artifacts = getSelectedDependencies( dependencyGraph, currentProject.getArtifacts() ); |
| for ( Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); ) |
| { |
| Artifact artifact = it.next(); |
| if ( artifact.getArtifactHandler().isAddedToClasspath() ) |
| { |
| inscope.add( artifact ); |
| } |
| } |
| |
| HashSet<String> optionalArtifactIds = new HashSet<String>(); |
| for ( Iterator<Artifact> 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<String> required = new HashSet<String>(); |
| HashSet<String> optional = new HashSet<String>(); |
| for ( Iterator<Artifact> 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<String> optionalArtifactIds, Artifact artifact ) |
| { |
| List<String> trail = artifact.getDependencyTrail(); |
| for ( Iterator<String> 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<String, String> getProperties( Model projectModel, String prefix ) |
| { |
| Map<String, String> properties = new LinkedHashMap<String, String>(); |
| 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<License> licenses ) |
| { |
| if ( licenses == null || licenses.size() == 0 ) |
| return null; |
| StringBuffer sb = new StringBuffer(); |
| String del = ""; |
| for ( Iterator<License> 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() ); |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| p.store( out, "Generated by org.apache.felix.bundleplugin" ); |
| jar.putResource( path + "/pom.properties", new EmbeddedResource( out.toByteArray(), System.currentTimeMillis() ) ); |
| } |
| |
| |
| protected Jar[] getClasspath( MavenProject currentProject, DependencyNode dependencyGraph ) throws IOException, MojoExecutionException |
| { |
| List<Jar> list = new ArrayList<Jar>(); |
| |
| if ( getOutputDirectory() != null && getOutputDirectory().exists() ) |
| { |
| list.add( new Jar( ".", getOutputDirectory() ) ); |
| } |
| |
| final Collection<Artifact> artifacts = getSelectedDependencies( dependencyGraph, currentProject.getArtifacts() ); |
| for ( Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); ) |
| { |
| Artifact artifact = it.next(); |
| if ( artifact.getArtifactHandler().isAddedToClasspath() ) |
| { |
| File file = getFile( artifact ); |
| if ( file == null ) |
| { |
| getLog().warn( |
| "File is not available for artifact " + artifact + " in project " |
| + currentProject.getArtifact() ); |
| continue; |
| } |
| Jar jar = new Jar( artifact.getArtifactId(), file ); |
| list.add( jar ); |
| } |
| } |
| Jar[] cp = new Jar[list.size()]; |
| list.toArray( cp ); |
| return cp; |
| } |
| |
| |
| private Collection<Artifact> getSelectedDependencies( DependencyNode dependencyGraph, Collection<Artifact> artifacts ) throws MojoExecutionException |
| { |
| if ( null == excludeDependencies || excludeDependencies.length() == 0 ) |
| { |
| return artifacts; |
| } |
| else if ( "true".equalsIgnoreCase( excludeDependencies ) ) |
| { |
| return Collections.emptyList(); |
| } |
| |
| Collection<Artifact> selectedDependencies = new LinkedHashSet<Artifact>( artifacts ); |
| DependencyExcluder excluder = new DependencyExcluder( dependencyGraph, 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<String> i = currentProject.getFilters().iterator(); i.hasNext(); ) |
| { |
| File filterFile = new File( i.next() ); |
| if ( filterFile.isFile() ) |
| { |
| properties.putAll( PropertyUtils.loadProperties( filterFile ) ); |
| } |
| } |
| |
| 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<String> e = (Enumeration<String>) 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, ScrPlugin.class.getName() + "," |
| + BlueprintPlugin.class.getName() + "," |
| + SpringXMLType.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<Resource> getMavenResources( MavenProject currentProject, boolean test ) |
| { |
| List<Resource> resources = new ArrayList<Resource>( test ? currentProject.getTestResources() : currentProject.getResources() ); |
| |
| if ( currentProject.getCompileSourceRoots() != null ) |
| { |
| // also scan for any "packageinfo" files lurking in the source folders |
| final List<String> packageInfoIncludes = Collections.singletonList( "**/packageinfo" ); |
| for ( Iterator<String> 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<String> pathSet = new LinkedHashSet<String>(); |
| for ( Iterator<Resource> 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( ( String[] ) resource.getIncludes().toArray( EMPTY_STRING_ARRAY ) ); |
| } |
| else |
| { |
| scanner.setIncludes( DEFAULT_INCLUDES ); |
| } |
| |
| if ( resource.getExcludes() != null && !resource.getExcludes().isEmpty() ) |
| { |
| scanner.setExcludes( ( String[] ) resource.getExcludes().toArray( EMPTY_STRING_ARRAY ) ); |
| } |
| |
| scanner.addDefaultExcludes(); |
| scanner.scan(); |
| |
| List<String> includedFiles = Arrays.asList( scanner.getIncludedFiles() ); |
| |
| for ( Iterator<String> 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<String> i = pathSet.iterator(); i.hasNext(); ) |
| { |
| resourcePaths.append( i.next() ); |
| if ( i.hasNext() ) |
| { |
| resourcePaths.append( ',' ); |
| } |
| } |
| |
| return resourcePaths.toString(); |
| } |
| |
| |
| protected Collection<Artifact> getEmbeddableArtifacts( MavenProject currentProject, DependencyNode dependencyGraph, Analyzer analyzer ) |
| throws MojoExecutionException |
| { |
| final Collection<Artifact> artifacts; |
| |
| String embedTransitive = analyzer.getProperty( DependencyEmbedder.EMBED_TRANSITIVE ); |
| if ( Boolean.valueOf( embedTransitive ).booleanValue() ) |
| { |
| // includes transitive dependencies |
| artifacts = currentProject.getArtifacts(); |
| } |
| else |
| { |
| // only includes direct dependencies |
| artifacts = currentProject.getDependencyArtifacts(); |
| } |
| |
| return getSelectedDependencies( dependencyGraph, 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<StringBuilder, List<String>> map = new HashMap<StringBuilder, List<String>>(2); |
| map.put(mavenSourcePaths, currentProject.getCompileSourceRoots() ); |
| map.put(mavenTestSourcePaths, currentProject.getTestCompileSourceRoots() ); |
| for ( Map.Entry<StringBuilder, List<String>> entry : map.entrySet() ) |
| { |
| List<String> compileSourceRoots = entry.getValue(); |
| if ( compileSourceRoots != null ) |
| { |
| StringBuilder sourcePaths = entry.getKey(); |
| for ( Iterator<String> 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 sources)" ); |
| } |
| } |
| else if ( mavenSourcePaths.length() > 0 ) |
| { |
| analyzer.setProperty( Analyzer.SOURCEPATH, mavenSourcePaths.toString() ); |
| } |
| } |
| } |