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