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      * @required
251      * @readonly
252      */
253     protected ArtifactResolver artifactResolver;
254 
255 
256     /**
257      * Local maven repository.
258      */
259     @Parameter( readonly = true, required = true, defaultValue = "${localRepository}" )
260     protected ArtifactRepository localRepository;
261 
262     /**
263      * Remote repositories which will be searched for source attachments.
264      */
265     @Parameter( readonly = true, required = true, defaultValue = "${project.remoteArtifactRepositories}" )
266     protected List<ArtifactRepository> remoteArtifactRepositories;
267 
268 
269 
270     /**
271      * Project types which this plugin supports.
272      */
273     @Parameter
274     protected List<String> supportedProjectTypes = Arrays.asList("jar", "bundle");
275 
276     /**
277      * The directory for the generated bundles.
278      */
279     @Parameter( defaultValue = "${project.build.outputDirectory}" )
280     private File outputDirectory;
281 
282     /**
283      * The directory for the generated JAR.
284      */
285     @Parameter( defaultValue = "${project.build.directory}" )
286     private String buildDirectory;
287 
288     /**
289      * The Maven project.
290      */
291     @Parameter( defaultValue = "${project}", readonly = true, required = true )
292     protected MavenProject project;
293 
294     /**
295      * The BND instructions for the bundle.
296      * Maven will expand property macros in these values. If you want to use a BND macro, you must double the dollar sign
297      * for the plugin to pass it to BND correctly. For example: <br>
298      * {@code <_consumer-policy>$${range;[===,+)}<code>}<code>{@code </_consumer-policy> }
299      */
300     @Parameter
301     private Map<String, String> instructions = new LinkedHashMap<String, String>();
302 
303     /**
304      * Use locally patched version for now.
305      */
306     private final Maven2OsgiConverter m_maven2OsgiConverter = new DefaultMaven2OsgiConverter();
307 
308     /**
309      * The archive configuration to use.
310      */
311     @Parameter
312     private MavenArchiveConfiguration archive; // accessed indirectly in JarPluginConfiguration
313 
314     @Parameter( defaultValue = "${session}", readonly = true, required = true )
315     private MavenSession m_mavenSession;
316 
317     @Component
318     protected BuildContext buildContext;
319     
320     private static final String MAVEN_SYMBOLICNAME = "maven-symbolicname";
321     private static final String MAVEN_RESOURCES = "{maven-resources}";
322     private static final String MAVEN_TEST_RESOURCES = "{maven-test-resources}";
323     private static final String LOCAL_PACKAGES = "{local-packages}";
324     private static final String MAVEN_SOURCES = "{maven-sources}";
325     private static final String MAVEN_TEST_SOURCES = "{maven-test-sources}";
326     private static final String BUNDLE_PLUGIN_EXTENSION = "BNDExtension-";
327     private static final String BUNDLE_PLUGIN_PREPEND_EXTENSION = "BNDPrependExtension-";
328 
329     private static final String[] EMPTY_STRING_ARRAY =
330         {};
331     private static final String[] DEFAULT_INCLUDES =
332         { "**/**" };
333 
334     private static final String NL = System.getProperty( "line.separator" );
335 
336 
337     protected Maven2OsgiConverter getMaven2OsgiConverter()
338     {
339         return m_maven2OsgiConverter;
340     }
341 
342 
343     protected MavenProject getProject()
344     {
345         return project;
346     }
347 
348     protected DependencyNode buildDependencyGraph( MavenProject mavenProject ) throws MojoExecutionException
349     {
350         DependencyNode dependencyGraph;
351         try
352         {
353             dependencyGraph = m_dependencyGraphBuilder.buildDependencyGraph( mavenProject, null );
354         }
355         catch ( DependencyGraphBuilderException e )
356         {
357             throw new MojoExecutionException( e.getMessage(), e );
358         }
359         return dependencyGraph;
360     }
361 
362     /**
363      * @see org.apache.maven.plugin.AbstractMojo#execute()
364      */
365     public void execute() throws MojoExecutionException
366     {
367         Properties properties = new Properties();
368         String projectType = getProject().getArtifact().getType();
369 
370         // ignore unsupported project types, useful when bundleplugin is configured in parent pom
371         if ( !supportedProjectTypes.contains( projectType ) )
372         {
373             getLog().warn(
374                 "Ignoring project type " + projectType + " - supportedProjectTypes = " + supportedProjectTypes );
375             return;
376         }
377 
378         execute( getProject(), buildDependencyGraph(getProject()), instructions, properties );
379     }
380 
381 
382     protected void execute( MavenProject currentProject, DependencyNode dependencyGraph, Map<String, String> originalInstructions, Properties properties )
383         throws MojoExecutionException
384     {
385         try
386         {
387             execute( currentProject, dependencyGraph, originalInstructions, properties, getClasspath( currentProject, dependencyGraph ) );
388         }
389         catch ( IOException e )
390         {
391             throw new MojoExecutionException( "Error calculating classpath for project " + currentProject, e );
392         }
393     }
394 
395 
396     /* transform directives from their XML form to the expected BND syntax (eg. _include becomes -include) */
397     protected static Map<String, String> transformDirectives( Map<String, String> originalInstructions )
398     {
399         Map<String, String> transformedInstructions = new LinkedHashMap<String, String>();
400         for ( Iterator<Map.Entry<String, String>> i = originalInstructions.entrySet().iterator(); i.hasNext(); )
401         {
402             Map.Entry<String, String> e = i.next();
403 
404             String key = e.getKey();
405             if ( key.startsWith( "_" ) )
406             {
407                 key = "-" + key.substring( 1 );
408             }
409 
410             String value = e.getValue();
411             if ( null == value )
412             {
413                 value = "";
414             }
415             else
416             {
417                 value = value.replaceAll( "\\p{Blank}*[\r\n]\\p{Blank}*", "" );
418             }
419 
420             if ( Analyzer.WAB.equals( key ) && value.length() == 0 )
421             {
422                 // provide useful default
423                 value = "src/main/webapp/";
424             }
425 
426             transformedInstructions.put( key, value );
427         }
428         return transformedInstructions;
429     }
430 
431 
432     protected boolean reportErrors( String prefix, Analyzer analyzer )
433     {
434         List<String> errors = analyzer.getErrors();
435         List<String> warnings = analyzer.getWarnings();
436 
437         for ( Iterator<String> w = warnings.iterator(); w.hasNext(); )
438         {
439             String msg = w.next();
440             getLog().warn( prefix + " : " + msg );
441         }
442 
443         boolean hasErrors = false;
444         String fileNotFound = "Input file does not exist: ";
445         for ( Iterator<String> e = errors.iterator(); e.hasNext(); )
446         {
447             String msg = e.next();
448             if ( msg.startsWith(fileNotFound) && msg.endsWith( "~" ) )
449             {
450                 // treat as warning; this error happens when you have duplicate entries in Include-Resource
451                 String duplicate = Processor.removeDuplicateMarker( msg.substring( fileNotFound.length() ) );
452                 getLog().warn( prefix + " : Duplicate path '" + duplicate + "' in Include-Resource" );
453             }
454             else
455             {
456                 getLog().error( prefix + " : " + msg );
457                 hasErrors = true;
458             }
459         }
460         return hasErrors;
461     }
462 
463 
464     protected void execute( MavenProject currentProject, DependencyNode dependencyGraph, Map<String, String> originalInstructions, Properties properties,
465         Jar[] classpath ) throws MojoExecutionException
466     {
467         try
468         {
469             File jarFile = new File( getBuildDirectory(), getBundleName( currentProject ) );
470             Builder builder = buildOSGiBundle( currentProject, dependencyGraph, originalInstructions, properties, classpath );
471             boolean hasErrors = reportErrors( "Bundle " + currentProject.getArtifact(), builder );
472             if ( hasErrors )
473             {
474                 String failok = builder.getProperty( "-failok" );
475                 if ( null == failok || "false".equalsIgnoreCase( failok ) )
476                 {
477                     jarFile.delete();
478 
479                     throw new MojoFailureException( "Error(s) found in bundle configuration" );
480                 }
481             }
482 
483             // attach bundle to maven project
484             jarFile.getParentFile().mkdirs();
485             builder.getJar().write( jarFile );
486 
487             Artifact mainArtifact = currentProject.getArtifact();
488 
489             if ( "bundle".equals( mainArtifact.getType() ) )
490             {
491                 // workaround for MNG-1682: force maven to install artifact using the "jar" handler
492                 mainArtifact.setArtifactHandler( m_artifactHandlerManager.getArtifactHandler( "jar" ) );
493             }
494 
495             boolean customClassifier = null != classifier && classifier.trim().length() > 0;
496             boolean customPackaging = null != packaging && packaging.trim().length() > 0;
497 
498             if ( customClassifier && customPackaging )
499             {
500                 m_projectHelper.attachArtifact( currentProject, packaging, classifier, jarFile );
501             }
502             else if ( customClassifier )
503             {
504                 m_projectHelper.attachArtifact( currentProject, jarFile, classifier );
505             }
506             else if ( customPackaging )
507             {
508                 m_projectHelper.attachArtifact( currentProject, packaging, jarFile );
509             }
510             else
511             {
512                 mainArtifact.setFile( jarFile );
513             }
514 
515             if ( unpackBundle )
516             {
517                 unpackBundle( jarFile );
518             }
519 
520             if ( manifestLocation != null )
521             {
522                 File outputFile = new File( manifestLocation, "MANIFEST.MF" );
523 
524                 try
525                 {
526                     ManifestPlugin.writeManifest( builder, outputFile, niceManifest, exportScr, scrLocation, buildContext, getLog() );
527                 }
528                 catch ( IOException e )
529                 {
530                     getLog().error( "Error trying to write Manifest to file " + outputFile, e );
531                 }
532             }
533 
534             // cleanup...
535             builder.close();
536         }
537         catch ( MojoFailureException e )
538         {
539             getLog().error( e.getLocalizedMessage() );
540             throw new MojoExecutionException( "Error(s) found in bundle configuration", e );
541         }
542         catch ( Exception e )
543         {
544             getLog().error( "An internal error occurred", e );
545             throw new MojoExecutionException( "Internal error in maven-bundle-plugin", e );
546         }
547     }
548 
549 
550     protected Builder getOSGiBuilder( MavenProject currentProject, Map<String, String> originalInstructions, Properties properties,
551         Jar[] classpath ) throws Exception
552     {
553         properties.putAll( getDefaultProperties( currentProject ) );
554         properties.putAll( transformDirectives( originalInstructions ) );
555 
556         // process overrides from project
557         final Map<String, String> addProps = new HashMap<String, String>();
558         final Iterator<Map.Entry<Object, Object>> iter = currentProject.getProperties().entrySet().iterator();
559         while ( iter.hasNext() )
560         {
561             final Map.Entry<Object, Object> entry = iter.next();
562             final String key = entry.getKey().toString();
563             if ( key.startsWith(BUNDLE_PLUGIN_EXTENSION) )
564             {
565                 final String oKey = key.substring(BUNDLE_PLUGIN_EXTENSION.length());
566                 final String currentValue = properties.getProperty(oKey);
567                 if ( currentValue == null )
568                 {
569                     addProps.put(oKey, entry.getValue().toString());
570                 }
571                 else
572                 {
573                     addProps.put(oKey, currentValue + ',' + entry.getValue());
574                 }
575             }
576             if ( key.startsWith(BUNDLE_PLUGIN_PREPEND_EXTENSION) )
577             {
578                 final String oKey = key.substring(BUNDLE_PLUGIN_PREPEND_EXTENSION.length());
579                 final String currentValue = properties.getProperty(oKey);
580                 if ( currentValue == null )
581                 {
582                     addProps.put(oKey, entry.getValue().toString());
583                 }
584                 else
585                 {
586                     addProps.put(oKey, entry.getValue() + "," + currentValue);
587                 }
588             }
589         }
590         properties.putAll( addProps );
591         final Iterator<String> keyIter = addProps.keySet().iterator();
592         while ( keyIter.hasNext() )
593         {
594             Object key = keyIter.next();
595             properties.remove(BUNDLE_PLUGIN_EXTENSION + key);
596             properties.remove(BUNDLE_PLUGIN_PREPEND_EXTENSION + key);
597         }
598 
599         if (properties.getProperty("Bundle-Activator") != null
600                 && properties.getProperty("Bundle-Activator").isEmpty())
601         {
602             properties.remove("Bundle-Activator");
603         }
604         if (properties.containsKey("-disable-plugin"))
605         {
606             String[] disabled = properties.remove("-disable-plugin").toString().replaceAll(" ", "").split(",");
607             String[] enabled = properties.getProperty(Analyzer.PLUGIN, "").replaceAll(" ", "").split(",");
608             Set<String> plugin = new LinkedHashSet<String>();
609             plugin.addAll(Arrays.asList(enabled));
610             plugin.removeAll(Arrays.asList(disabled));
611             StringBuilder sb = new StringBuilder();
612             for (String s : plugin)
613             {
614                 if (sb.length() > 0)
615                 {
616                     sb.append(",");
617                 }
618                 sb.append(s);
619             }
620             properties.setProperty(Analyzer.PLUGIN, sb.toString());
621         }
622 
623         Builder builder = new Builder();
624         synchronized ( BundlePlugin.class ) // protect setBase...getBndLastModified which uses static DateFormat
625         {
626             builder.setBase( getBase( currentProject ) );
627         }
628         builder.setProperties( sanitize( properties ) );
629         if ( classpath != null )
630         {
631             builder.setClasspath( classpath );
632         }
633 
634         return builder;
635     }
636 
637 
638     protected static Properties sanitize( Properties properties )
639     {
640         // convert any non-String keys/values to Strings
641         Properties sanitizedEntries = new Properties();
642         for ( Iterator<Map.Entry<Object,Object>> itr = properties.entrySet().iterator(); itr.hasNext(); )
643         {
644             Map.Entry<Object,Object> entry = itr.next();
645             if ( entry.getKey() instanceof String == false )
646             {
647                 String key = sanitize(entry.getKey());
648                 if ( !properties.containsKey( key ) )
649                 {
650                     sanitizedEntries.setProperty( key, sanitize( entry.getValue() ) );
651                 }
652                 itr.remove();
653             }
654             else if ( entry.getValue() instanceof String == false )
655             {
656                 entry.setValue( sanitize( entry.getValue() ) );
657             }
658         }
659         properties.putAll( sanitizedEntries );
660         return properties;
661     }
662 
663 
664     protected static String sanitize( Object value )
665     {
666         if ( value instanceof String )
667         {
668             return ( String ) value;
669         }
670         else if ( value instanceof Iterable )
671         {
672             String delim = "";
673             StringBuilder buf = new StringBuilder();
674             for ( Object i : ( Iterable<?> ) value )
675             {
676                 buf.append( delim ).append( i );
677                 delim = ", ";
678             }
679             return buf.toString();
680         }
681         else if ( value.getClass().isArray() )
682         {
683             String delim = "";
684             StringBuilder buf = new StringBuilder();
685             for ( int i = 0, len = Array.getLength( value ); i < len; i++ )
686             {
687                 buf.append( delim ).append( Array.get( value, i ) );
688                 delim = ", ";
689             }
690             return buf.toString();
691         }
692         else
693         {
694             return String.valueOf( value );
695         }
696     }
697 
698 
699     protected void addMavenInstructions( MavenProject currentProject, DependencyNode dependencyGraph, Builder builder ) throws Exception
700     {
701         if ( currentProject.getBasedir() != null )
702         {
703             // update BND instructions to add included Maven resources
704             includeMavenResources(currentProject, builder, getLog());
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 = new Analyzer().parseHeader( importPackages );
1192                 for ( Map.Entry<String, ? extends Map<String, String>> entry : values.entrySet() )
1193                 {
1194                     String pkg = entry.getKey();
1195                     Map<String, String> options = entry.getValue();
1196                     if ( !options.containsKey( "resolution:" ) && optionalPackages.contains( pkg ) )
1197                     {
1198                         options.put( "resolution:", "optional" );
1199                     }
1200                 }
1201                 String result = Processor.printClauses( values );
1202                 bundleManifest.getMainAttributes().putValue( "Import-Package", result );
1203             }
1204 
1205             jar.setManifest( bundleManifest );
1206         }
1207         catch ( Exception e )
1208         {
1209             getLog().warn( "Unable to merge Maven manifest: " + e.getLocalizedMessage() );
1210         }
1211 
1212         if ( addMavenDescriptor )
1213         {
1214             doMavenMetadata( currentProject, jar );
1215         }
1216 
1217         if ( getLog().isDebugEnabled() )
1218         {
1219             getLog().debug( "Final Manifest:" + NL + dumpManifest( jar.getManifest(), new StringBuilder() ) );
1220         }
1221 
1222         builder.setJar( jar );
1223     }
1224 
1225 
1226     protected static void mergeManifest( Instructions instructions, Manifest... manifests ) throws IOException
1227     {
1228         for ( int i = manifests.length - 2; i >= 0; i-- )
1229         {
1230             Manifest mergedManifest = manifests[i];
1231             Manifest manifest = manifests[i + 1];
1232             Attributes mergedMainAttributes = mergedManifest.getMainAttributes();
1233             Attributes mainAttributes = manifest.getMainAttributes();
1234             Attributes filteredMainAttributes = filterAttributes( instructions, mainAttributes, null );
1235             if ( !filteredMainAttributes.isEmpty() )
1236             {
1237                 mergeAttributes( mergedMainAttributes, filteredMainAttributes );
1238             }
1239             Map<String, Attributes> mergedEntries = mergedManifest.getEntries();
1240             Map<String, Attributes> entries = manifest.getEntries();
1241             for ( Map.Entry<String, Attributes> entry : entries.entrySet() )
1242             {
1243                 String name = entry.getKey();
1244                 Attributes attributes = entry.getValue();
1245                 Attributes filteredAttributes = filterAttributes( instructions, attributes, null );
1246                 if ( !filteredAttributes.isEmpty() )
1247                 {
1248                     Attributes mergedAttributes = mergedManifest.getAttributes( name );
1249                     if ( mergedAttributes != null)
1250                     {
1251                         mergeAttributes(mergedAttributes, filteredAttributes);
1252                     }
1253                     else
1254                     {
1255                         mergedEntries.put(name, filteredAttributes);
1256                     }
1257                 }
1258             }
1259         }
1260     }
1261 
1262 
1263     /**
1264      * @see Analyzer#filter
1265      */
1266     private static Attributes filterAttributes(Instructions instructions, Attributes source, Set<Instruction> nomatch) {
1267         Attributes result = new Attributes();
1268         Map<String, Object> keys = new TreeMap<String, Object>();
1269         for ( Object key : source.keySet() )
1270         {
1271             keys.put( key.toString(), key );
1272         }
1273 
1274         List<Instruction> filters = new ArrayList<Instruction>( instructions.keySet() );
1275         if (nomatch == null)
1276         {
1277             nomatch = Create.set();
1278         }
1279         for ( Instruction instruction : filters ) {
1280             boolean match = false;
1281             for (Iterator<Map.Entry<String, Object>> i = keys.entrySet().iterator(); i.hasNext();)
1282             {
1283                 Map.Entry<String, Object> entry = i.next();
1284                 String key = entry.getKey();
1285                 if ( instruction.matches( key ) )
1286                 {
1287                     match = true;
1288                     if (!instruction.isNegated()) {
1289                         Object name = entry.getValue();
1290                         Object value = source.get( name );
1291                         result.put( name, value );
1292                     }
1293                     i.remove(); // Can never match again for another pattern
1294                 }
1295             }
1296             if (!match && !instruction.isAny())
1297                 nomatch.add(instruction);
1298         }
1299 
1300         /*
1301          * Tricky. If we have umatched instructions they might indicate that we
1302          * want to have multiple decorators for the same package. So we check
1303          * the unmatched against the result list. If then then match and have
1304          * actually interesting properties then we merge them
1305          */
1306 
1307         for (Iterator<Instruction> i = nomatch.iterator(); i.hasNext();) {
1308             Instruction instruction = i.next();
1309 
1310             // We assume the user knows what he is
1311             // doing and inserted a literal. So
1312             // we ignore any not matched literals
1313             // #252, we should not be negated to make it a constant
1314             if (instruction.isLiteral() && !instruction.isNegated()) {
1315                 Object key = keys.get( instruction.getLiteral() );
1316                 if ( key != null )
1317                 {
1318                     Object value = source.get( key );
1319                     result.put( key, value );
1320                 }
1321                 i.remove();
1322                 continue;
1323             }
1324 
1325             // Not matching a negated instruction looks
1326             // like an error ... Though so, but
1327             // in the second phase of Export-Package
1328             // the !package will never match anymore.
1329             if (instruction.isNegated()) {
1330                 i.remove();
1331                 continue;
1332             }
1333 
1334             // An optional instruction should not generate
1335             // an error
1336             if (instruction.isOptional()) {
1337                 i.remove();
1338                 continue;
1339             }
1340         }
1341         return result;
1342     }
1343 
1344 
1345     private static void mergeAttributes( Attributes... attributesArray ) throws IOException
1346     {
1347         for ( int i = attributesArray.length - 2; i >= 0; i-- )
1348         {
1349             Attributes mergedAttributes = attributesArray[i];
1350             Attributes attributes = attributesArray[i + 1];
1351             for ( Map.Entry<Object, Object> entry : attributes.entrySet() )
1352             {
1353                 Object name = entry.getKey();
1354                 String value = (String) entry.getValue();
1355                 String oldValue = (String) mergedAttributes.put( name, value );
1356                 if ( oldValue != null )
1357                 {
1358                     Parameters mergedClauses = OSGiHeader.parseHeader(oldValue);
1359                     Parameters clauses = OSGiHeader.parseHeader( value );
1360                     if ( !mergedClauses.isEqual( clauses) )
1361                     {
1362                         for ( Map.Entry<String, Attrs> clauseEntry : clauses.entrySet() )
1363                         {
1364                             String clause = clauseEntry.getKey();
1365                             Attrs attrs = clauseEntry.getValue();
1366                             Attrs mergedAttrs = mergedClauses.get( clause );
1367                             if ( mergedAttrs == null)
1368                             {
1369                                 mergedClauses.put( clause, attrs );
1370                             }
1371                             else if ( !mergedAttrs.isEqual(attrs) )
1372                             {
1373                                 for ( Map.Entry<String,String> adentry : attrs.entrySet() )
1374                                 {
1375                                     String adname = adentry.getKey();
1376                                     String ad = adentry.getValue();
1377                                     if ( mergedAttrs.containsKey( adname ) )
1378                                     {
1379                                         Attrs.Type type = attrs.getType( adname );
1380                                         switch (type)
1381                                         {
1382                                             case VERSIONS:
1383                                             case STRINGS:
1384                                             case LONGS:
1385                                             case DOUBLES:
1386                                                 ExtList<String> mergedAd = ExtList.from( mergedAttrs.get( adname ) );
1387                                                 ExtList.from( ad ).addAll( ExtList.from( ad ) );
1388                                                 mergedAttrs.put(adname, mergedAd.join() );
1389                                                 break;
1390                                         }
1391                                     }
1392                                     else
1393                                     {
1394                                         mergedAttrs.put( adname, ad );
1395                                     }
1396                                 }
1397                             }
1398                         }
1399                         mergedAttributes.put( name, Processor.printClauses( mergedClauses ) );
1400                     }
1401                 }
1402             }
1403         }
1404     }
1405 
1406 
1407     protected Set<String> getOptionalPackages( MavenProject currentProject, DependencyNode dependencyGraph ) throws IOException, MojoExecutionException
1408     {
1409         ArrayList<Artifact> inscope = new ArrayList<Artifact>();
1410         final Collection<Artifact> artifacts = getSelectedDependencies( dependencyGraph, currentProject.getArtifacts() );
1411         for ( Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); )
1412         {
1413             Artifact artifact = it.next();
1414             if ( artifact.getArtifactHandler().isAddedToClasspath() )
1415             {
1416                 inscope.add( artifact );
1417             }
1418         }
1419 
1420         HashSet<String> optionalArtifactIds = new HashSet<String>();
1421         for ( Iterator<Artifact> it = inscope.iterator(); it.hasNext(); )
1422         {
1423             Artifact artifact = it.next();
1424             if ( artifact.isOptional() )
1425             {
1426                 String id = artifact.toString();
1427                 if ( artifact.getScope() != null )
1428                 {
1429                     // strip the scope...
1430                     id = id.replaceFirst( ":[^:]*$", "" );
1431                 }
1432                 optionalArtifactIds.add( id );
1433             }
1434 
1435         }
1436 
1437         HashSet<String> required = new HashSet<String>();
1438         HashSet<String> optional = new HashSet<String>();
1439         for ( Iterator<Artifact> it = inscope.iterator(); it.hasNext(); )
1440         {
1441             Artifact artifact = it.next();
1442             File file = getFile( artifact );
1443             if ( file == null )
1444             {
1445                 continue;
1446             }
1447 
1448             Jar jar = new Jar( artifact.getArtifactId(), file );
1449             if ( isTransitivelyOptional( optionalArtifactIds, artifact ) )
1450             {
1451                 optional.addAll( jar.getPackages() );
1452             }
1453             else
1454             {
1455                 required.addAll( jar.getPackages() );
1456             }
1457             jar.close();
1458         }
1459 
1460         optional.removeAll( required );
1461         return optional;
1462     }
1463 
1464 
1465     /**
1466      * Check to see if any dependency along the dependency trail of
1467      * the artifact is optional.
1468      *
1469      * @param artifact
1470      */
1471     protected boolean isTransitivelyOptional( HashSet<String> optionalArtifactIds, Artifact artifact )
1472     {
1473         List<String> trail = artifact.getDependencyTrail();
1474         for ( Iterator<String> iterator = trail.iterator(); iterator.hasNext(); )
1475         {
1476             String next = iterator.next();
1477             if ( optionalArtifactIds.contains( next ) )
1478             {
1479                 return true;
1480             }
1481         }
1482         return false;
1483     }
1484 
1485 
1486     private void unpackBundle( File jarFile )
1487     {
1488         File outputDir = getOutputDirectory();
1489         if ( null == outputDir )
1490         {
1491             outputDir = new File( getBuildDirectory(), "classes" );
1492         }
1493 
1494         try
1495         {
1496             /*
1497              * this directory must exist before unpacking, otherwise the plexus
1498              * unarchiver decides to use the current working directory instead!
1499              */
1500             if ( !outputDir.exists() )
1501             {
1502                 outputDir.mkdirs();
1503             }
1504 
1505             UnArchiver unArchiver = m_archiverManager.getUnArchiver( "jar" );
1506             unArchiver.setDestDirectory( outputDir );
1507             unArchiver.setSourceFile( jarFile );
1508             unArchiver.extract();
1509         }
1510         catch ( Exception e )
1511         {
1512             getLog().error( "Problem unpacking " + jarFile + " to " + outputDir, e );
1513         }
1514     }
1515 
1516 
1517     protected static String removeTagFromInstruction( String instruction, String tag )
1518     {
1519         StringBuffer buf = new StringBuffer();
1520 
1521         String[] clauses = instruction.split( "," );
1522         for ( int i = 0; i < clauses.length; i++ )
1523         {
1524             String clause = clauses[i].trim();
1525             if ( !tag.equals( clause ) )
1526             {
1527                 if ( buf.length() > 0 )
1528                 {
1529                     buf.append( ',' );
1530                 }
1531                 buf.append( clause );
1532             }
1533         }
1534 
1535         return buf.toString();
1536     }
1537 
1538 
1539     private static Map<String, String> getProperties( Model projectModel, String prefix )
1540     {
1541         Map<String, String> properties = new LinkedHashMap<String, String>();
1542         Method methods[] = Model.class.getDeclaredMethods();
1543         for ( int i = 0; i < methods.length; i++ )
1544         {
1545             String name = methods[i].getName();
1546             if ( name.startsWith( "get" ) )
1547             {
1548                 try
1549                 {
1550                     Object v = methods[i].invoke( projectModel, null );
1551                     if ( v != null )
1552                     {
1553                         name = prefix + Character.toLowerCase( name.charAt( 3 ) ) + name.substring( 4 );
1554                         if ( v.getClass().isArray() )
1555                             properties.put( name, Arrays.asList( ( Object[] ) v ).toString() );
1556                         else
1557                             properties.put( name, v.toString() );
1558 
1559                     }
1560                 }
1561                 catch ( Exception e )
1562                 {
1563                     // too bad
1564                 }
1565             }
1566         }
1567         return properties;
1568     }
1569 
1570 
1571     private static StringBuffer printLicenses( List<License> licenses )
1572     {
1573         if ( licenses == null || licenses.size() == 0 )
1574             return null;
1575         StringBuffer sb = new StringBuffer();
1576         String del = "";
1577         for ( Iterator<License> i = licenses.iterator(); i.hasNext(); )
1578         {
1579             License l = i.next();
1580             String url = l.getUrl();
1581             if ( url == null )
1582                 continue;
1583             sb.append( del );
1584             sb.append( url );
1585             del = ", ";
1586         }
1587         if ( sb.length() == 0 )
1588             return null;
1589         return sb;
1590     }
1591 
1592 
1593     /**
1594      * @param jar
1595      * @throws IOException
1596      */
1597     private void doMavenMetadata( MavenProject currentProject, Jar jar ) throws IOException
1598     {
1599         String path = "META-INF/maven/" + currentProject.getGroupId() + "/" + currentProject.getArtifactId();
1600 
1601         File pomFile = currentProject.getFile();
1602         if ( pomFile == null || !pomFile.exists() )
1603         {
1604             pomFile = new File( currentProject.getBasedir(), "pom.xml" );
1605         }
1606         if ( pomFile.exists() )
1607         {
1608             jar.putResource( path + "/pom.xml", new FileResource( pomFile ) );
1609         }
1610 
1611         Properties p = new Properties();
1612         p.put( "version", currentProject.getVersion() );
1613         p.put( "groupId", currentProject.getGroupId() );
1614         p.put( "artifactId", currentProject.getArtifactId() );
1615         ByteArrayOutputStream out = new ByteArrayOutputStream();
1616         p.store( out, "Generated by org.apache.felix.bundleplugin" );
1617         jar.putResource( path + "/pom.properties", new EmbeddedResource( out.toByteArray(), System.currentTimeMillis() ) );
1618     }
1619 
1620 
1621     protected Jar[] getClasspath( MavenProject currentProject, DependencyNode dependencyGraph ) throws IOException, MojoExecutionException
1622     {
1623         List<Jar> list = new ArrayList<Jar>();
1624 
1625         if ( getOutputDirectory() != null && getOutputDirectory().exists() )
1626         {
1627             list.add( new Jar( ".", getOutputDirectory() ) );
1628         }
1629 
1630         final Collection<Artifact> artifacts = getSelectedDependencies( dependencyGraph, currentProject.getArtifacts() );
1631         for ( Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); )
1632         {
1633             Artifact artifact = it.next();
1634             if ( artifact.getArtifactHandler().isAddedToClasspath() && !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
1635             {
1636                 File file = getFile( artifact );
1637                 if ( file == null )
1638                 {
1639                     getLog().warn(
1640                         "File is not available for artifact " + artifact + " in project "
1641                             + currentProject.getArtifact() );
1642                     continue;
1643                 }
1644                 Jar jar = new Jar( artifact.getArtifactId(), file );
1645                 list.add( jar );
1646             }
1647         }
1648         Jar[] cp = new Jar[list.size()];
1649         list.toArray( cp );
1650         return cp;
1651     }
1652 
1653 
1654     private Collection<Artifact> getSelectedDependencies( DependencyNode dependencyGraph, Collection<Artifact> artifacts ) throws MojoExecutionException
1655     {
1656         if ( null == excludeDependencies || excludeDependencies.length() == 0 )
1657         {
1658             return artifacts;
1659         }
1660         else if ( "true".equalsIgnoreCase( excludeDependencies ) )
1661         {
1662             return Collections.emptyList();
1663         }
1664 
1665         Collection<Artifact> selectedDependencies = new LinkedHashSet<Artifact>( artifacts );
1666         DependencyExcluder excluder = new DependencyExcluder( dependencyGraph, artifacts );
1667         excluder.processHeaders( excludeDependencies );
1668         selectedDependencies.removeAll( excluder.getExcludedArtifacts() );
1669 
1670         return selectedDependencies;
1671     }
1672 
1673 
1674     /**
1675      * Get the file for an Artifact
1676      *
1677      * @param artifact
1678      */
1679     protected File getFile( Artifact artifact )
1680     {
1681         return artifact.getFile();
1682     }
1683 
1684 
1685     private static void header( Properties properties, String key, Object value )
1686     {
1687         if ( value == null )
1688             return;
1689 
1690         if ( value instanceof Collection && ( ( Collection ) value ).isEmpty() )
1691             return;
1692 
1693         properties.put( key, value.toString().replaceAll( "[\r\n]", "" ) );
1694     }
1695 
1696 
1697     /**
1698      * Convert a Maven version into an OSGi compliant version
1699      *
1700      * @param version Maven version
1701      * @return the OSGi version
1702      */
1703     protected String convertVersionToOsgi( String version )
1704     {
1705         return getMaven2OsgiConverter().getVersion( version );
1706     }
1707 
1708 
1709     /**
1710      * TODO this should return getMaven2Osgi().getBundleFileName( project.getArtifact() )
1711      */
1712     protected String getBundleName( MavenProject currentProject )
1713     {
1714         String extension;
1715         try
1716         {
1717             extension = currentProject.getArtifact().getArtifactHandler().getExtension();
1718         }
1719         catch ( Throwable e )
1720         {
1721             extension = currentProject.getArtifact().getType();
1722         }
1723         if ( StringUtils.isEmpty( extension ) || "bundle".equals( extension ) || "pom".equals( extension ) )
1724         {
1725             extension = "jar"; // just in case maven gets confused
1726         }
1727         if ( null != classifier && classifier.trim().length() > 0 )
1728         {
1729             return finalName + '-' + classifier + '.' + extension;
1730         }
1731         return finalName + '.' + extension;
1732     }
1733 
1734 
1735     protected String getBuildDirectory()
1736     {
1737         return buildDirectory;
1738     }
1739 
1740 
1741     protected void setBuildDirectory( String _buildirectory )
1742     {
1743         buildDirectory = _buildirectory;
1744     }
1745 
1746 
1747     protected Properties getDefaultProperties( MavenProject currentProject )
1748     {
1749         Properties properties = new Properties();
1750 
1751         String bsn;
1752         try
1753         {
1754             bsn = getMaven2OsgiConverter().getBundleSymbolicName( currentProject.getArtifact() );
1755         }
1756         catch ( Exception e )
1757         {
1758             bsn = currentProject.getGroupId() + "." + currentProject.getArtifactId();
1759         }
1760 
1761         // Setup defaults
1762         properties.put( MAVEN_SYMBOLICNAME, bsn );
1763         properties.put( Analyzer.BUNDLE_SYMBOLICNAME, bsn );
1764         properties.put( Analyzer.IMPORT_PACKAGE, "*" );
1765         properties.put( Analyzer.BUNDLE_VERSION, getMaven2OsgiConverter().getVersion( currentProject.getVersion() ) );
1766 
1767         // remove the extraneous Include-Resource and Private-Package entries from generated manifest
1768         properties.put( Constants.REMOVEHEADERS, Analyzer.INCLUDE_RESOURCE + ',' + Analyzer.PRIVATE_PACKAGE );
1769 
1770         header( properties, Analyzer.BUNDLE_DESCRIPTION, currentProject.getDescription() );
1771         StringBuffer licenseText = printLicenses( currentProject.getLicenses() );
1772         if ( licenseText != null )
1773         {
1774             header( properties, Analyzer.BUNDLE_LICENSE, licenseText );
1775         }
1776         header( properties, Analyzer.BUNDLE_NAME, currentProject.getName() );
1777 
1778         if ( currentProject.getOrganization() != null )
1779         {
1780             if ( currentProject.getOrganization().getName() != null )
1781             {
1782                 String organizationName = currentProject.getOrganization().getName();
1783                 header( properties, Analyzer.BUNDLE_VENDOR, organizationName );
1784                 properties.put( "project.organization.name", organizationName );
1785                 properties.put( "pom.organization.name", organizationName );
1786             }
1787             if ( currentProject.getOrganization().getUrl() != null )
1788             {
1789                 String organizationUrl = currentProject.getOrganization().getUrl();
1790                 header( properties, Analyzer.BUNDLE_DOCURL, organizationUrl );
1791                 properties.put( "project.organization.url", organizationUrl );
1792                 properties.put( "pom.organization.url", organizationUrl );
1793             }
1794         }
1795 
1796         properties.putAll( currentProject.getProperties() );
1797         properties.putAll( currentProject.getModel().getProperties() );
1798 
1799         for ( Iterator<String> i = currentProject.getFilters().iterator(); i.hasNext(); )
1800         {
1801             File filterFile = new File( i.next() );
1802             if ( filterFile.isFile() )
1803             {
1804                 properties.putAll( PropertyUtils.loadProperties( filterFile ) );
1805             }
1806         }
1807 
1808         if ( m_mavenSession != null )
1809         {
1810             try
1811             {
1812                 // don't pass upper-case session settings to bnd as they end up in the manifest
1813                 Properties sessionProperties = m_mavenSession.getExecutionProperties();
1814                 for ( Enumeration<String> e = (Enumeration<String>) sessionProperties.propertyNames(); e.hasMoreElements(); )
1815                 {
1816                     String key = e.nextElement();
1817                     if ( key.length() > 0 && !Character.isUpperCase( key.charAt( 0 ) ) )
1818                     {
1819                         properties.put( key, sessionProperties.getProperty( key ) );
1820                     }
1821                 }
1822             }
1823             catch ( Exception e )
1824             {
1825                 getLog().warn( "Problem with Maven session properties: " + e.getLocalizedMessage() );
1826             }
1827         }
1828 
1829         properties.putAll( getProperties( currentProject.getModel(), "project.build." ) );
1830         properties.putAll( getProperties( currentProject.getModel(), "pom." ) );
1831         properties.putAll( getProperties( currentProject.getModel(), "project." ) );
1832 
1833         properties.put( "project.baseDir", getBase( currentProject ) );
1834         properties.put( "project.build.directory", getBuildDirectory() );
1835         properties.put( "project.build.outputdirectory", getOutputDirectory() );
1836 
1837         properties.put( "classifier", classifier == null ? "" : classifier );
1838 
1839         // Add default plugins
1840         header( properties, Analyzer.PLUGIN, BlueprintPlugin.class.getName() + ","
1841                                            + SpringXMLType.class.getName() + ","
1842                                            + JpaPlugin.class.getName() );
1843 
1844         return properties;
1845     }
1846 
1847 
1848     protected static File getBase( MavenProject currentProject )
1849     {
1850         return currentProject.getBasedir() != null ? currentProject.getBasedir() : new File( "" );
1851     }
1852 
1853 
1854     protected File getOutputDirectory()
1855     {
1856         return outputDirectory;
1857     }
1858 
1859 
1860     protected void setOutputDirectory( File _outputDirectory )
1861     {
1862         outputDirectory = _outputDirectory;
1863     }
1864 
1865 
1866     private static void addLocalPackages( File outputDirectory, Analyzer analyzer ) throws IOException
1867     {
1868         Packages packages = new Packages();
1869 
1870         if ( outputDirectory != null && outputDirectory.isDirectory() )
1871         {
1872             // scan classes directory for potential packages
1873             DirectoryScanner scanner = new DirectoryScanner();
1874             scanner.setBasedir( outputDirectory );
1875             scanner.setIncludes( new String[]
1876                 { "**/*.class" } );
1877 
1878             scanner.addDefaultExcludes();
1879             scanner.scan();
1880 
1881             String[] paths = scanner.getIncludedFiles();
1882             for ( int i = 0; i < paths.length; i++ )
1883             {
1884                 packages.put( analyzer.getPackageRef( getPackageName( paths[i] ) ) );
1885             }
1886         }
1887 
1888         Packages exportedPkgs = new Packages();
1889         Packages privatePkgs = new Packages();
1890 
1891         boolean noprivatePackages = "!*".equals( analyzer.getProperty( Analyzer.PRIVATE_PACKAGE ) );
1892 
1893         for ( PackageRef pkg : packages.keySet() )
1894         {
1895             // mark all source packages as private by default (can be overridden by export list)
1896             privatePkgs.put( pkg );
1897 
1898             // we can't export the default package (".") and we shouldn't export internal packages
1899             String fqn = pkg.getFQN();
1900             if ( noprivatePackages || !( ".".equals( fqn ) || fqn.contains( ".internal" ) || fqn.contains( ".impl" ) ) )
1901             {
1902                 exportedPkgs.put( pkg );
1903             }
1904         }
1905 
1906         Properties properties = analyzer.getProperties();
1907         String exported = properties.getProperty( Analyzer.EXPORT_PACKAGE );
1908         if ( exported == null )
1909         {
1910             if ( !properties.containsKey( Analyzer.EXPORT_CONTENTS ) )
1911             {
1912                 // no -exportcontents overriding the exports, so use our computed list
1913                 for ( Attrs attrs : exportedPkgs.values() )
1914                 {
1915                     attrs.put( Constants.SPLIT_PACKAGE_DIRECTIVE, "merge-first" );
1916                 }
1917                 properties.setProperty( Analyzer.EXPORT_PACKAGE, Processor.printClauses( exportedPkgs ) );
1918             }
1919             else
1920             {
1921                 // leave Export-Package empty (but non-null) as we have -exportcontents
1922                 properties.setProperty( Analyzer.EXPORT_PACKAGE, "" );
1923             }
1924         }
1925         else if ( exported.indexOf( LOCAL_PACKAGES ) >= 0 )
1926         {
1927             String newExported = StringUtils.replace( exported, LOCAL_PACKAGES, Processor.printClauses( exportedPkgs ) );
1928             properties.setProperty( Analyzer.EXPORT_PACKAGE, newExported );
1929         }
1930 
1931         String internal = properties.getProperty( Analyzer.PRIVATE_PACKAGE );
1932         if ( internal == null )
1933         {
1934             if ( !privatePkgs.isEmpty() )
1935             {
1936                 for ( Attrs attrs : privatePkgs.values() )
1937                 {
1938                     attrs.put( Constants.SPLIT_PACKAGE_DIRECTIVE, "merge-first" );
1939                 }
1940                 properties.setProperty( Analyzer.PRIVATE_PACKAGE, Processor.printClauses( privatePkgs ) );
1941             }
1942             else
1943             {
1944                 // if there are really no private packages then use "!*" as this will keep the Bnd Tool happy
1945                 properties.setProperty( Analyzer.PRIVATE_PACKAGE, "!*" );
1946             }
1947         }
1948         else if ( internal.indexOf( LOCAL_PACKAGES ) >= 0 )
1949         {
1950             String newInternal = StringUtils.replace( internal, LOCAL_PACKAGES, Processor.printClauses( privatePkgs ) );
1951             properties.setProperty( Analyzer.PRIVATE_PACKAGE, newInternal );
1952         }
1953     }
1954 
1955 
1956     private static String getPackageName( String filename )
1957     {
1958         int n = filename.lastIndexOf( File.separatorChar );
1959         return n < 0 ? "." : filename.substring( 0, n ).replace( File.separatorChar, '.' );
1960     }
1961 
1962 
1963     private static List<Resource> getMavenResources( MavenProject currentProject, boolean test )
1964     {
1965         List<Resource> resources = new ArrayList<Resource>( test ? currentProject.getTestResources() : currentProject.getResources() );
1966 
1967         if ( currentProject.getCompileSourceRoots() != null )
1968         {
1969             // also scan for any "packageinfo" files lurking in the source folders
1970             final List<String> packageInfoIncludes = Collections.singletonList( "**/packageinfo" );
1971             for ( Iterator<String> i = currentProject.getCompileSourceRoots().iterator(); i.hasNext(); )
1972             {
1973                 String sourceRoot = i.next();
1974                 Resource packageInfoResource = new Resource();
1975                 packageInfoResource.setDirectory( sourceRoot );
1976                 packageInfoResource.setIncludes( packageInfoIncludes );
1977                 resources.add( packageInfoResource );
1978             }
1979         }
1980 
1981         return resources;
1982     }
1983 
1984 
1985     protected static String getMavenResourcePaths( MavenProject currentProject, boolean test )
1986     {
1987         final String basePath = currentProject.getBasedir().getAbsolutePath();
1988 
1989         Set<String> pathSet = new LinkedHashSet<String>();
1990         for ( Iterator<Resource> i = getMavenResources( currentProject, test ).iterator(); i.hasNext(); )
1991         {
1992             Resource resource = i.next();
1993 
1994             final String sourcePath = resource.getDirectory();
1995             final String targetPath = resource.getTargetPath();
1996 
1997             // ignore empty or non-local resources
1998             if ( new File( sourcePath ).exists() && ( ( targetPath == null ) || ( targetPath.indexOf( ".." ) < 0 ) ) )
1999             {
2000                 DirectoryScanner scanner = new DirectoryScanner();
2001 
2002                 scanner.setBasedir( sourcePath );
2003                 if ( resource.getIncludes() != null && !resource.getIncludes().isEmpty() )
2004                 {
2005                     scanner.setIncludes( resource.getIncludes().toArray( EMPTY_STRING_ARRAY ) );
2006                 }
2007                 else
2008                 {
2009                     scanner.setIncludes( DEFAULT_INCLUDES );
2010                 }
2011 
2012                 if ( resource.getExcludes() != null && !resource.getExcludes().isEmpty() )
2013                 {
2014                     scanner.setExcludes( resource.getExcludes().toArray( EMPTY_STRING_ARRAY ) );
2015                 }
2016 
2017                 scanner.addDefaultExcludes();
2018                 scanner.scan();
2019 
2020                 List<String> includedFiles = Arrays.asList( scanner.getIncludedFiles() );
2021 
2022                 for ( Iterator<String> j = includedFiles.iterator(); j.hasNext(); )
2023                 {
2024                     String name = j.next();
2025                     String path = sourcePath + '/' + name;
2026 
2027                     // make relative to project
2028                     if ( path.startsWith( basePath ) )
2029                     {
2030                         if ( path.length() == basePath.length() )
2031                         {
2032                             path = ".";
2033                         }
2034                         else
2035                         {
2036                             path = path.substring( basePath.length() + 1 );
2037                         }
2038                     }
2039 
2040                     // replace windows backslash with a slash
2041                     // this is a workaround for a problem with bnd 0.0.189
2042                     if ( File.separatorChar != '/' )
2043                     {
2044                         name = name.replace( File.separatorChar, '/' );
2045                         path = path.replace( File.separatorChar, '/' );
2046                     }
2047 
2048                     // copy to correct place
2049                     path = name + '=' + path;
2050                     if ( targetPath != null )
2051                     {
2052                         path = targetPath + '/' + path;
2053                     }
2054 
2055                     // use Bnd filtering?
2056                     if ( resource.isFiltering() )
2057                     {
2058                         path = '{' + path + '}';
2059                     }
2060 
2061                     pathSet.add( path );
2062                 }
2063             }
2064         }
2065 
2066         StringBuffer resourcePaths = new StringBuffer();
2067         for ( Iterator<String> i = pathSet.iterator(); i.hasNext(); )
2068         {
2069             resourcePaths.append( i.next() );
2070             if ( i.hasNext() )
2071             {
2072                 resourcePaths.append( ',' );
2073             }
2074         }
2075 
2076         return resourcePaths.toString();
2077     }
2078 
2079 
2080     protected Collection<Artifact> getEmbeddableArtifacts( MavenProject currentProject, DependencyNode dependencyGraph, Analyzer analyzer )
2081         throws MojoExecutionException
2082     {
2083         final Collection<Artifact> artifacts;
2084 
2085         String embedTransitive = analyzer.getProperty( DependencyEmbedder.EMBED_TRANSITIVE );
2086         if ( Boolean.valueOf( embedTransitive ).booleanValue() )
2087         {
2088             // includes transitive dependencies
2089             artifacts = currentProject.getArtifacts();
2090         }
2091         else
2092         {
2093             // only includes direct dependencies
2094             artifacts = currentProject.getDependencyArtifacts();
2095         }
2096 
2097         return getSelectedDependencies( dependencyGraph, artifacts );
2098     }
2099 
2100 
2101     protected static void addMavenSourcePath( MavenProject currentProject, Analyzer analyzer, Log log )
2102     {
2103         // pass maven source paths onto BND analyzer
2104         StringBuilder mavenSourcePaths = new StringBuilder();
2105         StringBuilder mavenTestSourcePaths = new StringBuilder();
2106         Map<StringBuilder, List<String>> map = new HashMap<StringBuilder, List<String>>(2);
2107         map.put(mavenSourcePaths, currentProject.getCompileSourceRoots() );
2108         map.put(mavenTestSourcePaths, currentProject.getTestCompileSourceRoots() );
2109         for ( Map.Entry<StringBuilder, List<String>> entry : map.entrySet() )
2110         {
2111             List<String> compileSourceRoots = entry.getValue();
2112             if ( compileSourceRoots != null )
2113             {
2114                 StringBuilder sourcePaths = entry.getKey();
2115                 for ( Iterator<String> i = compileSourceRoots.iterator(); i.hasNext(); )
2116                 {
2117                     if ( sourcePaths.length() > 0 )
2118                     {
2119                         sourcePaths.append( ',' );
2120                     }
2121                     sourcePaths.append( i.next() );
2122                 }
2123             }
2124         }
2125         final String sourcePath = analyzer.getProperty( Analyzer.SOURCEPATH );
2126         if ( sourcePath != null )
2127         {
2128             if ( sourcePath.contains(MAVEN_SOURCES) || sourcePath.contains(MAVEN_TEST_RESOURCES) )
2129             {
2130                 String combinedSource = StringUtils.replace( sourcePath, MAVEN_SOURCES, mavenSourcePaths.toString() );
2131                 combinedSource = StringUtils.replace( combinedSource, MAVEN_TEST_SOURCES, mavenTestSourcePaths.toString() );
2132                 if ( combinedSource.length() > 0 )
2133                 {
2134                     analyzer.setProperty( Analyzer.SOURCEPATH, combinedSource );
2135                 }
2136                 else
2137                 {
2138                     analyzer.unsetProperty( Analyzer.SOURCEPATH );
2139                 }
2140             }
2141             else if ( mavenSourcePaths.length() > 0 )
2142             {
2143                 log.warn( Analyzer.SOURCEPATH + ": overriding " + mavenSourcePaths + " with " + sourcePath + " (add "
2144                     + MAVEN_SOURCES + " if you want to include the maven sources)" );
2145             }
2146             else if ( mavenTestSourcePaths.length() > 0 )
2147             {
2148                 log.warn( Analyzer.SOURCEPATH + ": overriding " + mavenTestSourcePaths + " with " + sourcePath + " (add "
2149                         + MAVEN_TEST_SOURCES + " if you want to include the maven test sources)" );
2150             }
2151         }
2152         else if ( mavenSourcePaths.length() > 0 )
2153         {
2154             analyzer.setProperty( Analyzer.SOURCEPATH, mavenSourcePaths.toString() );
2155         }
2156     }
2157 }