View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.felix.bundleplugin;
20  
21  
22  import java.io.ByteArrayInputStream;
23  import java.io.ByteArrayOutputStream;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.Writer;
29  import java.lang.reflect.Array;
30  import java.lang.reflect.Method;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.Collection;
34  import java.util.Collections;
35  import java.util.Enumeration;
36  import java.util.HashMap;
37  import java.util.HashSet;
38  import java.util.Iterator;
39  import java.util.LinkedHashMap;
40  import java.util.LinkedHashSet;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.Map.Entry;
44  import java.util.Properties;
45  import java.util.Set;
46  import java.util.TreeMap;
47  import java.util.jar.Attributes;
48  import java.util.jar.Manifest;
49  
50  import org.apache.felix.bundleplugin.pom.PomWriter;
51  import org.apache.maven.archiver.ManifestSection;
52  import org.apache.maven.archiver.MavenArchiveConfiguration;
53  import org.apache.maven.archiver.MavenArchiver;
54  import org.apache.maven.artifact.Artifact;
55  import org.apache.maven.artifact.factory.ArtifactFactory;
56  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
57  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
58  import org.apache.maven.artifact.repository.ArtifactRepository;
59  import org.apache.maven.artifact.resolver.ArtifactCollector;
60  import org.apache.maven.artifact.resolver.ArtifactResolver;
61  import org.apache.maven.execution.MavenSession;
62  import org.apache.maven.model.Dependency;
63  import org.apache.maven.model.Exclusion;
64  import org.apache.maven.model.License;
65  import org.apache.maven.model.Model;
66  import org.apache.maven.model.Resource;
67  import org.apache.maven.plugin.AbstractMojo;
68  import org.apache.maven.plugin.MojoExecutionException;
69  import org.apache.maven.plugin.MojoFailureException;
70  import org.apache.maven.plugin.logging.Log;
71  import org.apache.maven.plugins.annotations.Component;
72  import org.apache.maven.plugins.annotations.LifecyclePhase;
73  import org.apache.maven.plugins.annotations.Mojo;
74  import org.apache.maven.plugins.annotations.Parameter;
75  import org.apache.maven.plugins.annotations.ResolutionScope;
76  import org.apache.maven.project.MavenProject;
77  import org.apache.maven.project.MavenProjectBuilder;
78  import org.apache.maven.project.MavenProjectHelper;
79  import org.apache.maven.project.ProjectBuildingException;
80  import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
81  import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
82  import org.apache.maven.shared.osgi.DefaultMaven2OsgiConverter;
83  import org.apache.maven.shared.osgi.Maven2OsgiConverter;
84  import org.codehaus.plexus.archiver.UnArchiver;
85  import org.codehaus.plexus.archiver.manager.ArchiverManager;
86  import org.codehaus.plexus.util.DirectoryScanner;
87  import org.codehaus.plexus.util.FileUtils;
88  import org.codehaus.plexus.util.PropertyUtils;
89  import org.codehaus.plexus.util.StringUtils;
90  import org.codehaus.plexus.util.WriterFactory;
91  import org.sonatype.plexus.build.incremental.BuildContext;
92  
93  import aQute.bnd.header.Attrs;
94  import aQute.bnd.header.OSGiHeader;
95  import aQute.bnd.header.Parameters;
96  import aQute.bnd.osgi.Analyzer;
97  import aQute.bnd.osgi.Builder;
98  import aQute.bnd.osgi.Constants;
99  import aQute.bnd.osgi.Descriptors.PackageRef;
100 import aQute.bnd.osgi.EmbeddedResource;
101 import aQute.bnd.osgi.FileResource;
102 import aQute.bnd.osgi.Instruction;
103 import aQute.bnd.osgi.Instructions;
104 import aQute.bnd.osgi.Jar;
105 import aQute.bnd.osgi.Packages;
106 import aQute.bnd.osgi.Processor;
107 import aQute.lib.collections.ExtList;
108 import aQute.lib.spring.SpringXMLType;
109 import aQute.libg.generics.Create;
110 
111 
112 /**
113  * Create an OSGi bundle from Maven project
114  *
115  */
116 @Mojo( name = "bundle", requiresDependencyResolution = ResolutionScope.TEST,
117        threadSafe = true,
118        defaultPhase = LifecyclePhase.PACKAGE )
119 public class BundlePlugin extends AbstractMojo
120 {
121     /**
122      * Directory where the manifest will be written
123      */
124     @Parameter( property = "manifestLocation", defaultValue = "${project.build.outputDirectory}/META-INF" )
125     protected File manifestLocation;
126 
127     /**
128      * Output a nicely formatted manifest that still respects the 72 character line limit.
129      */
130     @Parameter( property = "niceManifest", defaultValue = "false" )
131     protected boolean niceManifest;
132 
133     /**
134      * File where the BND instructions will be dumped
135      */
136     @Parameter( property = "dumpInstructions" )
137     protected File dumpInstructions;
138 
139     /**
140      * File where the BND class-path will be dumped
141      */
142     @Parameter( property = "dumpClasspath" )
143     protected File dumpClasspath;
144 
145     /**
146      * When true, unpack the bundle contents to the outputDirectory
147      */
148     @Parameter( property = "unpackBundle" )
149     protected boolean unpackBundle;
150 
151     /**
152      * Comma separated list of artifactIds to exclude from the dependency classpath passed to BND (use "true" to exclude everything)
153      */
154     @Parameter( property = "excludeDependencies" )
155     protected String excludeDependencies;
156 
157     /**
158      * Final name of the bundle (without classifier or extension)
159      */
160     @Parameter( defaultValue = "${project.build.finalName}")
161     private String finalName;
162 
163     /**
164      * Classifier type of the bundle to be installed.  For example, "jdk14".
165      * Defaults to none which means this is the project's main bundle.
166      */
167     @Parameter
168     protected String classifier;
169 
170     /**
171      * Packaging type of the bundle to be installed.  For example, "jar".
172      * Defaults to none which means use the same packaging as the project.
173      */
174     @Parameter
175     protected String packaging;
176 
177     /**
178      * If true, remove any inlined or embedded dependencies from the resulting pom.
179      */
180     @Parameter
181     protected boolean createDependencyReducedPom;
182 
183     /**
184      * Where to put the dependency reduced pom. Note: setting a value for this parameter with a directory other than
185      * ${basedir} will change the value of ${basedir} for all executions that come after the shade execution. This is
186      * often not what you want. This is considered an open issue with this plugin.
187      */
188     @Parameter( defaultValue = "${basedir}/dependency-reduced-pom.xml" )
189     protected File dependencyReducedPomLocation;
190 
191     /**
192      * Directory where the SCR files will be written
193      */
194     @Parameter(defaultValue="${project.build.outputDirectory}")
195     protected File scrLocation;
196 
197     /**
198      * When true, dump the generated SCR files
199      */
200     @Parameter
201     protected boolean exportScr;
202     
203     @Component
204     private MavenProjectHelper m_projectHelper;
205 
206     @Component
207     private ArchiverManager m_archiverManager;
208 
209     @Component
210     private ArtifactHandlerManager m_artifactHandlerManager;
211 
212     /* The current Maven session.  */
213     @Parameter( defaultValue = "${session}", readonly = true )
214     protected MavenSession session;
215 
216 
217     /**
218      * ProjectBuilder, needed to create projects from the artifacts.
219      */
220     @Component
221     protected MavenProjectBuilder mavenProjectBuilder;
222 
223     @Component
224     private DependencyTreeBuilder dependencyTreeBuilder;
225 
226     @Component
227     private ArtifactMetadataSource artifactMetadataSource;
228 
229     @Component
230     private ArtifactCollector artifactCollector;
231 
232     @Component
233     protected ArtifactFactory artifactFactory;
234 
235     /**
236      * Artifact resolver, needed to download source jars for inclusion in classpath.
237      */
238     @Component
239     protected ArtifactResolver artifactResolver;
240 
241 
242     /**
243      * Local maven repository.
244      */
245     @Parameter( readonly = true, required = true, defaultValue = "${localRepository}" )
246     protected ArtifactRepository localRepository;
247 
248     /**
249      * Remote repositories which will be searched for source attachments.
250      */
251     @Parameter( readonly = true, required = true, defaultValue = "${project.remoteArtifactRepositories}" )
252     protected List<ArtifactRepository> remoteArtifactRepositories;
253 
254 
255 
256     /**
257      * Project types which this plugin supports.
258      */
259     @Parameter
260     protected List<String> supportedProjectTypes = Arrays.asList("jar", "bundle");
261 
262     /**
263      * Project types which are not supported, but silently ignored.
264      */
265     @Parameter
266     protected List<String> noWarningProjectTypes = Collections.emptyList();
267 
268     /**
269      * The directory for the generated bundles.
270      */
271     @Parameter( defaultValue = "${project.build.outputDirectory}" )
272     private File outputDirectory;
273 
274     /**
275      * The directory for the generated JAR.
276      */
277     @Parameter( defaultValue = "${project.build.directory}" )
278     private String buildDirectory;
279 
280     /**
281      * The Maven project.
282      */
283     @Parameter( defaultValue = "${project}", readonly = true, required = true )
284     protected MavenProject project;
285 
286     /**
287      * The BND instructions for the bundle.
288      * Maven will expand property macros in these values. If you want to use a BND macro, you must double the dollar sign
289      * for the plugin to pass it to BND correctly. For example: <br>
290      * {@code <_consumer-policy>$${range;[===,+)}<code>}<code>{@code </_consumer-policy> }
291      */
292     @Parameter
293     private Map<String, String> instructions = new LinkedHashMap<String, String>();
294 
295     /**
296      * Use locally patched version for now.
297      */
298     private final Maven2OsgiConverter m_maven2OsgiConverter = new DefaultMaven2OsgiConverter();
299 
300     /**
301      * The archive configuration to use.
302      */
303     @Parameter
304     private MavenArchiveConfiguration archive; // accessed indirectly in JarPluginConfiguration
305 
306     @Parameter( defaultValue = "${session}", readonly = true, required = true )
307     private MavenSession m_mavenSession;
308 
309     @Component
310     protected BuildContext buildContext;
311     
312     private static final String MAVEN_SYMBOLICNAME = "maven-symbolicname";
313     private static final String MAVEN_RESOURCES = "{maven-resources}";
314     private static final String MAVEN_TEST_RESOURCES = "{maven-test-resources}";
315     private static final String LOCAL_PACKAGES = "{local-packages}";
316     private static final String MAVEN_SOURCES = "{maven-sources}";
317     private static final String MAVEN_TEST_SOURCES = "{maven-test-sources}";
318     private static final String BUNDLE_PLUGIN_EXTENSION = "BNDExtension-";
319     private static final String BUNDLE_PLUGIN_PREPEND_EXTENSION = "BNDPrependExtension-";
320 
321     private static final String[] EMPTY_STRING_ARRAY =
322         {};
323     private static final String[] DEFAULT_INCLUDES =
324         { "**/**" };
325 
326     private static final String NL = System.getProperty( "line.separator" );
327 
328 
329     protected Maven2OsgiConverter getMaven2OsgiConverter()
330     {
331         return m_maven2OsgiConverter;
332     }
333 
334 
335     protected MavenProject getProject()
336     {
337         return project;
338     }
339 
340     protected Map<String, String> getInstructions() {
341         return instructions;
342     }
343 
344     /**
345      * @see org.apache.maven.plugin.AbstractMojo#execute()
346      */
347     public void execute() throws MojoExecutionException
348     {
349         String projectType = getProject().getArtifact().getType();
350 
351         // ignore unsupported project types, useful when bundleplugin is configured in parent pom
352         if ( !supportedProjectTypes.contains( projectType ) )
353         {
354             if (!noWarningProjectTypes.contains( projectType ) )
355             {
356                 getLog().warn(
357                         "Ignoring project type " + projectType + " - supportedProjectTypes = " + supportedProjectTypes);
358             }
359             return;
360         }
361 
362         try
363         {
364             execute( instructions, getClasspath( project) );
365         }
366         catch ( IOException e )
367         {
368             throw new MojoExecutionException( "Error calculating classpath for project " + project, e );
369         }
370     }
371 
372 
373     /* transform directives from their XML form to the expected BND syntax (eg. _include becomes -include) */
374     protected static Map<String, String> transformDirectives( Map<String, String> originalInstructions )
375     {
376         Map<String, String> transformedInstructions = new LinkedHashMap<String, String>();
377         for ( Map.Entry<String, String> e : originalInstructions.entrySet() )
378         {
379             String key = e.getKey();
380             if ( key.startsWith( "_" ) )
381             {
382                 key = "-" + key.substring( 1 );
383             }
384 
385             String value = e.getValue();
386             if ( null == value )
387             {
388                 value = "";
389             }
390             else
391             {
392                 value = value.replaceAll( "\\p{Blank}*[\r\n]\\p{Blank}*", "" );
393             }
394 
395             if ( Analyzer.WAB.equals( key ) && value.length() == 0 )
396             {
397                 // provide useful default
398                 value = "src/main/webapp/";
399             }
400 
401             transformedInstructions.put( key, value );
402         }
403         return transformedInstructions;
404     }
405 
406 
407     protected boolean reportErrors( String prefix, Analyzer analyzer )
408     {
409         List<String> errors = analyzer.getErrors();
410         List<String> warnings = analyzer.getWarnings();
411 
412         for ( String msg : warnings )
413         {
414             getLog().warn( prefix + " : " + msg );
415         }
416 
417         boolean hasErrors = false;
418         String fileNotFound = "Input file does not exist: ";
419         for ( String msg : errors )
420         {
421             if ( msg.startsWith(fileNotFound) && msg.endsWith( "~" ) )
422             {
423                 // treat as warning; this error happens when you have duplicate entries in Include-Resource
424                 String duplicate = Processor.removeDuplicateMarker( msg.substring( fileNotFound.length() ) );
425                 getLog().warn( prefix + " : Duplicate path '" + duplicate + "' in Include-Resource" );
426             }
427             else
428             {
429                 getLog().error( prefix + " : " + msg );
430                 hasErrors = true;
431             }
432         }
433         return hasErrors;
434     }
435 
436 
437     protected void execute(Map<String, String> originalInstructions,
438                            ClassPathItem[] classpath) throws MojoExecutionException
439     {
440         try
441         {
442             File jarFile = new File( getBuildDirectory(), getBundleName( project ) );
443             Builder builder = buildOSGiBundle( project, originalInstructions, classpath );
444             boolean hasErrors = reportErrors( "Bundle " + project.getArtifact(), builder );
445             if ( hasErrors )
446             {
447                 String failok = builder.getProperty( "-failok" );
448                 if ( null == failok || "false".equalsIgnoreCase( failok ) )
449                 {
450                     jarFile.delete();
451 
452                     throw new MojoFailureException( "Error(s) found in bundle configuration" );
453                 }
454             }
455 
456             // attach bundle to maven project
457             jarFile.getParentFile().mkdirs();
458             builder.getJar().write( jarFile );
459 
460             Artifact mainArtifact = project.getArtifact();
461 
462             if ( "bundle".equals( mainArtifact.getType() ) )
463             {
464                 // workaround for MNG-1682: force maven to install artifact using the "jar" handler
465                 mainArtifact.setArtifactHandler( m_artifactHandlerManager.getArtifactHandler( "jar" ) );
466             }
467 
468             boolean customClassifier = null != classifier && classifier.trim().length() > 0;
469             boolean customPackaging = null != packaging && packaging.trim().length() > 0;
470 
471             if ( customClassifier && customPackaging )
472             {
473                 m_projectHelper.attachArtifact( project, packaging, classifier, jarFile );
474             }
475             else if ( customClassifier )
476             {
477                 m_projectHelper.attachArtifact( project, jarFile, classifier );
478             }
479             else if ( customPackaging )
480             {
481                 m_projectHelper.attachArtifact( project, packaging, jarFile );
482             }
483             else
484             {
485                 mainArtifact.setFile( jarFile );
486             }
487 
488             if ( unpackBundle )
489             {
490                 unpackBundle( jarFile );
491             }
492 
493             if ( manifestLocation != null )
494             {
495                 File outputFile = new File( manifestLocation, "MANIFEST.MF" );
496 
497                 try
498                 {
499                     ManifestPlugin.writeManifest( builder, outputFile, niceManifest, exportScr, scrLocation, buildContext, getLog() );
500                 }
501                 catch ( IOException e )
502                 {
503                     getLog().error( "Error trying to write Manifest to file " + outputFile, e );
504                 }
505             }
506 
507             // cleanup...
508             builder.close();
509         }
510         catch ( MojoFailureException e )
511         {
512             getLog().error( e.getLocalizedMessage() );
513             throw new MojoExecutionException( "Error(s) found in bundle configuration", e );
514         }
515         catch ( Exception e )
516         {
517             getLog().error( "An internal error occurred", e );
518             throw new MojoExecutionException( "Internal error in maven-bundle-plugin", e );
519         }
520     }
521 
522 
523     protected Builder getOSGiBuilder( MavenProject currentProject, Map<String, String> originalInstructions,
524         ClassPathItem[] classpath ) throws Exception
525     {
526         Properties properties = new Properties();
527         properties.putAll( getDefaultProperties( currentProject ) );
528         properties.putAll( transformDirectives( originalInstructions ) );
529 
530         // process overrides from project
531         final Map<String, String> addProps = new HashMap<>();
532         for ( Entry<Object, Object> entry : currentProject.getProperties().entrySet() )
533         {
534             final String key = entry.getKey().toString();
535             if ( key.startsWith(BUNDLE_PLUGIN_EXTENSION) )
536             {
537                 final String oKey = key.substring(BUNDLE_PLUGIN_EXTENSION.length());
538                 final String currentValue = properties.getProperty(oKey);
539                 if ( currentValue == null )
540                 {
541                     addProps.put(oKey, entry.getValue().toString());
542                 }
543                 else
544                 {
545                     addProps.put(oKey, currentValue + ',' + entry.getValue());
546                 }
547             }
548             if ( key.startsWith(BUNDLE_PLUGIN_PREPEND_EXTENSION) )
549             {
550                 final String oKey = key.substring(BUNDLE_PLUGIN_PREPEND_EXTENSION.length());
551                 final String currentValue = properties.getProperty(oKey);
552                 if ( currentValue == null )
553                 {
554                     addProps.put(oKey, entry.getValue().toString());
555                 }
556                 else
557                 {
558                     addProps.put(oKey, entry.getValue() + "," + currentValue);
559                 }
560             }
561         }
562         properties.putAll( addProps );
563         for ( String key : addProps.keySet() )
564         {
565             properties.remove(BUNDLE_PLUGIN_EXTENSION + key);
566             properties.remove(BUNDLE_PLUGIN_PREPEND_EXTENSION + key);
567         }
568 
569         if (properties.getProperty("Bundle-Activator") != null
570                 && properties.getProperty("Bundle-Activator").isEmpty())
571         {
572             properties.remove("Bundle-Activator");
573         }
574         if (properties.containsKey("-disable-plugin"))
575         {
576             String[] disabled = properties.remove("-disable-plugin").toString().replaceAll(" ", "").split(",");
577             String[] enabled = properties.getProperty(Analyzer.PLUGIN, "").replaceAll(" ", "").split(",");
578             Set<String> plugin = new LinkedHashSet<>();
579             plugin.addAll(Arrays.asList(enabled));
580             plugin.removeAll(Arrays.asList(disabled));
581             StringBuilder sb = new StringBuilder();
582             for (String s : plugin)
583             {
584                 if (sb.length() > 0)
585                 {
586                     sb.append(",");
587                 }
588                 sb.append(s);
589             }
590             properties.setProperty(Analyzer.PLUGIN, sb.toString());
591         }
592 
593         Builder builder = new Builder();
594         synchronized ( BundlePlugin.class ) // protect setBase...getBndLastModified which uses static DateFormat
595         {
596             builder.setBase( getBase( currentProject ) );
597         }
598         builder.setProperties( sanitize( properties ) );
599         if ( classpath != null )
600         {
601             List<Jar> jars = new ArrayList<>();
602             for ( int i = 0; i < classpath.length; i++ ) {
603                 if ( classpath[i].file.exists() ) {
604                     jars.add( new Jar( classpath[i].id, classpath[i].file ) );
605                 }
606             }
607             builder.setClasspath( jars );
608         }
609 
610         return builder;
611     }
612 
613 
614     protected static Properties sanitize( Properties properties )
615     {
616         // convert any non-String keys/values to Strings
617         Properties sanitizedEntries = new Properties();
618         for ( Iterator<Map.Entry<Object,Object>> itr = properties.entrySet().iterator(); itr.hasNext(); )
619         {
620             Map.Entry<Object,Object> entry = itr.next();
621             if ( !(entry.getKey() instanceof String) )
622             {
623                 String key = sanitize(entry.getKey());
624                 if ( !properties.containsKey( key ) )
625                 {
626                     sanitizedEntries.setProperty( key, sanitize( entry.getValue() ) );
627                 }
628                 itr.remove();
629             }
630             else if ( !(entry.getValue() instanceof String) )
631             {
632                 entry.setValue( sanitize( entry.getValue() ) );
633             }
634         }
635         properties.putAll( sanitizedEntries );
636         return properties;
637     }
638 
639 
640     protected static String sanitize( Object value )
641     {
642         if ( value instanceof String )
643         {
644             return ( String ) value;
645         }
646         else if ( value instanceof Iterable )
647         {
648             String delim = "";
649             StringBuilder buf = new StringBuilder();
650             for ( Object i : ( Iterable<?> ) value )
651             {
652                 buf.append( delim ).append( i );
653                 delim = ", ";
654             }
655             return buf.toString();
656         }
657         else if ( value.getClass().isArray() )
658         {
659             String delim = "";
660             StringBuilder buf = new StringBuilder();
661             for ( int i = 0, len = Array.getLength( value ); i < len; i++ )
662             {
663                 buf.append( delim ).append( Array.get( value, i ) );
664                 delim = ", ";
665             }
666             return buf.toString();
667         }
668         else
669         {
670             return String.valueOf( value );
671         }
672     }
673 
674 
675     protected void addMavenInstructions(MavenProject currentProject, Builder builder) throws Exception
676     {
677         if ( currentProject.getBasedir() != null )
678         {
679             // update BND instructions to add included Maven resources
680             includeMavenResources(currentProject, builder, getLog());
681 
682             // Fixup error messages
683             includeJava9Fixups(currentProject, builder);
684 
685             // calculate default export/private settings based on sources
686             addLocalPackages(outputDirectory, builder);
687 
688             // tell BND where the current project source resides
689             addMavenSourcePath(currentProject, builder, getLog());
690         }
691 
692         // update BND instructions to embed selected Maven dependencies
693         Collection<Artifact> embeddableArtifacts = getEmbeddableArtifacts( currentProject, builder );
694         DependencyEmbedder dependencyEmbedder = new DependencyEmbedder(getLog(), embeddableArtifacts);
695         dependencyEmbedder.processHeaders(builder);
696 
697         Collection<Artifact> embeddedArtifacts = dependencyEmbedder.getEmbeddedArtifacts();
698         if ( !embeddedArtifacts.isEmpty() && createDependencyReducedPom )
699         {
700             Set<String> embeddedIds = new HashSet<String>();
701             for ( Artifact artifact : embeddedArtifacts )
702             {
703                 embeddedIds.add( getId( artifact ) );
704             }
705             createDependencyReducedPom( embeddedIds );
706 
707         }
708 
709         if ( dumpInstructions != null || getLog().isDebugEnabled() )
710         {
711             StringBuilder buf = new StringBuilder();
712             getLog().debug( "BND Instructions:" + NL + dumpInstructions( builder.getProperties(), buf ) );
713             if ( dumpInstructions != null )
714             {
715                 getLog().info( "Writing BND instructions to " + dumpInstructions );
716                 dumpInstructions.getParentFile().mkdirs();
717                 FileUtils.fileWrite( dumpInstructions, "# BND instructions" + NL + buf );
718             }
719         }
720 
721 
722 
723         if ( dumpClasspath != null || getLog().isDebugEnabled() )
724         {
725             StringBuilder buf = new StringBuilder();
726             getLog().debug("BND Classpath:" + NL + dumpClasspath(builder.getClasspath(), buf));
727             if ( dumpClasspath != null )
728             {
729                 getLog().info( "Writing BND classpath to " + dumpClasspath );
730                 dumpClasspath.getParentFile().mkdirs();
731                 FileUtils.fileWrite( dumpClasspath, "# BND classpath" + NL + buf );
732             }
733         }
734     }
735 
736 
737     // We need to find the direct dependencies that have been included in the uber JAR so that we can modify the
738     // POM accordingly.
739     private void createDependencyReducedPom( Set<String> artifactsToRemove )
740             throws IOException, ProjectBuildingException, DependencyTreeBuilderException {
741         Model model = project.getOriginalModel();
742         List<Dependency> dependencies = new ArrayList<Dependency>();
743 
744         boolean modified = false;
745 
746         List<Dependency> transitiveDeps = new ArrayList<Dependency>();
747 
748         for ( Artifact artifact : project.getArtifacts() )
749         {
750             if ( "pom".equals( artifact.getType() ) )
751             {
752                 // don't include pom type dependencies in dependency reduced pom
753                 continue;
754             }
755 
756             //promote
757             Dependency dep = new Dependency();
758             dep.setArtifactId( artifact.getArtifactId() );
759             if ( artifact.hasClassifier() )
760             {
761                 dep.setClassifier( artifact.getClassifier() );
762             }
763             dep.setGroupId( artifact.getGroupId() );
764             dep.setOptional( artifact.isOptional() );
765             dep.setScope( artifact.getScope() );
766             dep.setType( artifact.getType() );
767             dep.setVersion( artifact.getVersion() );
768 
769             //we'll figure out the exclusions in a bit.
770 
771             transitiveDeps.add( dep );
772         }
773         List<Dependency> origDeps = project.getDependencies();
774 
775         for (Dependency d : origDeps)
776         {
777             dependencies.add(d);
778 
779             String id = getId(d);
780 
781             if (artifactsToRemove.contains(id))
782             {
783                 modified = true;
784 
785                 dependencies.remove(d);
786             }
787         }
788 
789         // Check to see if we have a reduction and if so rewrite the POM.
790         if ( modified )
791         {
792             while ( modified )
793             {
794 
795                 model.setDependencies( dependencies );
796 
797                 if ( dependencyReducedPomLocation == null )
798                 {
799                     // MSHADE-123: We can't default to 'target' because it messes up uses of ${project.basedir}
800                     dependencyReducedPomLocation = new File ( project.getBasedir(), "dependency-reduced-pom.xml" );
801                 }
802 
803                 File f = dependencyReducedPomLocation;
804                 if ( f.exists() )
805                 {
806                     f.delete();
807                 }
808 
809                 Writer w = WriterFactory.newXmlWriter( f );
810 
811                 String origRelativePath = null;
812                 String replaceRelativePath = null;
813                 if ( model.getParent() != null)
814                 {
815                     origRelativePath = model.getParent().getRelativePath();
816 
817                 }
818                 replaceRelativePath = origRelativePath;
819 
820                 if ( origRelativePath == null )
821                 {
822                     origRelativePath = "../pom.xml";
823                 }
824 
825                 if ( model.getParent() != null )
826                 {
827                     File parentFile = new File( project.getBasedir(), model.getParent().getRelativePath() ).getCanonicalFile();
828                     if ( !parentFile.isFile() )
829                     {
830                         parentFile = new File( parentFile, "pom.xml");
831                     }
832 
833                     parentFile = parentFile.getCanonicalFile();
834 
835                     String relPath = RelativizePath.convertToRelativePath( parentFile, f );
836                     model.getParent().setRelativePath( relPath );
837                 }
838 
839                 try
840                 {
841                     PomWriter.write( w, model, true );
842                 }
843                 finally
844                 {
845                     if ( model.getParent() != null )
846                     {
847                         model.getParent().setRelativePath( replaceRelativePath );
848                     }
849                     w.close();
850                 }
851 
852                 MavenProject p2 = mavenProjectBuilder.build( f, localRepository, null );
853                 modified = updateExcludesInDeps( p2, dependencies, transitiveDeps );
854 
855             }
856 
857             project.setFile( dependencyReducedPomLocation );
858         }
859     }
860 
861     private String getId( Artifact artifact )
862     {
863         return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getType(), artifact.getClassifier() );
864     }
865 
866     private String getId( Dependency dependency )
867     {
868         return getId( dependency.getGroupId(), dependency.getArtifactId(), dependency.getType(),
869                 dependency.getClassifier() );
870     }
871 
872     private String getId( String groupId, String artifactId, String type, String classifier )
873     {
874         return groupId + ":" + artifactId + ":" + type + ":" + ( ( classifier != null ) ? classifier : "" );
875     }
876 
877     public boolean updateExcludesInDeps( MavenProject project, List<Dependency> dependencies, List<Dependency> transitiveDeps )
878             throws DependencyTreeBuilderException
879     {
880         org.apache.maven.shared.dependency.tree.DependencyNode node = dependencyTreeBuilder.buildDependencyTree(project, localRepository, artifactFactory,
881                 artifactMetadataSource, null,
882                 artifactCollector);
883         boolean modified = false;
884         for (org.apache.maven.shared.dependency.tree.DependencyNode n2 : node.getChildren())
885         {
886             for (org.apache.maven.shared.dependency.tree.DependencyNode n3 : n2.getChildren())
887             {
888                 //anything two levels deep that is marked "included"
889                 //is stuff that was excluded by the original poms, make sure it
890                 //remains excluded IF promoting transitives.
891                 if (n3.getState() == org.apache.maven.shared.dependency.tree.DependencyNode.INCLUDED)
892                 {
893                     //check if it really isn't in the list of original dependencies.  Maven
894                     //prior to 2.0.8 may grab versions from transients instead of
895                     //from the direct deps in which case they would be marked included
896                     //instead of OMITTED_FOR_DUPLICATE
897 
898                     //also, if not promoting the transitives, level 2's would be included
899                     boolean found = false;
900                     for (Dependency dep : transitiveDeps)
901                     {
902                         if (dep.getArtifactId().equals(n3.getArtifact().getArtifactId()) && dep.getGroupId().equals(
903                                 n3.getArtifact().getGroupId()))
904                         {
905                             found = true;
906                         }
907 
908                     }
909 
910                     if (!found)
911                     {
912                         for (Dependency dep : dependencies)
913                         {
914                             if (dep.getArtifactId().equals(n2.getArtifact().getArtifactId())
915                                     && dep.getGroupId().equals(n2.getArtifact().getGroupId()))
916                             {
917                                 Exclusion exclusion = new Exclusion();
918                                 exclusion.setArtifactId(n3.getArtifact().getArtifactId());
919                                 exclusion.setGroupId(n3.getArtifact().getGroupId());
920                                 dep.addExclusion(exclusion);
921                                 modified = true;
922                                 break;
923                             }
924                         }
925                     }
926                 }
927             }
928         }
929         return modified;
930     }
931 
932 
933     protected Builder buildOSGiBundle(MavenProject currentProject, Map<String, String> originalInstructions,
934                                       ClassPathItem[] classpath) throws Exception
935     {
936         Builder builder = getOSGiBuilder( currentProject, originalInstructions, classpath );
937 
938         addMavenInstructions( currentProject, builder );
939 
940         builder.build();
941 
942         mergeMavenManifest(currentProject, builder);
943 
944         return builder;
945     }
946 
947 
948     protected static StringBuilder dumpInstructions( Properties properties, StringBuilder buf )
949     {
950         try
951         {
952             buf.append( "#-----------------------------------------------------------------------" + NL );
953             Properties stringProperties = new Properties();
954             for ( Enumeration<String> e = (Enumeration<String>) properties.propertyNames(); e.hasMoreElements(); )
955             {
956                 // we can only store String properties
957                 String key = e.nextElement();
958                 String value = properties.getProperty( key );
959                 if ( value != null )
960                 {
961                     stringProperties.setProperty( key, value );
962                 }
963             }
964             ByteArrayOutputStream out = new ByteArrayOutputStream();
965             stringProperties.store( out, null ); // properties encoding is 8859_1
966             buf.append( out.toString( "8859_1" ) );
967             buf.append("#-----------------------------------------------------------------------").append(NL);
968         }
969         catch ( Throwable e )
970         {
971             // ignore...
972         }
973         return buf;
974     }
975 
976 
977     protected static StringBuilder dumpClasspath( List<Jar> classpath, StringBuilder buf )
978     {
979         try
980         {
981             buf.append("#-----------------------------------------------------------------------").append(NL);
982             buf.append("-classpath:\\").append(NL);
983             for ( Iterator<Jar> i = classpath.iterator(); i.hasNext(); )
984             {
985                 File path = i.next().getSource();
986                 if ( path != null )
987                 {
988                     buf.append(' ').append(path.toString()).append(i.hasNext() ? ",\\" : "").append(NL);
989                 }
990             }
991             buf.append("#-----------------------------------------------------------------------").append(NL);
992         }
993         catch ( Throwable e )
994         {
995             // ignore...
996         }
997         return buf;
998     }
999 
1000 
1001     protected static StringBuilder dumpManifest( Manifest manifest, StringBuilder buf )
1002     {
1003         try
1004         {
1005             buf.append("#-----------------------------------------------------------------------").append(NL);
1006             ByteArrayOutputStream out = new ByteArrayOutputStream();
1007             ManifestWriter.outputManifest(manifest, out, true); // manifest encoding is UTF8
1008             buf.append( out.toString( "UTF8" ) );
1009             buf.append("#-----------------------------------------------------------------------").append(NL);
1010         }
1011         catch ( Throwable e )
1012         {
1013             // ignore...
1014         }
1015         return buf;
1016     }
1017 
1018 
1019     protected static void includeMavenResources( MavenProject currentProject, Analyzer analyzer, Log log )
1020     {
1021         // pass maven resource paths onto BND analyzer
1022         final String mavenResourcePaths = getMavenResourcePaths( currentProject, false );
1023         final String mavenTestResourcePaths = getMavenResourcePaths( currentProject, true );
1024         final String includeResource = analyzer.getProperty( Analyzer.INCLUDE_RESOURCE );
1025         if ( includeResource != null )
1026         {
1027             if ( includeResource.contains( MAVEN_RESOURCES ) || includeResource.contains( MAVEN_TEST_RESOURCES ) )
1028             {
1029                 String combinedResource = StringUtils.replace( includeResource, MAVEN_RESOURCES, mavenResourcePaths );
1030                 combinedResource = StringUtils.replace( combinedResource, MAVEN_TEST_RESOURCES, mavenTestResourcePaths );
1031                 if ( combinedResource.length() > 0 )
1032                 {
1033                     analyzer.setProperty( Analyzer.INCLUDE_RESOURCE, combinedResource );
1034                 }
1035                 else
1036                 {
1037                     analyzer.unsetProperty( Analyzer.INCLUDE_RESOURCE );
1038                 }
1039             }
1040             else if ( mavenResourcePaths.length() > 0 )
1041             {
1042                 log.warn( Analyzer.INCLUDE_RESOURCE + ": overriding " + mavenResourcePaths + " with " + includeResource
1043                         + " (add " + MAVEN_RESOURCES + " if you want to include the maven resources)" );
1044             }
1045         }
1046         else if ( mavenResourcePaths.length() > 0 )
1047         {
1048             analyzer.setProperty( Analyzer.INCLUDE_RESOURCE, mavenResourcePaths );
1049         }
1050     }
1051 
1052 
1053     protected void mergeMavenManifest(MavenProject currentProject, Builder builder) throws Exception
1054     {
1055         Jar jar = builder.getJar();
1056 
1057         if ( getLog().isDebugEnabled() )
1058         {
1059             getLog().debug( "BND Manifest:" + NL + dumpManifest( jar.getManifest(), new StringBuilder() ) );
1060         }
1061 
1062         boolean addMavenDescriptor = currentProject.getBasedir() != null;
1063 
1064         try
1065         {
1066             /*
1067              * Grab customized manifest entries from the maven-jar-plugin configuration
1068              */
1069             MavenArchiveConfiguration archiveConfig = JarPluginConfiguration.getArchiveConfiguration( currentProject );
1070             String mavenManifestText = new MavenArchiver().getManifest( currentProject, archiveConfig ).toString();
1071             addMavenDescriptor = addMavenDescriptor && archiveConfig.isAddMavenDescriptor();
1072 
1073             Manifest mavenManifest = new Manifest();
1074 
1075             // First grab the external manifest file (if specified and different to target location)
1076             File externalManifestFile = archiveConfig.getManifestFile();
1077             if ( null != externalManifestFile )
1078             {
1079                 if ( !externalManifestFile.isAbsolute() )
1080                 {
1081                     externalManifestFile = new File( currentProject.getBasedir(), externalManifestFile.getPath() );
1082                 }
1083                 if ( externalManifestFile.exists() && !externalManifestFile.equals( new File( manifestLocation, "MANIFEST.MF" ) ) )
1084                 {
1085                     InputStream mis = new FileInputStream( externalManifestFile );
1086                     mavenManifest.read( mis );
1087                     mis.close();
1088                 }
1089             }
1090 
1091             // Then apply customized entries from the jar plugin; note: manifest encoding is UTF8
1092             mavenManifest.read( new ByteArrayInputStream( mavenManifestText.getBytes( "UTF8" ) ) );
1093 
1094             if ( !archiveConfig.isManifestSectionsEmpty() )
1095             {
1096                 /*
1097                  * Add customized manifest sections (for some reason MavenArchiver doesn't do this for us)
1098                  */
1099                 List<ManifestSection> sections = archiveConfig.getManifestSections();
1100                 for ( Iterator<ManifestSection> i = sections.iterator(); i.hasNext(); )
1101                 {
1102                     ManifestSection section = i.next();
1103                     Attributes attributes = new Attributes();
1104 
1105                     if ( !section.isManifestEntriesEmpty() )
1106                     {
1107                         Map<String, String> entries = section.getManifestEntries();
1108                         for ( Iterator<Map.Entry<String, String>> j = entries.entrySet().iterator(); j.hasNext(); )
1109                         {
1110                             Map.Entry<String, String> entry = j.next();
1111                             attributes.putValue( entry.getKey(), entry.getValue() );
1112                         }
1113                     }
1114 
1115                     mavenManifest.getEntries().put( section.getName(), attributes );
1116                 }
1117             }
1118 
1119             Attributes mainMavenAttributes = mavenManifest.getMainAttributes();
1120             mainMavenAttributes.putValue( "Created-By", "Apache Maven Bundle Plugin" );
1121 
1122             String[] removeHeaders = builder.getProperty( Constants.REMOVEHEADERS, "" ).split( "," );
1123 
1124             // apply -removeheaders to the custom manifest
1125             for ( int i = 0; i < removeHeaders.length; i++ )
1126             {
1127                 for ( Iterator<Object> j = mainMavenAttributes.keySet().iterator(); j.hasNext(); )
1128                 {
1129                     if ( j.next().toString().matches( removeHeaders[i].trim() ) )
1130                     {
1131                         j.remove();
1132                     }
1133                 }
1134             }
1135 
1136             /*
1137              * Overlay generated bundle manifest with customized entries
1138              */
1139             Properties properties = builder.getProperties();
1140             Manifest bundleManifest = jar.getManifest();
1141             if ( properties.containsKey( "Merge-Headers" ) )
1142             {
1143                 Instructions instructions = new Instructions( ExtList.from(builder.getProperty("Merge-Headers")) );
1144                 mergeManifest( instructions, bundleManifest, mavenManifest );
1145             }
1146             else
1147             {
1148                 bundleManifest.getMainAttributes().putAll( mainMavenAttributes );
1149                 bundleManifest.getEntries().putAll( mavenManifest.getEntries() );
1150             }
1151 
1152             // adjust the import package attributes so that optional dependencies use
1153             // optional resolution.
1154             String importPackages = bundleManifest.getMainAttributes().getValue( "Import-Package" );
1155             if ( importPackages != null )
1156             {
1157                 Set optionalPackages = getOptionalPackages( currentProject);
1158 
1159                 Map<String, ? extends Map<String, String>> values;
1160                 try (Analyzer analyzer = new Analyzer()) {
1161                     values = analyzer.parseHeader( importPackages );
1162                 }
1163                 for ( Map.Entry<String, ? extends Map<String, String>> entry : values.entrySet() )
1164                 {
1165                     String pkg = entry.getKey();
1166                     Map<String, String> options = entry.getValue();
1167                     if ( !options.containsKey( "resolution:" ) && optionalPackages.contains( pkg ) )
1168                     {
1169                         options.put( "resolution:", "optional" );
1170                     }
1171                 }
1172                 String result = Processor.printClauses( values );
1173                 bundleManifest.getMainAttributes().putValue( "Import-Package", result );
1174             }
1175 
1176             jar.setManifest( bundleManifest );
1177         }
1178         catch ( Exception e )
1179         {
1180             getLog().warn( "Unable to merge Maven manifest: " + e.getLocalizedMessage() );
1181         }
1182 
1183         if ( addMavenDescriptor )
1184         {
1185             doMavenMetadata( currentProject, jar );
1186         }
1187 
1188         if ( getLog().isDebugEnabled() )
1189         {
1190             getLog().debug( "Final Manifest:" + NL + dumpManifest( jar.getManifest(), new StringBuilder() ) );
1191         }
1192 
1193         builder.setJar( jar );
1194     }
1195 
1196 
1197     protected static void mergeManifest( Instructions instructions, Manifest... manifests ) throws IOException
1198     {
1199         for ( int i = manifests.length - 2; i >= 0; i-- )
1200         {
1201             Manifest mergedManifest = manifests[i];
1202             Manifest manifest = manifests[i + 1];
1203             Attributes mergedMainAttributes = mergedManifest.getMainAttributes();
1204             Attributes mainAttributes = manifest.getMainAttributes();
1205             Attributes filteredMainAttributes = filterAttributes( instructions, mainAttributes, null );
1206             if ( !filteredMainAttributes.isEmpty() )
1207             {
1208                 mergeAttributes( mergedMainAttributes, filteredMainAttributes );
1209             }
1210             Map<String, Attributes> mergedEntries = mergedManifest.getEntries();
1211             Map<String, Attributes> entries = manifest.getEntries();
1212             for ( Map.Entry<String, Attributes> entry : entries.entrySet() )
1213             {
1214                 String name = entry.getKey();
1215                 Attributes attributes = entry.getValue();
1216                 Attributes filteredAttributes = filterAttributes( instructions, attributes, null );
1217                 if ( !filteredAttributes.isEmpty() )
1218                 {
1219                     Attributes mergedAttributes = mergedManifest.getAttributes( name );
1220                     if ( mergedAttributes != null)
1221                     {
1222                         mergeAttributes(mergedAttributes, filteredAttributes);
1223                     }
1224                     else
1225                     {
1226                         mergedEntries.put(name, filteredAttributes);
1227                     }
1228                 }
1229             }
1230         }
1231     }
1232 
1233 
1234     /**
1235      * @see Analyzer#filter
1236      */
1237     private static Attributes filterAttributes(Instructions instructions, Attributes source, Set<Instruction> nomatch) {
1238         Attributes result = new Attributes();
1239         Map<String, Object> keys = new TreeMap<String, Object>();
1240         for ( Object key : source.keySet() )
1241         {
1242             keys.put( key.toString(), key );
1243         }
1244 
1245         List<Instruction> filters = new ArrayList<Instruction>( instructions.keySet() );
1246         if (nomatch == null)
1247         {
1248             nomatch = Create.set();
1249         }
1250         for ( Instruction instruction : filters ) {
1251             boolean match = false;
1252             for (Iterator<Map.Entry<String, Object>> i = keys.entrySet().iterator(); i.hasNext();)
1253             {
1254                 Map.Entry<String, Object> entry = i.next();
1255                 String key = entry.getKey();
1256                 if ( instruction.matches( key ) )
1257                 {
1258                     match = true;
1259                     if (!instruction.isNegated()) {
1260                         Object name = entry.getValue();
1261                         Object value = source.get( name );
1262                         result.put( name, value );
1263                     }
1264                     i.remove(); // Can never match again for another pattern
1265                 }
1266             }
1267             if (!match && !instruction.isAny())
1268                 nomatch.add(instruction);
1269         }
1270 
1271         /*
1272          * Tricky. If we have umatched instructions they might indicate that we
1273          * want to have multiple decorators for the same package. So we check
1274          * the unmatched against the result list. If then then match and have
1275          * actually interesting properties then we merge them
1276          */
1277 
1278         for (Iterator<Instruction> i = nomatch.iterator(); i.hasNext();) {
1279             Instruction instruction = i.next();
1280 
1281             // We assume the user knows what he is
1282             // doing and inserted a literal. So
1283             // we ignore any not matched literals
1284             // #252, we should not be negated to make it a constant
1285             if (instruction.isLiteral() && !instruction.isNegated()) {
1286                 Object key = keys.get( instruction.getLiteral() );
1287                 if ( key != null )
1288                 {
1289                     Object value = source.get( key );
1290                     result.put( key, value );
1291                 }
1292                 i.remove();
1293                 continue;
1294             }
1295 
1296             // Not matching a negated instruction looks
1297             // like an error ... Though so, but
1298             // in the second phase of Export-Package
1299             // the !package will never match anymore.
1300             if (instruction.isNegated()) {
1301                 i.remove();
1302                 continue;
1303             }
1304 
1305             // An optional instruction should not generate
1306             // an error
1307             if (instruction.isOptional()) {
1308                 i.remove();
1309                 continue;
1310             }
1311         }
1312         return result;
1313     }
1314 
1315 
1316     private static void mergeAttributes( Attributes... attributesArray ) throws IOException
1317     {
1318         for ( int i = attributesArray.length - 2; i >= 0; i-- )
1319         {
1320             Attributes mergedAttributes = attributesArray[i];
1321             Attributes attributes = attributesArray[i + 1];
1322             for ( Map.Entry<Object, Object> entry : attributes.entrySet() )
1323             {
1324                 Object name = entry.getKey();
1325                 String value = (String) entry.getValue();
1326                 String oldValue = (String) mergedAttributes.put( name, value );
1327                 if ( oldValue != null )
1328                 {
1329                     Parameters mergedClauses = OSGiHeader.parseHeader(oldValue);
1330                     Parameters clauses = OSGiHeader.parseHeader( value );
1331                     if ( !mergedClauses.isEqual( clauses) )
1332                     {
1333                         for ( Map.Entry<String, Attrs> clauseEntry : clauses.entrySet() )
1334                         {
1335                             String clause = clauseEntry.getKey();
1336                             Attrs attrs = clauseEntry.getValue();
1337                             Attrs mergedAttrs = mergedClauses.get( clause );
1338                             if ( mergedAttrs == null)
1339                             {
1340                                 mergedClauses.put( clause, attrs );
1341                             }
1342                             else if ( !mergedAttrs.isEqual(attrs) )
1343                             {
1344                                 for ( Map.Entry<String,String> adentry : attrs.entrySet() )
1345                                 {
1346                                     String adname = adentry.getKey();
1347                                     String ad = adentry.getValue();
1348                                     if ( mergedAttrs.containsKey( adname ) )
1349                                     {
1350                                         Attrs.Type type = attrs.getType( adname );
1351                                         switch (type)
1352                                         {
1353                                             case VERSIONS:
1354                                             case STRINGS:
1355                                             case LONGS:
1356                                             case DOUBLES:
1357                                                 ExtList<String> mergedAd = ExtList.from( mergedAttrs.get( adname ) );
1358                                                 ExtList.from( ad ).addAll( ExtList.from( ad ) );
1359                                                 mergedAttrs.put(adname, mergedAd.join() );
1360                                                 break;
1361                                         }
1362                                     }
1363                                     else
1364                                     {
1365                                         mergedAttrs.put( adname, ad );
1366                                     }
1367                                 }
1368                             }
1369                         }
1370                         mergedAttributes.put( name, Processor.printClauses( mergedClauses ) );
1371                     }
1372                 }
1373             }
1374         }
1375     }
1376 
1377 
1378     protected Set<String> getOptionalPackages(MavenProject currentProject) throws IOException, MojoExecutionException
1379     {
1380         ArrayList<Artifact> inscope = new ArrayList<Artifact>();
1381         final Collection<Artifact> artifacts = getSelectedDependencies(currentProject.getArtifacts() );
1382         for ( Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); )
1383         {
1384             Artifact artifact = it.next();
1385             if ( artifact.getArtifactHandler().isAddedToClasspath() )
1386             {
1387                 inscope.add( artifact );
1388             }
1389         }
1390 
1391         HashSet<String> optionalArtifactIds = new HashSet<String>();
1392         for ( Iterator<Artifact> it = inscope.iterator(); it.hasNext(); )
1393         {
1394             Artifact artifact = it.next();
1395             if ( artifact.isOptional() )
1396             {
1397                 String id = artifact.toString();
1398                 if ( artifact.getScope() != null )
1399                 {
1400                     // strip the scope...
1401                     id = id.replaceFirst( ":[^:]*$", "" );
1402                 }
1403                 optionalArtifactIds.add( id );
1404             }
1405 
1406         }
1407 
1408         HashSet<String> required = new HashSet<String>();
1409         HashSet<String> optional = new HashSet<String>();
1410         for ( Iterator<Artifact> it = inscope.iterator(); it.hasNext(); )
1411         {
1412             Artifact artifact = it.next();
1413             File file = getFile( artifact );
1414             if ( file == null )
1415             {
1416                 continue;
1417             }
1418 
1419             Jar jar = new Jar( artifact.getArtifactId(), file );
1420             if ( isTransitivelyOptional( optionalArtifactIds, artifact ) )
1421             {
1422                 optional.addAll( jar.getPackages() );
1423             }
1424             else
1425             {
1426                 required.addAll( jar.getPackages() );
1427             }
1428             jar.close();
1429         }
1430 
1431         optional.removeAll( required );
1432         return optional;
1433     }
1434 
1435 
1436     /**
1437      * Check to see if any dependency along the dependency trail of
1438      * the artifact is optional.
1439      *
1440      * @param artifact
1441      */
1442     protected boolean isTransitivelyOptional( HashSet<String> optionalArtifactIds, Artifact artifact )
1443     {
1444         List<String> trail = artifact.getDependencyTrail();
1445         for ( Iterator<String> iterator = trail.iterator(); iterator.hasNext(); )
1446         {
1447             String next = iterator.next();
1448             if ( optionalArtifactIds.contains( next ) )
1449             {
1450                 return true;
1451             }
1452         }
1453         return false;
1454     }
1455 
1456 
1457     private void unpackBundle( File jarFile )
1458     {
1459         File outputDir = getOutputDirectory();
1460         if ( null == outputDir )
1461         {
1462             outputDir = new File( getBuildDirectory(), "classes" );
1463         }
1464 
1465         try
1466         {
1467             /*
1468              * this directory must exist before unpacking, otherwise the plexus
1469              * unarchiver decides to use the current working directory instead!
1470              */
1471             if ( !outputDir.exists() )
1472             {
1473                 outputDir.mkdirs();
1474             }
1475 
1476             UnArchiver unArchiver = m_archiverManager.getUnArchiver( "jar" );
1477             unArchiver.setDestDirectory( outputDir );
1478             unArchiver.setSourceFile( jarFile );
1479             unArchiver.extract();
1480         }
1481         catch ( Exception e )
1482         {
1483             getLog().error( "Problem unpacking " + jarFile + " to " + outputDir, e );
1484         }
1485     }
1486 
1487 
1488     protected static String removeTagFromInstruction( String instruction, String tag )
1489     {
1490         StringBuffer buf = new StringBuffer();
1491 
1492         String[] clauses = instruction.split( "," );
1493         for ( int i = 0; i < clauses.length; i++ )
1494         {
1495             String clause = clauses[i].trim();
1496             if ( !tag.equals( clause ) )
1497             {
1498                 if ( buf.length() > 0 )
1499                 {
1500                     buf.append( ',' );
1501                 }
1502                 buf.append( clause );
1503             }
1504         }
1505 
1506         return buf.toString();
1507     }
1508 
1509 
1510     private static Map<String, String> getProperties( Model projectModel, String prefix )
1511     {
1512         Map<String, String> properties = new LinkedHashMap<String, String>();
1513         Method methods[] = Model.class.getDeclaredMethods();
1514         for ( int i = 0; i < methods.length; i++ )
1515         {
1516             String name = methods[i].getName();
1517             if ( name.startsWith( "get" ) )
1518             {
1519                 try
1520                 {
1521                     Object v = methods[i].invoke( projectModel, null );
1522                     if ( v != null )
1523                     {
1524                         name = prefix + Character.toLowerCase( name.charAt( 3 ) ) + name.substring( 4 );
1525                         if ( v.getClass().isArray() )
1526                             properties.put( name, Arrays.asList( ( Object[] ) v ).toString() );
1527                         else
1528                             properties.put( name, v.toString() );
1529 
1530                     }
1531                 }
1532                 catch ( Exception e )
1533                 {
1534                     // too bad
1535                 }
1536             }
1537         }
1538         return properties;
1539     }
1540 
1541 
1542     private static StringBuffer printLicenses( List<License> licenses )
1543     {
1544         if ( licenses == null || licenses.size() == 0 )
1545             return null;
1546         StringBuffer sb = new StringBuffer();
1547         String del = "";
1548         for ( Iterator<License> i = licenses.iterator(); i.hasNext(); )
1549         {
1550             License l = i.next();
1551             String url = l.getUrl();
1552             if ( url == null )
1553                 continue;
1554             sb.append( del );
1555             sb.append( url );
1556             del = ", ";
1557         }
1558         if ( sb.length() == 0 )
1559             return null;
1560         return sb;
1561     }
1562 
1563 
1564     /**
1565      * @param jar
1566      * @throws IOException
1567      */
1568     private void doMavenMetadata( MavenProject currentProject, Jar jar ) throws IOException
1569     {
1570         String path = "META-INF/maven/" + currentProject.getGroupId() + "/" + currentProject.getArtifactId();
1571 
1572         File pomFile = currentProject.getFile();
1573         if ( pomFile == null || !pomFile.exists() )
1574         {
1575             pomFile = new File( currentProject.getBasedir(), "pom.xml" );
1576         }
1577         if ( pomFile.exists() )
1578         {
1579             jar.putResource( path + "/pom.xml", new FileResource( pomFile ) );
1580         }
1581 
1582         Properties p = new Properties();
1583         p.put( "version", currentProject.getVersion() );
1584         p.put( "groupId", currentProject.getGroupId() );
1585         p.put( "artifactId", currentProject.getArtifactId() );
1586         ByteArrayOutputStream out = new ByteArrayOutputStream();
1587         p.store( out, "Generated by org.apache.felix.bundleplugin" );
1588         jar.putResource( path + "/pom.properties", new EmbeddedResource( out.toByteArray(), System.currentTimeMillis() ) );
1589     }
1590 
1591 
1592     protected ClassPathItem[] getClasspath(MavenProject currentProject) throws IOException, MojoExecutionException
1593     {
1594         List<ClassPathItem> list = new ArrayList<ClassPathItem>( currentProject.getArtifacts().size() + 1 );
1595 
1596         String d = currentProject.getBuild() != null ? currentProject.getBuild().getOutputDirectory() : null;
1597         if ( d != null )
1598         {
1599             list.add( new ClassPathItem( ".", new File( d ) ) );
1600         }
1601 
1602         final Collection<Artifact> artifacts = getSelectedDependencies(currentProject.getArtifacts() );
1603         for ( Artifact artifact : artifacts  )
1604         {
1605             if ( artifact.getArtifactHandler().isAddedToClasspath() && !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
1606             {
1607                 File file = getFile( artifact );
1608                 if ( file == null )
1609                 {
1610                     getLog().warn(
1611                             "File is not available for artifact " + artifact + " in project "
1612                                     + currentProject.getArtifact() );
1613                     continue;
1614                 }
1615                 ClassPathItem jar = new ClassPathItem( artifact.getArtifactId(), file );
1616                 list.add( jar );
1617             }
1618         }
1619         ClassPathItem[] cp = new ClassPathItem[list.size()];
1620         list.toArray( cp );
1621 
1622         return cp;
1623     }
1624 
1625 
1626     private Collection<Artifact> getSelectedDependencies(Collection<Artifact> artifacts) throws MojoExecutionException
1627     {
1628         if ( null == excludeDependencies || excludeDependencies.length() == 0 )
1629         {
1630             return artifacts;
1631         }
1632         else if ( "true".equalsIgnoreCase( excludeDependencies ) )
1633         {
1634             return Collections.emptyList();
1635         }
1636 
1637         Collection<Artifact> selectedDependencies = new LinkedHashSet<Artifact>( artifacts );
1638         DependencyExcluder excluder = new DependencyExcluder(artifacts );
1639         excluder.processHeaders( excludeDependencies );
1640         selectedDependencies.removeAll( excluder.getExcludedArtifacts() );
1641 
1642         return selectedDependencies;
1643     }
1644 
1645 
1646     /**
1647      * Get the file for an Artifact
1648      *
1649      * @param artifact
1650      */
1651     protected File getFile( Artifact artifact )
1652     {
1653         return artifact.getFile();
1654     }
1655 
1656 
1657     private static void header( Properties properties, String key, Object value )
1658     {
1659         if ( value == null )
1660             return;
1661 
1662         if ( value instanceof Collection && ( ( Collection ) value ).isEmpty() )
1663             return;
1664 
1665         properties.put( key, value.toString().replaceAll( "[\r\n]", "" ) );
1666     }
1667 
1668 
1669     /**
1670      * Convert a Maven version into an OSGi compliant version
1671      *
1672      * @param version Maven version
1673      * @return the OSGi version
1674      */
1675     protected String convertVersionToOsgi( String version )
1676     {
1677         return getMaven2OsgiConverter().getVersion( version );
1678     }
1679 
1680 
1681     /**
1682      * TODO this should return getMaven2Osgi().getBundleFileName( project.getArtifact() )
1683      */
1684     protected String getBundleName( MavenProject currentProject )
1685     {
1686         String extension;
1687         try
1688         {
1689             extension = currentProject.getArtifact().getArtifactHandler().getExtension();
1690         }
1691         catch ( Throwable e )
1692         {
1693             extension = currentProject.getArtifact().getType();
1694         }
1695         if ( StringUtils.isEmpty( extension ) || "bundle".equals( extension ) || "pom".equals( extension ) )
1696         {
1697             extension = "jar"; // just in case maven gets confused
1698         }
1699         if ( null != classifier && classifier.trim().length() > 0 )
1700         {
1701             return finalName + '-' + classifier + '.' + extension;
1702         }
1703         return finalName + '.' + extension;
1704     }
1705 
1706 
1707     protected String getBuildDirectory()
1708     {
1709         return buildDirectory;
1710     }
1711 
1712 
1713     protected void setBuildDirectory( String _buildirectory )
1714     {
1715         buildDirectory = _buildirectory;
1716     }
1717 
1718 
1719     protected Properties getDefaultProperties( MavenProject currentProject )
1720     {
1721         Properties properties = new Properties();
1722 
1723         String bsn;
1724         try
1725         {
1726             bsn = getMaven2OsgiConverter().getBundleSymbolicName( currentProject.getArtifact() );
1727         }
1728         catch ( Exception e )
1729         {
1730             bsn = currentProject.getGroupId() + "." + currentProject.getArtifactId();
1731         }
1732 
1733         // Setup defaults
1734         properties.put( MAVEN_SYMBOLICNAME, bsn );
1735         properties.put( Analyzer.BUNDLE_SYMBOLICNAME, bsn );
1736         properties.put( Analyzer.IMPORT_PACKAGE, "*" );
1737         properties.put( Analyzer.BUNDLE_VERSION, getMaven2OsgiConverter().getVersion( currentProject.getVersion() ) );
1738 
1739         // remove the extraneous Include-Resource and Private-Package entries from generated manifest
1740         properties.put( Constants.REMOVEHEADERS, Analyzer.INCLUDE_RESOURCE + ',' + Analyzer.PRIVATE_PACKAGE );
1741 
1742         header( properties, Analyzer.BUNDLE_DESCRIPTION, currentProject.getDescription() );
1743         StringBuffer licenseText = printLicenses( currentProject.getLicenses() );
1744         if ( licenseText != null )
1745         {
1746             header( properties, Analyzer.BUNDLE_LICENSE, licenseText );
1747         }
1748         header( properties, Analyzer.BUNDLE_NAME, currentProject.getName() );
1749 
1750         if ( currentProject.getOrganization() != null )
1751         {
1752             if ( currentProject.getOrganization().getName() != null )
1753             {
1754                 String organizationName = currentProject.getOrganization().getName();
1755                 header( properties, Analyzer.BUNDLE_VENDOR, organizationName );
1756                 properties.put( "project.organization.name", organizationName );
1757                 properties.put( "pom.organization.name", organizationName );
1758             }
1759             if ( currentProject.getOrganization().getUrl() != null )
1760             {
1761                 String organizationUrl = currentProject.getOrganization().getUrl();
1762                 header( properties, Analyzer.BUNDLE_DOCURL, organizationUrl );
1763                 properties.put( "project.organization.url", organizationUrl );
1764                 properties.put( "pom.organization.url", organizationUrl );
1765             }
1766         }
1767 
1768         properties.putAll( currentProject.getProperties() );
1769         properties.putAll( currentProject.getModel().getProperties() );
1770 
1771         for ( Iterator<String> i = currentProject.getFilters().iterator(); i.hasNext(); )
1772         {
1773             File filterFile = new File( i.next() );
1774             if ( filterFile.isFile() )
1775             {
1776                 properties.putAll( PropertyUtils.loadProperties( filterFile ) );
1777             }
1778         }
1779 
1780         if ( m_mavenSession != null )
1781         {
1782             try
1783             {
1784                 // don't pass upper-case session settings to bnd as they end up in the manifest
1785                 Properties sessionProperties = m_mavenSession.getExecutionProperties();
1786                 for ( Enumeration<String> e = (Enumeration<String>) sessionProperties.propertyNames(); e.hasMoreElements(); )
1787                 {
1788                     String key = e.nextElement();
1789                     if ( key.length() > 0 && !Character.isUpperCase( key.charAt( 0 ) ) )
1790                     {
1791                         properties.put( key, sessionProperties.getProperty( key ) );
1792                     }
1793                 }
1794             }
1795             catch ( Exception e )
1796             {
1797                 getLog().warn( "Problem with Maven session properties: " + e.getLocalizedMessage() );
1798             }
1799         }
1800 
1801         properties.putAll( getProperties( currentProject.getModel(), "project.build." ) );
1802         properties.putAll( getProperties( currentProject.getModel(), "pom." ) );
1803         properties.putAll( getProperties( currentProject.getModel(), "project." ) );
1804 
1805         properties.put( "project.baseDir", getBase( currentProject ) );
1806         properties.put( "project.build.directory", getBuildDirectory() );
1807         properties.put( "project.build.outputdirectory", getOutputDirectory() );
1808 
1809         properties.put( "classifier", classifier == null ? "" : classifier );
1810 
1811         // Add default plugins
1812         header( properties, Analyzer.PLUGIN, BlueprintPlugin.class.getName() + ","
1813                                            + SpringXMLType.class.getName() + ","
1814                                            + JpaPlugin.class.getName() );
1815 
1816         return properties;
1817     }
1818 
1819 
1820     protected static File getBase( MavenProject currentProject )
1821     {
1822         return currentProject.getBasedir() != null ? currentProject.getBasedir() : new File( "" );
1823     }
1824 
1825 
1826     protected File getOutputDirectory()
1827     {
1828         return outputDirectory;
1829     }
1830 
1831 
1832     protected void setOutputDirectory( File _outputDirectory )
1833     {
1834         outputDirectory = _outputDirectory;
1835     }
1836 
1837 
1838     private static void addLocalPackages( File outputDirectory, Analyzer analyzer ) throws IOException
1839     {
1840         Packages packages = new Packages();
1841 
1842         if ( outputDirectory != null && outputDirectory.isDirectory() )
1843         {
1844             // scan classes directory for potential packages
1845             DirectoryScanner scanner = new DirectoryScanner();
1846             scanner.setBasedir( outputDirectory );
1847             scanner.setIncludes( new String[]
1848                 { "**/*.class" } );
1849 
1850             scanner.addDefaultExcludes();
1851             scanner.scan();
1852 
1853             String[] paths = scanner.getIncludedFiles();
1854             for ( int i = 0; i < paths.length; i++ )
1855             {
1856                 packages.put( analyzer.getPackageRef( getPackageName( paths[i] ) ) );
1857             }
1858         }
1859 
1860         Packages exportedPkgs = new Packages();
1861         Packages privatePkgs = new Packages();
1862 
1863         boolean noprivatePackages = "!*".equals( analyzer.getProperty( Analyzer.PRIVATE_PACKAGE ) );
1864 
1865         for ( PackageRef pkg : packages.keySet() )
1866         {
1867             // mark all source packages as private by default (can be overridden by export list)
1868             privatePkgs.put( pkg );
1869 
1870             // we can't export the default package (".") and we shouldn't export internal packages
1871             String fqn = pkg.getFQN();
1872             if ( noprivatePackages || !( ".".equals( fqn ) || fqn.contains( ".internal" ) || fqn.contains( ".impl" ) ) )
1873             {
1874                 exportedPkgs.put( pkg );
1875             }
1876         }
1877 
1878         Properties properties = analyzer.getProperties();
1879         String exported = properties.getProperty( Analyzer.EXPORT_PACKAGE );
1880         if ( exported == null )
1881         {
1882             if ( !properties.containsKey( Analyzer.EXPORT_CONTENTS ) )
1883             {
1884                 // no -exportcontents overriding the exports, so use our computed list
1885                 for ( Attrs attrs : exportedPkgs.values() )
1886                 {
1887                     attrs.put( Constants.SPLIT_PACKAGE_DIRECTIVE, "merge-first" );
1888                 }
1889                 properties.setProperty( Analyzer.EXPORT_PACKAGE, Processor.printClauses( exportedPkgs ) );
1890             }
1891             else
1892             {
1893                 // leave Export-Package empty (but non-null) as we have -exportcontents
1894                 properties.setProperty( Analyzer.EXPORT_PACKAGE, "" );
1895             }
1896         }
1897         else if ( exported.indexOf( LOCAL_PACKAGES ) >= 0 )
1898         {
1899             String newExported = StringUtils.replace( exported, LOCAL_PACKAGES, Processor.printClauses( exportedPkgs ) );
1900             properties.setProperty( Analyzer.EXPORT_PACKAGE, newExported );
1901         }
1902 
1903         String internal = properties.getProperty( Analyzer.PRIVATE_PACKAGE );
1904         if ( internal == null )
1905         {
1906             if ( !privatePkgs.isEmpty() )
1907             {
1908                 for ( Attrs attrs : privatePkgs.values() )
1909                 {
1910                     attrs.put( Constants.SPLIT_PACKAGE_DIRECTIVE, "merge-first" );
1911                 }
1912                 properties.setProperty( Analyzer.PRIVATE_PACKAGE, Processor.printClauses( privatePkgs ) );
1913             }
1914             else
1915             {
1916                 // if there are really no private packages then use "!*" as this will keep the Bnd Tool happy
1917                 properties.setProperty( Analyzer.PRIVATE_PACKAGE, "!*" );
1918             }
1919         }
1920         else if ( internal.indexOf( LOCAL_PACKAGES ) >= 0 )
1921         {
1922             String newInternal = StringUtils.replace( internal, LOCAL_PACKAGES, Processor.printClauses( privatePkgs ) );
1923             properties.setProperty( Analyzer.PRIVATE_PACKAGE, newInternal );
1924         }
1925     }
1926 
1927 
1928     private static String getPackageName( String filename )
1929     {
1930         int n = filename.lastIndexOf( File.separatorChar );
1931         return n < 0 ? "." : filename.substring( 0, n ).replace( File.separatorChar, '.' );
1932     }
1933 
1934 
1935     private static List<Resource> getMavenResources( MavenProject currentProject, boolean test )
1936     {
1937         List<Resource> resources = new ArrayList<Resource>( test ? currentProject.getTestResources() : currentProject.getResources() );
1938 
1939         if ( currentProject.getCompileSourceRoots() != null )
1940         {
1941             // also scan for any "packageinfo" files lurking in the source folders
1942             final List<String> packageInfoIncludes = Collections.singletonList( "**/packageinfo" );
1943             for ( Iterator<String> i = currentProject.getCompileSourceRoots().iterator(); i.hasNext(); )
1944             {
1945                 String sourceRoot = i.next();
1946                 Resource packageInfoResource = new Resource();
1947                 packageInfoResource.setDirectory( sourceRoot );
1948                 packageInfoResource.setIncludes( packageInfoIncludes );
1949                 resources.add( packageInfoResource );
1950             }
1951         }
1952 
1953         return resources;
1954     }
1955 
1956 
1957     protected static String getMavenResourcePaths( MavenProject currentProject, boolean test )
1958     {
1959         final String basePath = currentProject.getBasedir().getAbsolutePath();
1960 
1961         Set<String> pathSet = new LinkedHashSet<String>();
1962         for ( Iterator<Resource> i = getMavenResources( currentProject, test ).iterator(); i.hasNext(); )
1963         {
1964             Resource resource = i.next();
1965 
1966             final String sourcePath = resource.getDirectory();
1967             final String targetPath = resource.getTargetPath();
1968 
1969             // ignore empty or non-local resources
1970             if ( new File( sourcePath ).exists() && ( ( targetPath == null ) || ( targetPath.indexOf( ".." ) < 0 ) ) )
1971             {
1972                 DirectoryScanner scanner = new DirectoryScanner();
1973 
1974                 scanner.setBasedir( sourcePath );
1975                 if ( resource.getIncludes() != null && !resource.getIncludes().isEmpty() )
1976                 {
1977                     scanner.setIncludes( resource.getIncludes().toArray( EMPTY_STRING_ARRAY ) );
1978                 }
1979                 else
1980                 {
1981                     scanner.setIncludes( DEFAULT_INCLUDES );
1982                 }
1983 
1984                 if ( resource.getExcludes() != null && !resource.getExcludes().isEmpty() )
1985                 {
1986                     scanner.setExcludes( resource.getExcludes().toArray( EMPTY_STRING_ARRAY ) );
1987                 }
1988 
1989                 scanner.addDefaultExcludes();
1990                 scanner.scan();
1991 
1992                 List<String> includedFiles = Arrays.asList( scanner.getIncludedFiles() );
1993 
1994                 for ( Iterator<String> j = includedFiles.iterator(); j.hasNext(); )
1995                 {
1996                     String name = j.next();
1997                     String path = sourcePath + '/' + name;
1998 
1999                     // make relative to project
2000                     if ( path.startsWith( basePath ) )
2001                     {
2002                         if ( path.length() == basePath.length() )
2003                         {
2004                             path = ".";
2005                         }
2006                         else
2007                         {
2008                             path = path.substring( basePath.length() + 1 );
2009                         }
2010                     }
2011 
2012                     // replace windows backslash with a slash
2013                     // this is a workaround for a problem with bnd 0.0.189
2014                     if ( File.separatorChar != '/' )
2015                     {
2016                         name = name.replace( File.separatorChar, '/' );
2017                         path = path.replace( File.separatorChar, '/' );
2018                     }
2019 
2020                     // copy to correct place
2021                     path = name + '=' + path;
2022                     if ( targetPath != null )
2023                     {
2024                         path = targetPath + '/' + path;
2025                     }
2026 
2027                     // use Bnd filtering?
2028                     if ( resource.isFiltering() )
2029                     {
2030                         path = '{' + path + '}';
2031                     }
2032 
2033                     pathSet.add( path );
2034                 }
2035             }
2036         }
2037 
2038         StringBuffer resourcePaths = new StringBuffer();
2039         for ( Iterator<String> i = pathSet.iterator(); i.hasNext(); )
2040         {
2041             resourcePaths.append( i.next() );
2042             if ( i.hasNext() )
2043             {
2044                 resourcePaths.append( ',' );
2045             }
2046         }
2047 
2048         return resourcePaths.toString();
2049     }
2050 
2051 
2052     protected Collection<Artifact> getEmbeddableArtifacts(MavenProject currentProject, Analyzer analyzer)
2053         throws MojoExecutionException
2054     {
2055         final Collection<Artifact> artifacts;
2056 
2057         String embedTransitive = analyzer.getProperty( DependencyEmbedder.EMBED_TRANSITIVE );
2058         if (Boolean.valueOf(embedTransitive))
2059         {
2060             // includes transitive dependencies
2061             artifacts = currentProject.getArtifacts();
2062         }
2063         else
2064         {
2065             // only includes direct dependencies
2066             artifacts = currentProject.getDependencyArtifacts();
2067         }
2068 
2069         return getSelectedDependencies(artifacts );
2070     }
2071 
2072 
2073     protected static void addMavenSourcePath( MavenProject currentProject, Analyzer analyzer, Log log )
2074     {
2075         // pass maven source paths onto BND analyzer
2076         StringBuilder mavenSourcePaths = new StringBuilder();
2077         StringBuilder mavenTestSourcePaths = new StringBuilder();
2078         Map<StringBuilder, List<String>> map = new HashMap<StringBuilder, List<String>>(2);
2079         map.put(mavenSourcePaths, currentProject.getCompileSourceRoots() );
2080         map.put(mavenTestSourcePaths, currentProject.getTestCompileSourceRoots() );
2081         for ( Map.Entry<StringBuilder, List<String>> entry : map.entrySet() )
2082         {
2083             List<String> compileSourceRoots = entry.getValue();
2084             if ( compileSourceRoots != null )
2085             {
2086                 StringBuilder sourcePaths = entry.getKey();
2087                 for ( Iterator<String> i = compileSourceRoots.iterator(); i.hasNext(); )
2088                 {
2089                     if ( sourcePaths.length() > 0 )
2090                     {
2091                         sourcePaths.append( ',' );
2092                     }
2093                     sourcePaths.append( i.next() );
2094                 }
2095             }
2096         }
2097         final String sourcePath = analyzer.getProperty( Analyzer.SOURCEPATH );
2098         if ( sourcePath != null )
2099         {
2100             if ( sourcePath.contains(MAVEN_SOURCES) || sourcePath.contains(MAVEN_TEST_RESOURCES) )
2101             {
2102                 String combinedSource = StringUtils.replace( sourcePath, MAVEN_SOURCES, mavenSourcePaths.toString() );
2103                 combinedSource = StringUtils.replace( combinedSource, MAVEN_TEST_SOURCES, mavenTestSourcePaths.toString() );
2104                 if ( combinedSource.length() > 0 )
2105                 {
2106                     analyzer.setProperty( Analyzer.SOURCEPATH, combinedSource );
2107                 }
2108                 else
2109                 {
2110                     analyzer.unsetProperty( Analyzer.SOURCEPATH );
2111                 }
2112             }
2113             else if ( mavenSourcePaths.length() > 0 )
2114             {
2115                 log.warn( Analyzer.SOURCEPATH + ": overriding " + mavenSourcePaths + " with " + sourcePath + " (add "
2116                     + MAVEN_SOURCES + " if you want to include the maven sources)" );
2117             }
2118             else if ( mavenTestSourcePaths.length() > 0 )
2119             {
2120                 log.warn( Analyzer.SOURCEPATH + ": overriding " + mavenTestSourcePaths + " with " + sourcePath + " (add "
2121                         + MAVEN_TEST_SOURCES + " if you want to include the maven test sources)" );
2122             }
2123         }
2124         else if ( mavenSourcePaths.length() > 0 )
2125         {
2126             analyzer.setProperty( Analyzer.SOURCEPATH, mavenSourcePaths.toString() );
2127         }
2128     }
2129 
2130     /**
2131      * Downgrade the message "Classes found in the wrong directory" to a warning. This allows the plugin
2132      * to process a multi-release JAR (see JEP 238, http://openjdk.java.net/jeps/238).
2133      * 
2134      * Note that the version-specific paths will NOT be visible at runtime nor processed by bnd for
2135      * imported packages etc. This will not be possible until a runtime solution for multi-release
2136      * JARs exists in OSGi. This fix only allows these JARs to be processed at all and to be usable on
2137      * Java 8 (and below), and also on Java 9 where the version-specific customizations are optional.
2138      */
2139     protected static void includeJava9Fixups(MavenProject currentProject, Analyzer analyzer)
2140     {
2141         final String classesInWrongDirError = "Classes found in the wrong directory";
2142         final String newFixup = "Classes found in the wrong directory;"
2143             + Analyzer.FIXUPMESSAGES_IS_DIRECTIVE + "="
2144             + Analyzer.FIXUPMESSAGES_IS_WARNING;
2145 
2146         String fixups = analyzer.getProperty(Analyzer.FIXUPMESSAGES);
2147         if (fixups != null && !fixups.isEmpty()) {
2148             if (!fixups.contains(classesInWrongDirError)) {
2149                 fixups += "," + newFixup;
2150             }
2151         } else {
2152             fixups = newFixup;
2153         }
2154         analyzer.setProperty(Analyzer.FIXUPMESSAGES, fixups);
2155     }
2156 
2157     static class ClassPathItem {
2158         final String id;
2159         final File file;
2160 
2161         public ClassPathItem(String id, File file) {
2162             this.id = id;
2163             this.file = file;
2164         }
2165     }
2166 }