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