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