| /* |
| * 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.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<ArtifactRepository> remoteArtifactRepositories; |
| |
| |
| |
| /** |
| * Project types which this plugin supports. |
| */ |
| @Parameter |
| protected List<String> supportedProjectTypes = Arrays.asList("jar", "bundle"); |
| |
| /** |
| * Project types which are not supported, but silently ignored. |
| */ |
| @Parameter |
| protected List<String> 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: <br> |
| * {@code <_consumer-policy>$${range;[===,+)}<code>}<code>{@code </_consumer-policy> } |
| */ |
| @Parameter |
| private Map<String, String> 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; |
| |
| /** |
| * Timestamp for reproducible output archive entries, either formatted as ISO 8601 |
| * <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> or as an int representing seconds since the epoch (like |
| * <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>). |
| * |
| * @since 5.1.3 |
| */ |
| @Parameter( defaultValue = "${project.build.outputTimestamp}" ) |
| private String outputTimestamp; |
| |
| @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<String, String> 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<String, String> transformDirectives( Map<String, String> originalInstructions ) |
| { |
| Map<String, String> transformedInstructions = new LinkedHashMap<>(); |
| for ( Map.Entry<String, String> 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.isEmpty() ) |
| { |
| // 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 ( 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<String, String> 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 |
| getLog().info("Building bundle: " + jarFile.getPath()); |
| 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<String, String> originalInstructions, |
| ClassPathItem[] classpath ) throws Exception |
| { |
| Properties properties = new Properties(); |
| properties.putAll( getDefaultProperties( currentProject ) ); |
| properties.putAll( transformDirectives( originalInstructions ) ); |
| |
| if ( new MavenArchiver().parseOutputTimestamp( outputTimestamp ) != null ) |
| { |
| properties.put( Constants.REPRODUCIBLE, "true" ); |
| } |
| |
| // process overrides from project |
| final Map<String, String> addProps = new HashMap<>(); |
| for ( Entry<Object, Object> 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<String> 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<Jar> 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<Map.Entry<Object,Object>> itr = properties.entrySet().iterator(); itr.hasNext(); ) |
| { |
| Map.Entry<Object,Object> 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<Artifact> embeddableArtifacts = getEmbeddableArtifacts( currentProject, builder ); |
| DependencyEmbedder dependencyEmbedder = new DependencyEmbedder(getLog(), embeddableArtifacts); |
| dependencyEmbedder.processHeaders(builder); |
| |
| Collection<Artifact> embeddedArtifacts = dependencyEmbedder.getEmbeddedArtifacts(); |
| if ( !embeddedArtifacts.isEmpty() && createDependencyReducedPom ) |
| { |
| Set<String> 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<String> artifactsToRemove ) |
| throws IOException, ProjectBuildingException, DependencyGraphBuilderException { |
| Model model = project.getOriginalModel(); |
| List<Dependency> dependencies = new ArrayList<>(); |
| |
| boolean modified = false; |
| |
| List<Dependency> 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<Dependency> 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<Dependency> dependencies, List<Dependency> 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<String, String> 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<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("#-----------------------------------------------------------------------").append(NL); |
| } |
| catch ( Throwable e ) |
| { |
| // ignore... |
| } |
| return buf; |
| } |
| |
| |
| protected static StringBuilder dumpClasspath( List<Jar> classpath, StringBuilder buf ) |
| { |
| try |
| { |
| buf.append("#-----------------------------------------------------------------------").append(NL); |
| buf.append("-classpath:\\").append(NL); |
| for ( Iterator<Jar> 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( session, 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", createdBy( "Apache Maven Bundle Plugin", "org.apache.felix", |
| "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); |
| |
| Map<String, ? extends Map<String, String>> values; |
| try (Analyzer analyzer = new Analyzer()) { |
| values = 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 ); |
| } |
| |
| private static String getCreatedByVersion( String groupId, String artifactId ) |
| { |
| try |
| { |
| final Properties properties = PropertyUtils.loadProperties( MavenArchiver.class.getResourceAsStream( |
| "/META-INF/maven/" + groupId + "/" + artifactId + "/pom.properties" ) ); |
| |
| return properties.getProperty( "version" ); |
| } |
| catch ( IOException ioe ) |
| { |
| return null; |
| } |
| } |
| |
| private String createdBy( String description, String groupId, String artifactId ) |
| { |
| String createdBy = description; |
| String version = getCreatedByVersion( groupId, artifactId ); |
| if ( version != null ) |
| { |
| createdBy += " " + version; |
| } |
| return createdBy; |
| } |
| |
| 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<>(); |
| for ( Object key : source.keySet() ) |
| { |
| keys.put( key.toString(), key ); |
| } |
| |
| List<Instruction> filters = new ArrayList<>( 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) throws IOException, MojoExecutionException |
| { |
| ArrayList<Artifact> inscope = new ArrayList<>(); |
| final Collection<Artifact> artifacts = getSelectedDependencies(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<>(); |
| 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<>(); |
| HashSet<String> optional = new HashSet<>(); |
| 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<>(); |
| 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() ); |
| 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<ClassPathItem> list = new ArrayList<>( currentProject.getArtifacts().size() + 1 ); |
| |
| if ( getOutputDirectory() != null && getOutputDirectory().exists() ) |
| { |
| list.add( new ClassPathItem( ".", getOutputDirectory() ) ); |
| } |
| |
| final Collection<Artifact> 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<Artifact> getSelectedDependencies(Collection<Artifact> artifacts) throws MojoExecutionException |
| { |
| if ( null == excludeDependencies || excludeDependencies.isEmpty() ) |
| { |
| return artifacts; |
| } |
| else if ( "true".equalsIgnoreCase( excludeDependencies ) ) |
| { |
| return Collections.emptyList(); |
| } |
| |
| Collection<Artifact> 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<String> 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<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, 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<Resource> getMavenResources( MavenProject currentProject, boolean test ) |
| { |
| List<Resource> 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<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<>(); |
| 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( 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<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, Analyzer analyzer) |
| throws MojoExecutionException |
| { |
| final Collection<Artifact> 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<StringBuilder, List<String>> map = new HashMap<>(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_SOURCES) ) |
| { |
| 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; |
| } |
| } |
| } |