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