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