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