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