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.lang.reflect.Array;
29  import java.lang.reflect.Method;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Collection;
33  import java.util.Collections;
34  import java.util.Enumeration;
35  import java.util.HashSet;
36  import java.util.Iterator;
37  import java.util.LinkedHashMap;
38  import java.util.LinkedHashSet;
39  import java.util.List;
40  import java.util.Map;
41  import java.util.Properties;
42  import java.util.Set;
43  import java.util.TreeSet;
44  import java.util.jar.Attributes;
45  import java.util.jar.Manifest;
46  
47  import org.apache.maven.archiver.ManifestSection;
48  import org.apache.maven.archiver.MavenArchiveConfiguration;
49  import org.apache.maven.archiver.MavenArchiver;
50  import org.apache.maven.artifact.Artifact;
51  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
52  import org.apache.maven.execution.MavenSession;
53  import org.apache.maven.model.License;
54  import org.apache.maven.model.Model;
55  import org.apache.maven.model.Resource;
56  import org.apache.maven.plugin.AbstractMojo;
57  import org.apache.maven.plugin.MojoExecutionException;
58  import org.apache.maven.plugin.MojoFailureException;
59  import org.apache.maven.plugin.logging.Log;
60  import org.apache.maven.project.MavenProject;
61  import org.apache.maven.project.MavenProjectHelper;
62  import org.apache.maven.shared.osgi.DefaultMaven2OsgiConverter;
63  import org.apache.maven.shared.osgi.Maven2OsgiConverter;
64  import org.codehaus.plexus.archiver.UnArchiver;
65  import org.codehaus.plexus.archiver.manager.ArchiverManager;
66  import org.codehaus.plexus.util.DirectoryScanner;
67  import org.codehaus.plexus.util.FileUtils;
68  import org.codehaus.plexus.util.StringUtils;
69  
70  import aQute.lib.osgi.Analyzer;
71  import aQute.lib.osgi.Builder;
72  import aQute.lib.osgi.Constants;
73  import aQute.lib.osgi.EmbeddedResource;
74  import aQute.lib.osgi.FileResource;
75  import aQute.lib.osgi.Jar;
76  import aQute.lib.osgi.Processor;
77  import aQute.lib.spring.SpringXMLType;
78  
79  
80  /**
81   * Create an OSGi bundle from Maven project
82   *
83   * @goal bundle
84   * @phase package
85   * @requiresDependencyResolution test
86   * @description build an OSGi bundle jar
87   * @threadSafe
88   */
89  public class BundlePlugin extends AbstractMojo
90  {
91      /**
92       * Directory where the manifest will be written
93       *
94       * @parameter expression="${manifestLocation}" default-value="${project.build.outputDirectory}/META-INF"
95       */
96      protected File manifestLocation;
97  
98      /**
99       * File where the BND instructions will be dumped
100      *
101      * @parameter expression="${dumpInstructions}"
102      */
103     protected File dumpInstructions;
104 
105     /**
106      * File where the BND class-path will be dumped
107      *
108      * @parameter expression="${dumpClasspath}"
109      */
110     protected File dumpClasspath;
111 
112     /**
113      * When true, unpack the bundle contents to the outputDirectory
114      *
115      * @parameter expression="${unpackBundle}"
116      */
117     protected boolean unpackBundle;
118 
119     /**
120      * Comma separated list of artifactIds to exclude from the dependency classpath passed to BND (use "true" to exclude everything)
121      *
122      * @parameter expression="${excludeDependencies}"
123      */
124     protected String excludeDependencies;
125 
126     /**
127      * Classifier type of the bundle to be installed.  For example, "jdk14".
128      * Defaults to none which means this is the project's main bundle.
129      *
130      * @parameter
131      */
132     protected String classifier;
133 
134     /**
135      * @component
136      */
137     private MavenProjectHelper m_projectHelper;
138 
139     /**
140      * @component
141      */
142     private ArchiverManager m_archiverManager;
143 
144     /**
145      * @component
146      */
147     private ArtifactHandlerManager m_artifactHandlerManager;
148 
149     /**
150      * Project types which this plugin supports.
151      *
152      * @parameter
153      */
154     protected List supportedProjectTypes = Arrays.asList( new String[]
155         { "jar", "bundle" } );
156 
157     /**
158      * The directory for the generated bundles.
159      *
160      * @parameter expression="${project.build.outputDirectory}"
161      * @required
162      */
163     private File outputDirectory;
164 
165     /**
166      * The directory for the generated JAR.
167      *
168      * @parameter expression="${project.build.directory}"
169      * @required
170      */
171     private String buildDirectory;
172 
173     /**
174      * The Maven project.
175      *
176      * @parameter expression="${project}"
177      * @required
178      * @readonly
179      */
180     private MavenProject project;
181 
182     /**
183      * The BND instructions for the bundle.
184      *
185      * @parameter
186      */
187     private Map instructions = new LinkedHashMap();
188 
189     /**
190      * Use locally patched version for now.
191      */
192     private Maven2OsgiConverter m_maven2OsgiConverter = new DefaultMaven2OsgiConverter();
193 
194     /**
195      * The archive configuration to use.
196      *
197      * @parameter
198      */
199     private MavenArchiveConfiguration archive; // accessed indirectly in JarPluginConfiguration
200 
201     /**
202      * @parameter default-value="${session}"
203      * @required
204      * @readonly
205      */
206     private MavenSession m_mavenSession;
207 
208     private static final String MAVEN_SYMBOLICNAME = "maven-symbolicname";
209     private static final String MAVEN_RESOURCES = "{maven-resources}";
210     private static final String LOCAL_PACKAGES = "{local-packages}";
211     private static final String MAVEN_SOURCES = "{maven-sources}";
212 
213     private static final String[] EMPTY_STRING_ARRAY =
214         {};
215     private static final String[] DEFAULT_INCLUDES =
216         { "**/**" };
217 
218     private static final String NL = System.getProperty( "line.separator" );
219 
220 
221     protected Maven2OsgiConverter getMaven2OsgiConverter()
222     {
223         return m_maven2OsgiConverter;
224     }
225 
226 
227     protected void setMaven2OsgiConverter( Maven2OsgiConverter maven2OsgiConverter )
228     {
229         m_maven2OsgiConverter = maven2OsgiConverter;
230     }
231 
232 
233     protected MavenProject getProject()
234     {
235         return project;
236     }
237 
238 
239     /**
240      * @see org.apache.maven.plugin.AbstractMojo#execute()
241      */
242     public void execute() throws MojoExecutionException
243     {
244         Properties properties = new Properties();
245         String projectType = getProject().getArtifact().getType();
246 
247         // ignore unsupported project types, useful when bundleplugin is configured in parent pom
248         if ( !supportedProjectTypes.contains( projectType ) )
249         {
250             getLog().warn(
251                 "Ignoring project type " + projectType + " - supportedProjectTypes = " + supportedProjectTypes );
252             return;
253         }
254 
255         execute( getProject(), instructions, properties );
256     }
257 
258 
259     protected void execute( MavenProject currentProject, Map originalInstructions, Properties properties )
260         throws MojoExecutionException
261     {
262         try
263         {
264             execute( currentProject, originalInstructions, properties, getClasspath( currentProject ) );
265         }
266         catch ( IOException e )
267         {
268             throw new MojoExecutionException( "Error calculating classpath for project " + currentProject, e );
269         }
270     }
271 
272 
273     /* transform directives from their XML form to the expected BND syntax (eg. _include becomes -include) */
274     protected static Map transformDirectives( Map originalInstructions )
275     {
276         Map transformedInstructions = new LinkedHashMap();
277         for ( Iterator i = originalInstructions.entrySet().iterator(); i.hasNext(); )
278         {
279             Map.Entry e = ( Map.Entry ) i.next();
280 
281             String key = ( String ) e.getKey();
282             if ( key.startsWith( "_" ) )
283             {
284                 key = "-" + key.substring( 1 );
285             }
286 
287             String value = ( String ) e.getValue();
288             if ( null == value )
289             {
290                 value = "";
291             }
292             else
293             {
294                 value = value.replaceAll( "\\p{Blank}*[\r\n]\\p{Blank}*", "" );
295             }
296 
297             if ( Analyzer.WAB.equals( key ) && value.length() == 0 )
298             {
299                 // provide useful default
300                 value = "src/main/webapp/";
301             }
302 
303             transformedInstructions.put( key, value );
304         }
305         return transformedInstructions;
306     }
307 
308 
309     protected boolean reportErrors( String prefix, Analyzer analyzer )
310     {
311         List errors = analyzer.getErrors();
312         List warnings = analyzer.getWarnings();
313 
314         for ( Iterator w = warnings.iterator(); w.hasNext(); )
315         {
316             String msg = ( String ) w.next();
317             getLog().warn( prefix + " : " + msg );
318         }
319 
320         boolean hasErrors = false;
321         String fileNotFound = "Input file does not exist: ";
322         for ( Iterator e = errors.iterator(); e.hasNext(); )
323         {
324             String msg = ( String ) e.next();
325             if ( msg.startsWith( fileNotFound ) && msg.endsWith( "~" ) )
326             {
327                 // treat as warning; this error happens when you have duplicate entries in Include-Resource
328                 String duplicate = Processor.removeDuplicateMarker( msg.substring( fileNotFound.length() ) );
329                 getLog().warn( prefix + " : Duplicate path '" + duplicate + "' in Include-Resource" );
330             }
331             else
332             {
333                 getLog().error( prefix + " : " + msg );
334                 hasErrors = true;
335             }
336         }
337         return hasErrors;
338     }
339 
340 
341     protected void execute( MavenProject currentProject, Map originalInstructions, Properties properties,
342         Jar[] classpath ) throws MojoExecutionException
343     {
344         try
345         {
346             File jarFile = new File( getBuildDirectory(), getBundleName( currentProject ) );
347             Builder builder = buildOSGiBundle( currentProject, originalInstructions, properties, classpath );
348             boolean hasErrors = reportErrors( "Bundle " + currentProject.getArtifact(), builder );
349             if ( hasErrors )
350             {
351                 String failok = builder.getProperty( "-failok" );
352                 if ( null == failok || "false".equalsIgnoreCase( failok ) )
353                 {
354                     jarFile.delete();
355 
356                     throw new MojoFailureException( "Error(s) found in bundle configuration" );
357                 }
358             }
359 
360             // attach bundle to maven project
361             jarFile.getParentFile().mkdirs();
362             builder.getJar().write( jarFile );
363 
364             Artifact mainArtifact = currentProject.getArtifact();
365 
366             if ( "bundle".equals( mainArtifact.getType() ) )
367             {
368                 // workaround for MNG-1682: force maven to install artifact using the "jar" handler
369                 mainArtifact.setArtifactHandler( m_artifactHandlerManager.getArtifactHandler( "jar" ) );
370             }
371 
372             if ( null == classifier || classifier.trim().length() == 0 )
373             {
374                 mainArtifact.setFile( jarFile );
375             }
376             else
377             {
378                 m_projectHelper.attachArtifact( currentProject, jarFile, classifier );
379             }
380 
381             if ( unpackBundle )
382             {
383                 unpackBundle( jarFile );
384             }
385 
386             if ( manifestLocation != null )
387             {
388                 File outputFile = new File( manifestLocation, "MANIFEST.MF" );
389 
390                 try
391                 {
392                     Manifest manifest = builder.getJar().getManifest();
393                     ManifestPlugin.writeManifest( manifest, outputFile );
394                 }
395                 catch ( IOException e )
396                 {
397                     getLog().error( "Error trying to write Manifest to file " + outputFile, e );
398                 }
399             }
400 
401             // cleanup...
402             builder.close();
403         }
404         catch ( MojoFailureException e )
405         {
406             getLog().error( e.getLocalizedMessage() );
407             throw new MojoExecutionException( "Error(s) found in bundle configuration", e );
408         }
409         catch ( Exception e )
410         {
411             getLog().error( "An internal error occurred", e );
412             throw new MojoExecutionException( "Internal error in maven-bundle-plugin", e );
413         }
414     }
415 
416 
417     protected Builder getOSGiBuilder( MavenProject currentProject, Map originalInstructions, Properties properties,
418         Jar[] classpath ) throws Exception
419     {
420         properties.putAll( getDefaultProperties( currentProject ) );
421         properties.putAll( transformDirectives( originalInstructions ) );
422 
423         Builder builder = new Builder();
424         builder.setBase( getBase( currentProject ) );
425         builder.setProperties( sanitize( properties ) );
426         if ( classpath != null )
427         {
428             builder.setClasspath( classpath );
429         }
430 
431         return builder;
432     }
433 
434 
435     protected static Properties sanitize( Properties properties )
436     {
437         // convert any non-String keys/values to Strings
438         Properties sanitizedEntries = new Properties();
439         for ( Iterator itr = properties.entrySet().iterator(); itr.hasNext(); )
440         {
441             Map.Entry entry = ( Map.Entry ) itr.next();
442             if ( entry.getKey() instanceof String == false )
443             {
444                 String key = sanitize( entry.getKey() );
445                 if ( !properties.containsKey( key ) )
446                 {
447                     sanitizedEntries.setProperty( key, sanitize( entry.getValue() ) );
448                 }
449                 itr.remove();
450             }
451             else if ( entry.getValue() instanceof String == false )
452             {
453                 entry.setValue( sanitize( entry.getValue() ) );
454             }
455         }
456         properties.putAll( sanitizedEntries );
457         return properties;
458     }
459 
460 
461     protected static String sanitize( Object value )
462     {
463         if ( value instanceof String )
464         {
465             return ( String ) value;
466         }
467         else if ( value instanceof Iterable )
468         {
469             String delim = "";
470             StringBuilder buf = new StringBuilder();
471             for ( Object i : ( Iterable<?> ) value )
472             {
473                 buf.append( delim ).append( i );
474                 delim = ", ";
475             }
476             return buf.toString();
477         }
478         else if ( value.getClass().isArray() )
479         {
480             String delim = "";
481             StringBuilder buf = new StringBuilder();
482             for ( int i = 0, len = Array.getLength( value ); i < len; i++ )
483             {
484                 buf.append( delim ).append( Array.get( value, i ) );
485                 delim = ", ";
486             }
487             return buf.toString();
488         }
489         else
490         {
491             return String.valueOf( value );
492         }
493     }
494 
495 
496     protected void addMavenInstructions( MavenProject currentProject, Builder builder ) throws Exception
497     {
498         if ( currentProject.getBasedir() != null )
499         {
500             // update BND instructions to add included Maven resources
501             includeMavenResources( currentProject, builder, getLog() );
502 
503             // calculate default export/private settings based on sources
504             addLocalPackages( outputDirectory, builder );
505 
506             // tell BND where the current project source resides
507             addMavenSourcePath( currentProject, builder, getLog() );
508         }
509 
510         // update BND instructions to embed selected Maven dependencies
511         Collection embeddableArtifacts = getEmbeddableArtifacts( currentProject, builder );
512         new DependencyEmbedder( getLog(), embeddableArtifacts ).processHeaders( builder );
513 
514         if ( dumpInstructions != null || getLog().isDebugEnabled() )
515         {
516             StringBuilder buf = new StringBuilder();
517             getLog().debug( "BND Instructions:" + NL + dumpInstructions( builder.getProperties(), buf ) );
518             if ( dumpInstructions != null )
519             {
520                 getLog().info( "Writing BND instructions to " + dumpInstructions );
521                 dumpInstructions.getParentFile().mkdirs();
522                 FileUtils.fileWrite( dumpInstructions, "# BND instructions" + NL + buf );
523             }
524         }
525 
526         if ( dumpClasspath != null || getLog().isDebugEnabled() )
527         {
528             StringBuilder buf = new StringBuilder();
529             getLog().debug( "BND Classpath:" + NL + dumpClasspath( builder.getClasspath(), buf ) );
530             if ( dumpClasspath != null )
531             {
532                 getLog().info( "Writing BND classpath to " + dumpClasspath );
533                 dumpClasspath.getParentFile().mkdirs();
534                 FileUtils.fileWrite( dumpClasspath, "# BND classpath" + NL + buf );
535             }
536         }
537     }
538 
539 
540     protected Builder buildOSGiBundle( MavenProject currentProject, Map originalInstructions, Properties properties,
541         Jar[] classpath ) throws Exception
542     {
543         Builder builder = getOSGiBuilder( currentProject, originalInstructions, properties, classpath );
544 
545         addMavenInstructions( currentProject, builder );
546 
547         builder.build();
548 
549         mergeMavenManifest( currentProject, builder );
550 
551         return builder;
552     }
553 
554 
555     protected static StringBuilder dumpInstructions( Properties properties, StringBuilder buf )
556     {
557         try
558         {
559             buf.append( "#-----------------------------------------------------------------------" + NL );
560             Properties stringProperties = new Properties();
561             for ( Enumeration e = properties.propertyNames(); e.hasMoreElements(); )
562             {
563                 // we can only store String properties
564                 String key = ( String ) e.nextElement();
565                 String value = properties.getProperty( key );
566                 if ( value != null )
567                 {
568                     stringProperties.setProperty( key, value );
569                 }
570             }
571             ByteArrayOutputStream out = new ByteArrayOutputStream();
572             stringProperties.store( out, null ); // properties encoding is 8859_1
573             buf.append( out.toString( "8859_1" ) );
574             buf.append( "#-----------------------------------------------------------------------" + NL );
575         }
576         catch ( Throwable e )
577         {
578             // ignore...
579         }
580         return buf;
581     }
582 
583 
584     protected static StringBuilder dumpClasspath( List classpath, StringBuilder buf )
585     {
586         try
587         {
588             buf.append( "#-----------------------------------------------------------------------" + NL );
589             buf.append( "-classpath:\\" + NL );
590             for ( Iterator i = classpath.iterator(); i.hasNext(); )
591             {
592                 File path = ( ( Jar ) i.next() ).getSource();
593                 if ( path != null )
594                 {
595                     buf.append( ' ' + path.toString() + ( i.hasNext() ? ",\\" : "" ) + NL );
596                 }
597             }
598             buf.append( "#-----------------------------------------------------------------------" + NL );
599         }
600         catch ( Throwable e )
601         {
602             // ignore...
603         }
604         return buf;
605     }
606 
607 
608     protected static StringBuilder dumpManifest( Manifest manifest, StringBuilder buf )
609     {
610         try
611         {
612             buf.append( "#-----------------------------------------------------------------------" + NL );
613             ByteArrayOutputStream out = new ByteArrayOutputStream();
614             Jar.writeManifest( manifest, out ); // manifest encoding is UTF8
615             buf.append( out.toString( "UTF8" ) );
616             buf.append( "#-----------------------------------------------------------------------" + NL );
617         }
618         catch ( Throwable e )
619         {
620             // ignore...
621         }
622         return buf;
623     }
624 
625 
626     protected static void includeMavenResources( MavenProject currentProject, Analyzer analyzer, Log log )
627     {
628         // pass maven resource paths onto BND analyzer
629         final String mavenResourcePaths = getMavenResourcePaths( currentProject );
630         final String includeResource = ( String ) analyzer.getProperty( Analyzer.INCLUDE_RESOURCE );
631         if ( includeResource != null )
632         {
633             if ( includeResource.indexOf( MAVEN_RESOURCES ) >= 0 )
634             {
635                 // if there is no maven resource path, we do a special treatment and replace
636                 // every occurance of MAVEN_RESOURCES and a following comma with an empty string
637                 if ( mavenResourcePaths.length() == 0 )
638                 {
639                     String cleanedResource = removeTagFromInstruction( includeResource, MAVEN_RESOURCES );
640                     if ( cleanedResource.length() > 0 )
641                     {
642                         analyzer.setProperty( Analyzer.INCLUDE_RESOURCE, cleanedResource );
643                     }
644                     else
645                     {
646                         analyzer.unsetProperty( Analyzer.INCLUDE_RESOURCE );
647                     }
648                 }
649                 else
650                 {
651                     String combinedResource = StringUtils
652                         .replace( includeResource, MAVEN_RESOURCES, mavenResourcePaths );
653                     analyzer.setProperty( Analyzer.INCLUDE_RESOURCE, combinedResource );
654                 }
655             }
656             else if ( mavenResourcePaths.length() > 0 )
657             {
658                 log.warn( Analyzer.INCLUDE_RESOURCE + ": overriding " + mavenResourcePaths + " with " + includeResource
659                     + " (add " + MAVEN_RESOURCES + " if you want to include the maven resources)" );
660             }
661         }
662         else if ( mavenResourcePaths.length() > 0 )
663         {
664             analyzer.setProperty( Analyzer.INCLUDE_RESOURCE, mavenResourcePaths );
665         }
666     }
667 
668 
669     protected void mergeMavenManifest( MavenProject currentProject, Builder builder ) throws Exception
670     {
671         Jar jar = builder.getJar();
672 
673         if ( getLog().isDebugEnabled() )
674         {
675             getLog().debug( "BND Manifest:" + NL + dumpManifest( jar.getManifest(), new StringBuilder() ) );
676         }
677 
678         boolean addMavenDescriptor = currentProject.getBasedir() != null;
679 
680         try
681         {
682             /*
683              * Grab customized manifest entries from the maven-jar-plugin configuration
684              */
685             MavenArchiveConfiguration archiveConfig = JarPluginConfiguration.getArchiveConfiguration( currentProject );
686             String mavenManifestText = new MavenArchiver().getManifest( currentProject, archiveConfig ).toString();
687             addMavenDescriptor = addMavenDescriptor && archiveConfig.isAddMavenDescriptor();
688 
689             Manifest mavenManifest = new Manifest();
690 
691             // First grab the external manifest file (if specified and different to target location)
692             File externalManifestFile = archiveConfig.getManifestFile();
693             if ( null != externalManifestFile && externalManifestFile.exists()
694                 && !externalManifestFile.equals( new File( manifestLocation, "MANIFEST.MF" ) ) )
695             {
696                 InputStream mis = new FileInputStream( externalManifestFile );
697                 mavenManifest.read( mis );
698                 mis.close();
699             }
700 
701             // Then apply customized entries from the jar plugin; note: manifest encoding is UTF8
702             mavenManifest.read( new ByteArrayInputStream( mavenManifestText.getBytes( "UTF8" ) ) );
703 
704             if ( !archiveConfig.isManifestSectionsEmpty() )
705             {
706                 /*
707                  * Add customized manifest sections (for some reason MavenArchiver doesn't do this for us)
708                  */
709                 List sections = archiveConfig.getManifestSections();
710                 for ( Iterator i = sections.iterator(); i.hasNext(); )
711                 {
712                     ManifestSection section = ( ManifestSection ) i.next();
713                     Attributes attributes = new Attributes();
714 
715                     if ( !section.isManifestEntriesEmpty() )
716                     {
717                         Map entries = section.getManifestEntries();
718                         for ( Iterator j = entries.entrySet().iterator(); j.hasNext(); )
719                         {
720                             Map.Entry entry = ( Map.Entry ) j.next();
721                             attributes.putValue( ( String ) entry.getKey(), ( String ) entry.getValue() );
722                         }
723                     }
724 
725                     mavenManifest.getEntries().put( section.getName(), attributes );
726                 }
727             }
728 
729             Attributes mainMavenAttributes = mavenManifest.getMainAttributes();
730             mainMavenAttributes.putValue( "Created-By", "Apache Maven Bundle Plugin" );
731 
732             String[] removeHeaders = builder.getProperty( Constants.REMOVEHEADERS, "" ).split( "," );
733 
734             // apply -removeheaders to the custom manifest
735             for ( int i = 0; i < removeHeaders.length; i++ )
736             {
737                 for ( Iterator j = mainMavenAttributes.keySet().iterator(); j.hasNext(); )
738                 {
739                     if ( j.next().toString().matches( removeHeaders[i].trim() ) )
740                     {
741                         j.remove();
742                     }
743                 }
744             }
745 
746             /*
747              * Overlay generated bundle manifest with customized entries
748              */
749             Manifest bundleManifest = jar.getManifest();
750             bundleManifest.getMainAttributes().putAll( mainMavenAttributes );
751             bundleManifest.getEntries().putAll( mavenManifest.getEntries() );
752 
753             // adjust the import package attributes so that optional dependencies use
754             // optional resolution.
755             String importPackages = bundleManifest.getMainAttributes().getValue( "Import-Package" );
756             if ( importPackages != null )
757             {
758                 Set optionalPackages = getOptionalPackages( currentProject );
759 
760                 Map<String, Map<String, String>> values = new Analyzer().parseHeader( importPackages );
761                 for ( Map.Entry<String, Map<String, String>> entry : values.entrySet() )
762                 {
763                     String pkg = entry.getKey();
764                     Map<String, String> options = entry.getValue();
765                     if ( !options.containsKey( "resolution:" ) && optionalPackages.contains( pkg ) )
766                     {
767                         options.put( "resolution:", "optional" );
768                     }
769                 }
770                 String result = Processor.printClauses( values );
771                 bundleManifest.getMainAttributes().putValue( "Import-Package", result );
772             }
773 
774             jar.setManifest( bundleManifest );
775         }
776         catch ( Exception e )
777         {
778             getLog().warn( "Unable to merge Maven manifest: " + e.getLocalizedMessage() );
779         }
780 
781         if ( addMavenDescriptor )
782         {
783             doMavenMetadata( currentProject, jar );
784         }
785 
786         if ( getLog().isDebugEnabled() )
787         {
788             getLog().debug( "Final Manifest:" + NL + dumpManifest( jar.getManifest(), new StringBuilder() ) );
789         }
790 
791         builder.setJar( jar );
792     }
793 
794 
795     protected Set getOptionalPackages( MavenProject currentProject ) throws IOException, MojoExecutionException
796     {
797         ArrayList inscope = new ArrayList();
798         final Collection artifacts = getSelectedDependencies( currentProject.getArtifacts() );
799         for ( Iterator it = artifacts.iterator(); it.hasNext(); )
800         {
801             Artifact artifact = ( Artifact ) it.next();
802             if ( artifact.getArtifactHandler().isAddedToClasspath() )
803             {
804                 if ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
805                 {
806                     inscope.add( artifact );
807                 }
808             }
809         }
810 
811         HashSet optionalArtifactIds = new HashSet();
812         for ( Iterator it = inscope.iterator(); it.hasNext(); )
813         {
814             Artifact artifact = ( Artifact ) it.next();
815             if ( artifact.isOptional() )
816             {
817                 String id = artifact.toString();
818                 if ( artifact.getScope() != null )
819                 {
820                     // strip the scope...
821                     id = id.replaceFirst( ":[^:]*$", "" );
822                 }
823                 optionalArtifactIds.add( id );
824             }
825 
826         }
827 
828         HashSet required = new HashSet();
829         HashSet optional = new HashSet();
830         for ( Iterator it = inscope.iterator(); it.hasNext(); )
831         {
832             Artifact artifact = ( Artifact ) it.next();
833             File file = getFile( artifact );
834             if ( file == null )
835             {
836                 continue;
837             }
838 
839             Jar jar = new Jar( artifact.getArtifactId(), file );
840             if ( isTransitivelyOptional( optionalArtifactIds, artifact ) )
841             {
842                 optional.addAll( jar.getPackages() );
843             }
844             else
845             {
846                 required.addAll( jar.getPackages() );
847             }
848             jar.close();
849         }
850 
851         optional.removeAll( required );
852         return optional;
853     }
854 
855 
856     /**
857      * Check to see if any dependency along the dependency trail of
858      * the artifact is optional.
859      *
860      * @param artifact
861      */
862     protected boolean isTransitivelyOptional( HashSet optionalArtifactIds, Artifact artifact )
863     {
864         List trail = artifact.getDependencyTrail();
865         for ( Iterator iterator = trail.iterator(); iterator.hasNext(); )
866         {
867             String next = ( String ) iterator.next();
868             if ( optionalArtifactIds.contains( next ) )
869             {
870                 return true;
871             }
872         }
873         return false;
874     }
875 
876 
877     private void unpackBundle( File jarFile )
878     {
879         File outputDir = getOutputDirectory();
880         if ( null == outputDir )
881         {
882             outputDir = new File( getBuildDirectory(), "classes" );
883         }
884 
885         try
886         {
887             /*
888              * this directory must exist before unpacking, otherwise the plexus
889              * unarchiver decides to use the current working directory instead!
890              */
891             if ( !outputDir.exists() )
892             {
893                 outputDir.mkdirs();
894             }
895 
896             UnArchiver unArchiver = m_archiverManager.getUnArchiver( "jar" );
897             unArchiver.setDestDirectory( outputDir );
898             unArchiver.setSourceFile( jarFile );
899             unArchiver.extract();
900         }
901         catch ( Exception e )
902         {
903             getLog().error( "Problem unpacking " + jarFile + " to " + outputDir, e );
904         }
905     }
906 
907 
908     protected static String removeTagFromInstruction( String instruction, String tag )
909     {
910         StringBuffer buf = new StringBuffer();
911 
912         String[] clauses = instruction.split( "," );
913         for ( int i = 0; i < clauses.length; i++ )
914         {
915             String clause = clauses[i].trim();
916             if ( !tag.equals( clause ) )
917             {
918                 if ( buf.length() > 0 )
919                 {
920                     buf.append( ',' );
921                 }
922                 buf.append( clause );
923             }
924         }
925 
926         return buf.toString();
927     }
928 
929 
930     private static Map getProperties( Model projectModel, String prefix )
931     {
932         Map properties = new LinkedHashMap();
933         Method methods[] = Model.class.getDeclaredMethods();
934         for ( int i = 0; i < methods.length; i++ )
935         {
936             String name = methods[i].getName();
937             if ( name.startsWith( "get" ) )
938             {
939                 try
940                 {
941                     Object v = methods[i].invoke( projectModel, null );
942                     if ( v != null )
943                     {
944                         name = prefix + Character.toLowerCase( name.charAt( 3 ) ) + name.substring( 4 );
945                         if ( v.getClass().isArray() )
946                             properties.put( name, Arrays.asList( ( Object[] ) v ).toString() );
947                         else
948                             properties.put( name, v );
949 
950                     }
951                 }
952                 catch ( Exception e )
953                 {
954                     // too bad
955                 }
956             }
957         }
958         return properties;
959     }
960 
961 
962     private static StringBuffer printLicenses( List licenses )
963     {
964         if ( licenses == null || licenses.size() == 0 )
965             return null;
966         StringBuffer sb = new StringBuffer();
967         String del = "";
968         for ( Iterator i = licenses.iterator(); i.hasNext(); )
969         {
970             License l = ( License ) i.next();
971             String url = l.getUrl();
972             if ( url == null )
973                 continue;
974             sb.append( del );
975             sb.append( url );
976             del = ", ";
977         }
978         if ( sb.length() == 0 )
979             return null;
980         return sb;
981     }
982 
983 
984     /**
985      * @param jar
986      * @throws IOException
987      */
988     private void doMavenMetadata( MavenProject currentProject, Jar jar ) throws IOException
989     {
990         String path = "META-INF/maven/" + currentProject.getGroupId() + "/" + currentProject.getArtifactId();
991         File pomFile = new File( currentProject.getBasedir(), "pom.xml" );
992         jar.putResource( path + "/pom.xml", new FileResource( pomFile ) );
993 
994         Properties p = new Properties();
995         p.put( "version", currentProject.getVersion() );
996         p.put( "groupId", currentProject.getGroupId() );
997         p.put( "artifactId", currentProject.getArtifactId() );
998         ByteArrayOutputStream out = new ByteArrayOutputStream();
999         p.store( out, "Generated by org.apache.felix.bundleplugin" );
1000         jar.putResource( path + "/pom.properties", new EmbeddedResource( out.toByteArray(), System.currentTimeMillis() ) );
1001     }
1002 
1003 
1004     protected Jar[] getClasspath( MavenProject currentProject ) throws IOException, MojoExecutionException
1005     {
1006         List list = new ArrayList();
1007 
1008         if ( getOutputDirectory() != null && getOutputDirectory().exists() )
1009         {
1010             list.add( new Jar( ".", getOutputDirectory() ) );
1011         }
1012 
1013         final Collection artifacts = getSelectedDependencies( currentProject.getArtifacts() );
1014         for ( Iterator it = artifacts.iterator(); it.hasNext(); )
1015         {
1016             Artifact artifact = ( Artifact ) it.next();
1017             if ( artifact.getArtifactHandler().isAddedToClasspath() )
1018             {
1019                 if ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
1020                 {
1021                     File file = getFile( artifact );
1022                     if ( file == null )
1023                     {
1024                         getLog().warn(
1025                             "File is not available for artifact " + artifact + " in project "
1026                                 + currentProject.getArtifact() );
1027                         continue;
1028                     }
1029                     Jar jar = new Jar( artifact.getArtifactId(), file );
1030                     list.add( jar );
1031                 }
1032             }
1033         }
1034         Jar[] cp = new Jar[list.size()];
1035         list.toArray( cp );
1036         return cp;
1037     }
1038 
1039 
1040     private Collection getSelectedDependencies( Collection artifacts ) throws MojoExecutionException
1041     {
1042         if ( null == excludeDependencies || excludeDependencies.length() == 0 )
1043         {
1044             return artifacts;
1045         }
1046         else if ( "true".equalsIgnoreCase( excludeDependencies ) )
1047         {
1048             return Collections.EMPTY_LIST;
1049         }
1050 
1051         Collection selectedDependencies = new LinkedHashSet( artifacts );
1052         DependencyExcluder excluder = new DependencyExcluder( artifacts );
1053         excluder.processHeaders( excludeDependencies );
1054         selectedDependencies.removeAll( excluder.getExcludedArtifacts() );
1055 
1056         return selectedDependencies;
1057     }
1058 
1059 
1060     /**
1061      * Get the file for an Artifact
1062      *
1063      * @param artifact
1064      */
1065     protected File getFile( Artifact artifact )
1066     {
1067         return artifact.getFile();
1068     }
1069 
1070 
1071     private static void header( Properties properties, String key, Object value )
1072     {
1073         if ( value == null )
1074             return;
1075 
1076         if ( value instanceof Collection && ( ( Collection ) value ).isEmpty() )
1077             return;
1078 
1079         properties.put( key, value.toString().replaceAll( "[\r\n]", "" ) );
1080     }
1081 
1082 
1083     /**
1084      * Convert a Maven version into an OSGi compliant version
1085      *
1086      * @param version Maven version
1087      * @return the OSGi version
1088      */
1089     protected String convertVersionToOsgi( String version )
1090     {
1091         return getMaven2OsgiConverter().getVersion( version );
1092     }
1093 
1094 
1095     /**
1096      * TODO this should return getMaven2Osgi().getBundleFileName( project.getArtifact() )
1097      */
1098     protected String getBundleName( MavenProject currentProject )
1099     {
1100         String extension;
1101         try
1102         {
1103             extension = currentProject.getArtifact().getArtifactHandler().getExtension();
1104         }
1105         catch ( Throwable e )
1106         {
1107             extension = currentProject.getArtifact().getType();
1108         }
1109         if ( StringUtils.isEmpty( extension ) || "bundle".equals( extension ) || "pom".equals( extension ) )
1110         {
1111             extension = "jar"; // just in case maven gets confused
1112         }
1113         String finalName = currentProject.getBuild().getFinalName();
1114         if ( null != classifier && classifier.trim().length() > 0 )
1115         {
1116             return finalName + '-' + classifier + '.' + extension;
1117         }
1118         return finalName + '.' + extension;
1119     }
1120 
1121 
1122     protected String getBuildDirectory()
1123     {
1124         return buildDirectory;
1125     }
1126 
1127 
1128     protected void setBuildDirectory( String _buildirectory )
1129     {
1130         buildDirectory = _buildirectory;
1131     }
1132 
1133 
1134     protected Properties getDefaultProperties( MavenProject currentProject )
1135     {
1136         Properties properties = new Properties();
1137 
1138         String bsn;
1139         try
1140         {
1141             bsn = getMaven2OsgiConverter().getBundleSymbolicName( currentProject.getArtifact() );
1142         }
1143         catch ( Exception e )
1144         {
1145             bsn = currentProject.getGroupId() + "." + currentProject.getArtifactId();
1146         }
1147 
1148         // Setup defaults
1149         properties.put( MAVEN_SYMBOLICNAME, bsn );
1150         properties.put( Analyzer.BUNDLE_SYMBOLICNAME, bsn );
1151         properties.put( Analyzer.IMPORT_PACKAGE, "*" );
1152         properties.put( Analyzer.BUNDLE_VERSION, getMaven2OsgiConverter().getVersion( currentProject.getVersion() ) );
1153 
1154         // remove the extraneous Include-Resource and Private-Package entries from generated manifest
1155         properties.put( Constants.REMOVEHEADERS, Analyzer.INCLUDE_RESOURCE + ',' + Analyzer.PRIVATE_PACKAGE );
1156 
1157         header( properties, Analyzer.BUNDLE_DESCRIPTION, currentProject.getDescription() );
1158         StringBuffer licenseText = printLicenses( currentProject.getLicenses() );
1159         if ( licenseText != null )
1160         {
1161             header( properties, Analyzer.BUNDLE_LICENSE, licenseText );
1162         }
1163         header( properties, Analyzer.BUNDLE_NAME, currentProject.getName() );
1164 
1165         if ( currentProject.getOrganization() != null )
1166         {
1167             if ( currentProject.getOrganization().getName() != null )
1168             {
1169                 String organizationName = currentProject.getOrganization().getName();
1170                 header( properties, Analyzer.BUNDLE_VENDOR, organizationName );
1171                 properties.put( "project.organization.name", organizationName );
1172                 properties.put( "pom.organization.name", organizationName );
1173             }
1174             if ( currentProject.getOrganization().getUrl() != null )
1175             {
1176                 String organizationUrl = currentProject.getOrganization().getUrl();
1177                 header( properties, Analyzer.BUNDLE_DOCURL, organizationUrl );
1178                 properties.put( "project.organization.url", organizationUrl );
1179                 properties.put( "pom.organization.url", organizationUrl );
1180             }
1181         }
1182 
1183         properties.putAll( currentProject.getProperties() );
1184         properties.putAll( currentProject.getModel().getProperties() );
1185         if ( m_mavenSession != null )
1186         {
1187             try
1188             {
1189                 // don't pass upper-case session settings to bnd as they end up in the manifest
1190                 Properties sessionProperties = m_mavenSession.getExecutionProperties();
1191                 for ( Enumeration e = sessionProperties.propertyNames(); e.hasMoreElements(); )
1192                 {
1193                     String key = ( String ) e.nextElement();
1194                     if ( key.length() > 0 && !Character.isUpperCase( key.charAt( 0 ) ) )
1195                     {
1196                         properties.put( key, sessionProperties.getProperty( key ) );
1197                     }
1198                 }
1199             }
1200             catch ( Exception e )
1201             {
1202                 getLog().warn( "Problem with Maven session properties: " + e.getLocalizedMessage() );
1203             }
1204         }
1205 
1206         properties.putAll( getProperties( currentProject.getModel(), "project.build." ) );
1207         properties.putAll( getProperties( currentProject.getModel(), "pom." ) );
1208         properties.putAll( getProperties( currentProject.getModel(), "project." ) );
1209 
1210         properties.put( "project.baseDir", getBase( currentProject ) );
1211         properties.put( "project.build.directory", getBuildDirectory() );
1212         properties.put( "project.build.outputdirectory", getOutputDirectory() );
1213 
1214         properties.put( "classifier", classifier == null ? "" : classifier );
1215 
1216         // Add default plugins
1217         header( properties, Analyzer.PLUGIN, BlueprintPlugin.class.getName() + "," + SpringXMLType.class.getName() );
1218 
1219         return properties;
1220     }
1221 
1222 
1223     protected static File getBase( MavenProject currentProject )
1224     {
1225         return currentProject.getBasedir() != null ? currentProject.getBasedir() : new File( "" );
1226     }
1227 
1228 
1229     protected File getOutputDirectory()
1230     {
1231         return outputDirectory;
1232     }
1233 
1234 
1235     protected void setOutputDirectory( File _outputDirectory )
1236     {
1237         outputDirectory = _outputDirectory;
1238     }
1239 
1240 
1241     private static void addLocalPackages( File outputDirectory, Analyzer analyzer )
1242     {
1243         Collection packages = new TreeSet();
1244 
1245         if ( outputDirectory != null && outputDirectory.isDirectory() )
1246         {
1247             // scan classes directory for potential packages
1248             DirectoryScanner scanner = new DirectoryScanner();
1249             scanner.setBasedir( outputDirectory );
1250             scanner.setIncludes( new String[]
1251                 { "**/*.class" } );
1252 
1253             scanner.addDefaultExcludes();
1254             scanner.scan();
1255 
1256             String[] paths = scanner.getIncludedFiles();
1257             for ( int i = 0; i < paths.length; i++ )
1258             {
1259                 packages.add( getPackageName( paths[i] ) );
1260             }
1261         }
1262 
1263         StringBuffer exportedPkgs = new StringBuffer();
1264         StringBuffer privatePkgs = new StringBuffer();
1265 
1266         boolean noprivatePackages = "!*".equals( analyzer.getProperty( Analyzer.PRIVATE_PACKAGE ) );
1267 
1268         for ( Iterator i = packages.iterator(); i.hasNext(); )
1269         {
1270             String pkg = ( String ) i.next();
1271 
1272             // mark all source packages as private by default (can be overridden by export list)
1273             if ( privatePkgs.length() > 0 )
1274             {
1275                 privatePkgs.append( ';' );
1276             }
1277             privatePkgs.append( pkg );
1278 
1279             // we can't export the default package (".") and we shouldn't export internal packages 
1280             if ( noprivatePackages || !( ".".equals( pkg ) || pkg.contains( ".internal" ) || pkg.contains( ".impl" ) ) )
1281             {
1282                 if ( exportedPkgs.length() > 0 )
1283                 {
1284                     exportedPkgs.append( ';' );
1285                 }
1286                 exportedPkgs.append( pkg );
1287             }
1288         }
1289 
1290         if ( analyzer.getProperty( Analyzer.EXPORT_PACKAGE ) == null )
1291         {
1292             if ( analyzer.getProperty( Analyzer.EXPORT_CONTENTS ) == null )
1293             {
1294                 // no -exportcontents overriding the exports, so use our computed list
1295                 analyzer.setProperty( Analyzer.EXPORT_PACKAGE, exportedPkgs + ";-split-package:=merge-first" );
1296             }
1297             else
1298             {
1299                 // leave Export-Package empty (but non-null) as we have -exportcontents
1300                 analyzer.setProperty( Analyzer.EXPORT_PACKAGE, "" );
1301             }
1302         }
1303         else
1304         {
1305             String exported = analyzer.getProperty( Analyzer.EXPORT_PACKAGE );
1306             if ( exported.indexOf( LOCAL_PACKAGES ) >= 0 )
1307             {
1308                 String newExported = StringUtils.replace( exported, LOCAL_PACKAGES, exportedPkgs.toString() );
1309                 analyzer.setProperty( Analyzer.EXPORT_PACKAGE, newExported );
1310 
1311             }
1312         }
1313 
1314         String internal = analyzer.getProperty( Analyzer.PRIVATE_PACKAGE );
1315         if ( internal == null )
1316         {
1317             if ( privatePkgs.length() > 0 )
1318             {
1319                 analyzer.setProperty( Analyzer.PRIVATE_PACKAGE, privatePkgs + ";-split-package:=merge-first" );
1320             }
1321             else
1322             {
1323                 // if there are really no private packages then use "!*" as this will keep the Bnd Tool happy
1324                 analyzer.setProperty( Analyzer.PRIVATE_PACKAGE, "!*" );
1325             }
1326         }
1327         else if ( internal.indexOf( LOCAL_PACKAGES ) >= 0 )
1328         {
1329             String newInternal = StringUtils.replace( internal, LOCAL_PACKAGES, privatePkgs.toString() );
1330             analyzer.setProperty( Analyzer.PRIVATE_PACKAGE, newInternal );
1331         }
1332     }
1333 
1334 
1335     private static String getPackageName( String filename )
1336     {
1337         int n = filename.lastIndexOf( File.separatorChar );
1338         return n < 0 ? "." : filename.substring( 0, n ).replace( File.separatorChar, '.' );
1339     }
1340 
1341 
1342     private static List getMavenResources( MavenProject currentProject )
1343     {
1344         List resources = new ArrayList( currentProject.getResources() );
1345 
1346         if ( currentProject.getCompileSourceRoots() != null )
1347         {
1348             // also scan for any "packageinfo" files lurking in the source folders
1349             List packageInfoIncludes = Collections.singletonList( "**/packageinfo" );
1350             for ( Iterator i = currentProject.getCompileSourceRoots().iterator(); i.hasNext(); )
1351             {
1352                 String sourceRoot = ( String ) i.next();
1353                 Resource packageInfoResource = new Resource();
1354                 packageInfoResource.setDirectory( sourceRoot );
1355                 packageInfoResource.setIncludes( packageInfoIncludes );
1356                 resources.add( packageInfoResource );
1357             }
1358         }
1359 
1360         return resources;
1361     }
1362 
1363 
1364     protected static String getMavenResourcePaths( MavenProject currentProject )
1365     {
1366         final String basePath = currentProject.getBasedir().getAbsolutePath();
1367 
1368         Set pathSet = new LinkedHashSet();
1369         for ( Iterator i = getMavenResources( currentProject ).iterator(); i.hasNext(); )
1370         {
1371             Resource resource = ( Resource ) i.next();
1372 
1373             final String sourcePath = resource.getDirectory();
1374             final String targetPath = resource.getTargetPath();
1375 
1376             // ignore empty or non-local resources
1377             if ( new File( sourcePath ).exists() && ( ( targetPath == null ) || ( targetPath.indexOf( ".." ) < 0 ) ) )
1378             {
1379                 DirectoryScanner scanner = new DirectoryScanner();
1380 
1381                 scanner.setBasedir( sourcePath );
1382                 if ( resource.getIncludes() != null && !resource.getIncludes().isEmpty() )
1383                 {
1384                     scanner.setIncludes( ( String[] ) resource.getIncludes().toArray( EMPTY_STRING_ARRAY ) );
1385                 }
1386                 else
1387                 {
1388                     scanner.setIncludes( DEFAULT_INCLUDES );
1389                 }
1390 
1391                 if ( resource.getExcludes() != null && !resource.getExcludes().isEmpty() )
1392                 {
1393                     scanner.setExcludes( ( String[] ) resource.getExcludes().toArray( EMPTY_STRING_ARRAY ) );
1394                 }
1395 
1396                 scanner.addDefaultExcludes();
1397                 scanner.scan();
1398 
1399                 List includedFiles = Arrays.asList( scanner.getIncludedFiles() );
1400 
1401                 for ( Iterator j = includedFiles.iterator(); j.hasNext(); )
1402                 {
1403                     String name = ( String ) j.next();
1404                     String path = sourcePath + '/' + name;
1405 
1406                     // make relative to project
1407                     if ( path.startsWith( basePath ) )
1408                     {
1409                         if ( path.length() == basePath.length() )
1410                         {
1411                             path = ".";
1412                         }
1413                         else
1414                         {
1415                             path = path.substring( basePath.length() + 1 );
1416                         }
1417                     }
1418 
1419                     // replace windows backslash with a slash
1420                     // this is a workaround for a problem with bnd 0.0.189
1421                     if ( File.separatorChar != '/' )
1422                     {
1423                         name = name.replace( File.separatorChar, '/' );
1424                         path = path.replace( File.separatorChar, '/' );
1425                     }
1426 
1427                     // copy to correct place
1428                     path = name + '=' + path;
1429                     if ( targetPath != null )
1430                     {
1431                         path = targetPath + '/' + path;
1432                     }
1433 
1434                     // use Bnd filtering?
1435                     if ( resource.isFiltering() )
1436                     {
1437                         path = '{' + path + '}';
1438                     }
1439 
1440                     pathSet.add( path );
1441                 }
1442             }
1443         }
1444 
1445         StringBuffer resourcePaths = new StringBuffer();
1446         for ( Iterator i = pathSet.iterator(); i.hasNext(); )
1447         {
1448             resourcePaths.append( i.next() );
1449             if ( i.hasNext() )
1450             {
1451                 resourcePaths.append( ',' );
1452             }
1453         }
1454 
1455         return resourcePaths.toString();
1456     }
1457 
1458 
1459     protected Collection getEmbeddableArtifacts( MavenProject currentProject, Analyzer analyzer )
1460         throws MojoExecutionException
1461     {
1462         final Collection artifacts;
1463 
1464         String embedTransitive = analyzer.getProperty( DependencyEmbedder.EMBED_TRANSITIVE );
1465         if ( Boolean.valueOf( embedTransitive ).booleanValue() )
1466         {
1467             // includes transitive dependencies
1468             artifacts = currentProject.getArtifacts();
1469         }
1470         else
1471         {
1472             // only includes direct dependencies
1473             artifacts = currentProject.getDependencyArtifacts();
1474         }
1475 
1476         return getSelectedDependencies( artifacts );
1477     }
1478 
1479 
1480     protected static void addMavenSourcePath( MavenProject currentProject, Analyzer analyzer, Log log )
1481     {
1482         // pass maven source paths onto BND analyzer
1483         StringBuilder mavenSourcePaths = new StringBuilder();
1484         if ( currentProject.getCompileSourceRoots() != null )
1485         {
1486             for ( Iterator i = currentProject.getCompileSourceRoots().iterator(); i.hasNext(); )
1487             {
1488                 if ( mavenSourcePaths.length() > 0 )
1489                 {
1490                     mavenSourcePaths.append( ',' );
1491                 }
1492                 mavenSourcePaths.append( ( String ) i.next() );
1493             }
1494         }
1495         final String sourcePath = ( String ) analyzer.getProperty( Analyzer.SOURCEPATH );
1496         if ( sourcePath != null )
1497         {
1498             if ( sourcePath.indexOf( MAVEN_SOURCES ) >= 0 )
1499             {
1500                 // if there is no maven source path, we do a special treatment and replace
1501                 // every occurance of MAVEN_SOURCES and a following comma with an empty string
1502                 if ( mavenSourcePaths.length() == 0 )
1503                 {
1504                     String cleanedSource = removeTagFromInstruction( sourcePath, MAVEN_SOURCES );
1505                     if ( cleanedSource.length() > 0 )
1506                     {
1507                         analyzer.setProperty( Analyzer.SOURCEPATH, cleanedSource );
1508                     }
1509                     else
1510                     {
1511                         analyzer.unsetProperty( Analyzer.SOURCEPATH );
1512                     }
1513                 }
1514                 else
1515                 {
1516                     String combinedSource = StringUtils
1517                         .replace( sourcePath, MAVEN_SOURCES, mavenSourcePaths.toString() );
1518                     analyzer.setProperty( Analyzer.SOURCEPATH, combinedSource );
1519                 }
1520             }
1521             else if ( mavenSourcePaths.length() > 0 )
1522             {
1523                 log.warn( Analyzer.SOURCEPATH + ": overriding " + mavenSourcePaths + " with " + sourcePath + " (add "
1524                     + MAVEN_SOURCES + " if you want to include the maven sources)" );
1525             }
1526         }
1527         else if ( mavenSourcePaths.length() > 0 )
1528         {
1529             analyzer.setProperty( Analyzer.SOURCEPATH, mavenSourcePaths.toString() );
1530         }
1531     }
1532 }