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