    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.File;
    23  import java.io.FilenameFilter;
    24  import java.util.Arrays;
    25  import java.util.Collection;
    26  import java.util.HashSet;
    27  import java.util.Iterator;
    28  import java.util.LinkedHashMap;
    29  import java.util.List;
    30  import java.util.Map;
    31  import java.util.Properties;
    32  import java.util.Set;
    33  import java.util.jar.Manifest;
    34  import java.util.regex.Matcher;
    35  import java.util.regex.Pattern;
    36
    37  import org.apache.maven.artifact.Artifact;
    38  import org.apache.maven.artifact.factory.ArtifactFactory;
    39  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
    40  import org.apache.maven.artifact.repository.ArtifactRepository;
    41  import org.apache.maven.artifact.resolver.ArtifactCollector;
    42  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
    43  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
    44  import org.apache.maven.artifact.resolver.ArtifactResolver;
    45  import org.apache.maven.artifact.versioning.VersionRange;
    46  import org.apache.maven.plugin.MojoExecutionException;
    47  import org.apache.maven.project.MavenProject;
    48  import org.apache.maven.project.MavenProjectBuilder;
    49  import org.apache.maven.project.ProjectBuildingException;
    50  import org.apache.maven.project.artifact.InvalidDependencyVersionException;
    51  import org.apache.maven.shared.dependency.tree.DependencyNode;
    52  import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
    53  import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
    54  import org.codehaus.plexus.util.FileUtils;
    55
    56  import aQute.bnd.osgi.Analyzer;
    57  import aQute.bnd.osgi.Jar;
    58
    59
    60  /**
    61   * Create OSGi bundles from all dependencies in the Maven project
    62   *
    63   * @goal bundleall
    64   * @phase package
    65   * @requiresDependencyResolution test
    66   * @description build an OSGi bundle jar for all transitive dependencies
    67   * @deprecated The bundleall goal is no longer supported and may be removed in a future release
    68   */
    69  @Deprecated
    70  public class BundleAllPlugin extends ManifestPlugin
    71  {
    72      private static final String LS = System.getProperty( "line.separator" );
    73
    74      private static final Pattern SNAPSHOT_VERSION_PATTERN = Pattern.compile( "[0-9]{8}_[0-9]{6}_[0-9]+" );
    75
    76      /**
    77       * Local repository.
    78       *
    79       * @parameter expression="${localRepository}"
    80       * @required
    81       * @readonly
    82       */
    83      private ArtifactRepository localRepository;
    84
    85      /**
    86       * Remote repositories.
    87       *
    88       * @parameter expression="${project.remoteArtifactRepositories}"
    89       * @required
    90       * @readonly
    91       */
    92      private List remoteRepositories;
    93
    94      /**
    95       * Import-Package to be used when wrapping dependencies.
    96       *
    97       * @parameter expression="${wrapImportPackage}" default-value="*"
    98       */
    99      private String wrapImportPackage;
    100
    101     /**
    102      * @component
    103      */
    104     private ArtifactFactory m_factory;
    105
    106     /**
    107      * @component
    108      */
    109     private ArtifactMetadataSource m_artifactMetadataSource;
    110
    111     /**
    112      * @component
    113      */
    114     private ArtifactCollector m_collector;
    115
    116     /**
    117      * Artifact resolver, needed to download jars.
    118      *
    119      * @component
    120      */
    121     private ArtifactResolver m_artifactResolver;
    122
    123     /**
    124      * @component
    125      */
    126     private DependencyTreeBuilder m_dependencyTreeBuilder;
    127
    128     /**
    129      * @component
    130      */
    131     private MavenProjectBuilder m_mavenProjectBuilder;
    132
    133     /**
    134      * Ignore missing artifacts that are not required by current project but are required by the
    135      * transitive dependencies.
    136      *
    137      * @parameter
    138      */
    139     private boolean ignoreMissingArtifacts;
    140
    141     private Set m_artifactsBeingProcessed = new HashSet();
    142
    143     /**
    144      * Process up to some depth
    145      *
    146      * @parameter
    147      */
    148     private int depth = Integer.MAX_VALUE;
    149
    150
    151     public void execute() throws MojoExecutionException
    152     {
    153         getLog().warn( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" );
    154         getLog().warn( "! The bundleall goal is no longer supported and may be removed in a future release !" );
    155         getLog().warn( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" );
    156
    157         BundleInfo bundleInfo = bundleAll( getProject() );
    158         logDuplicatedPackages( bundleInfo );
    159     }
    160
    161
    162     /**
    163      * Bundle a project and all its dependencies
    164      *
    165      * @param project
    166      * @throws MojoExecutionException
    167      */
    168     private BundleInfo bundleAll( MavenProject project ) throws MojoExecutionException
    169     {
    170         return bundleAll( project, depth );
    171     }
    172
    173
    174     /**
    175      * Bundle a project and its transitive dependencies up to some depth level
    176      *
    177      * @param project
    178      * @param maxDepth how deep to process the dependency tree
    179      * @throws MojoExecutionException
    180      */
    181     protected BundleInfo bundleAll( MavenProject project, int maxDepth ) throws MojoExecutionException
    182     {
    183         if ( alreadyBundled( project.getArtifact() ) )
    184         {
    185             getLog().debug( "Ignoring project already processed " + project.getArtifact() );
    186             return null;
    187         }
    188
    189         if ( m_artifactsBeingProcessed.contains( project.getArtifact() ) )
    190         {
    191             getLog().warn( "Ignoring artifact due to dependency cycle " + project.getArtifact() );
    192             return null;
    193         }
    194         m_artifactsBeingProcessed.add( project.getArtifact() );
    195
    196         DependencyNode dependencyTree;
    197
    198         try
    199         {
    200             dependencyTree = m_dependencyTreeBuilder.buildDependencyTree( project, localRepository, m_factory,
    201                 m_artifactMetadataSource, null, m_collector );
    202         }
    203         catch ( DependencyTreeBuilderException e )
    204         {
    205             throw new MojoExecutionException( "Unable to build dependency tree", e );
    206         }
    207
    208         BundleInfo bundleInfo = new BundleInfo();
    209
    210         if ( !dependencyTree.hasChildren() )
    211         {
    212             /* no need to traverse the tree */
    213             return bundleRoot( project, bundleInfo );
    214         }
    215
    216         getLog().debug( "Will bundle the following dependency tree" + LS + dependencyTree );
    217
    218         for ( Iterator it = dependencyTree.inverseIterator(); it.hasNext(); )
    219         {
    220             DependencyNode node = ( DependencyNode ) it.next();
    221             if ( !it.hasNext() )
    222             {
    223                 /* this is the root, current project */
    224                 break;
    225             }
    226
    227             if ( node.getState() != DependencyNode.INCLUDED )
    228             {
    229                 continue;
    230             }
    231
    232             if ( Artifact.SCOPE_SYSTEM.equals( node.getArtifact().getScope() ) )
    233             {
    234                 getLog().debug( "Ignoring system scoped artifact " + node.getArtifact() );
    235                 continue;
    236             }
    237
    238             Artifact artifact;
    239             try
    240             {
    241                 artifact = resolveArtifact( node.getArtifact() );
    242             }
    243             catch ( ArtifactNotFoundException e )
    244             {
    245                 if ( ignoreMissingArtifacts )
    246                 {
    247                     continue;
    248                 }
    249
    250                 throw new MojoExecutionException( "Artifact was not found in the repo" + node.getArtifact(), e );
    251             }
    252
    253             node.getArtifact().setFile( artifact.getFile() );
    254
    255             int nodeDepth = node.getDepth();
    256             if ( nodeDepth > maxDepth )
    257             {
    258                 /* node is deeper than we want */
    259                 getLog().debug(
    260                     "Ignoring " + node.getArtifact() + ", depth is " + nodeDepth + ", bigger than " + maxDepth );
    261                 continue;
    262             }
    263
    264             MavenProject childProject;
    265             try
    266             {
    267                 childProject = m_mavenProjectBuilder.buildFromRepository( artifact, remoteRepositories,
    268                     localRepository, true );
    269                 if ( childProject.getDependencyArtifacts() == null )
    270                 {
    271                     childProject.setDependencyArtifacts( childProject.createArtifacts( m_factory, null, null ) );
    272                 }
    273             }
    274             catch ( InvalidDependencyVersionException e )
    275             {
    276                 throw new MojoExecutionException( "Invalid dependency version for artifact " + artifact );
    277             }
    278             catch ( ProjectBuildingException e )
    279             {
    280                 throw new MojoExecutionException( "Unable to build project object for artifact " + artifact, e );
    281             }
    282
    283             childProject.setArtifact( artifact );
    284             getLog().debug( "Child project artifact location: " + childProject.getArtifact().getFile() );
    285
    286             if ( ( Artifact.SCOPE_COMPILE.equals( artifact.getScope() ) )
    287                 || ( Artifact.SCOPE_RUNTIME.equals( artifact.getScope() ) ) )
    288             {
    289                 BundleInfo subBundleInfo = bundleAll( childProject, maxDepth - 1 );
    290                 if ( subBundleInfo != null )
    291                 {
    292                     bundleInfo.merge( subBundleInfo );
    293                 }
    294             }
    295             else
    296             {
    297                 getLog().debug(
    298                     "Not processing due to scope (" + childProject.getArtifact().getScope() + "): "
    299                         + childProject.getArtifact() );
    300             }
    301         }
    302
    303         return bundleRoot( project, bundleInfo );
    304     }
    305
    306
    307     /**
    308      * Bundle the root of a dependency tree after all its children have been bundled
    309      *
    310      * @param project
    311      * @param bundleInfo
    312      * @return
    313      * @throws MojoExecutionException
    314      */
    315     private BundleInfo bundleRoot( MavenProject project, BundleInfo bundleInfo ) throws MojoExecutionException
    316     {
    317         /* do not bundle the project the mojo was called on */
    318         if ( getProject() != project )
    319         {
    320             getLog().debug( "Project artifact location: " + project.getArtifact().getFile() );
    321
    322             BundleInfo subBundleInfo = bundle( project );
    323             if ( subBundleInfo != null )
    324             {
    325                 bundleInfo.merge( subBundleInfo );
    326             }
    327         }
    328         return bundleInfo;
    329     }
    330
    331
    332     /**
    333      * Bundle one project only without building its childre
    334      *
    335      * @param project
    336      * @throws MojoExecutionException
    337      */
    338     protected BundleInfo bundle( MavenProject project ) throws MojoExecutionException
    339     {
    340         Artifact artifact = project.getArtifact();
    341         getLog().info( "Bundling " + artifact );
    342
    343         try
    344         {
    345             Map instructions = new LinkedHashMap();
    346             instructions.put( Analyzer.IMPORT_PACKAGE, wrapImportPackage );
    347
    348             project.getArtifact().setFile( getFile( artifact ) );
    349             File outputFile = getOutputFile( artifact );
    350
    351             if ( project.getArtifact().getFile().equals( outputFile ) )
    352             {
    353                 /* TODO find the cause why it's getting here */
    354                 return null;
    355                 //                getLog().error(
    356                 //                                "Trying to read and write " + artifact + " to the same file, try cleaning: "
    357                 //                                    + outputFile );
    358                 //                throw new IllegalStateException( "Trying to read and write " + artifact
    359                 //                    + " to the same file, try cleaning: " + outputFile );
    360             }
    361
    362             Analyzer analyzer = getAnalyzer( project, instructions, new Properties(), getClasspath( project ) );
    363
    364             Jar osgiJar = new Jar( project.getArtifactId(), project.getArtifact().getFile() );
    365
    366             outputFile.getAbsoluteFile().getParentFile().mkdirs();
    367
    368             Collection exportedPackages;
    369             if ( isOsgi( osgiJar ) )
    370             {
    371                 /* if it is already an OSGi jar copy it as is */
    372                 getLog().info(
    373                     "Using existing OSGi bundle for " + project.getGroupId() + ":" + project.getArtifactId() + ":"
    374                         + project.getVersion() );
    375                 String exportHeader = osgiJar.getManifest().getMainAttributes().getValue( Analyzer.EXPORT_PACKAGE );
    376                 exportedPackages = analyzer.parseHeader( exportHeader ).keySet();
    377                 FileUtils.copyFile( project.getArtifact().getFile(), outputFile );
    378             }
    379             else
    380             {
    381                 /* else generate the manifest from the packages */
    382                 exportedPackages = analyzer.getExports().keySet();
    383                 Manifest manifest = analyzer.getJar().getManifest();
    384                 osgiJar.setManifest( manifest );
    385                 osgiJar.write( outputFile );
    386             }
    387
    388             BundleInfo bundleInfo = addExportedPackages( project, exportedPackages );
    389
    390             // cleanup...
    391             analyzer.close();
    392             osgiJar.close();
    393
    394             return bundleInfo;
    395         }
    396         /* too bad Jar.write throws Exception */
    397         catch ( Exception e )
    398         {
    399             throw new MojoExecutionException( "Error generating OSGi bundle for project "
    400                 + getArtifactKey( project.getArtifact() ), e );
    401         }
    402     }
    403
    404
    405     private boolean isOsgi( Jar jar ) throws Exception
    406     {
    407         if ( jar.getManifest() != null )
    408         {
    409             return jar.getManifest().getMainAttributes().getValue( Analyzer.BUNDLE_NAME ) != null;
    410         }
    411         return false;
    412     }
    413
    414
    415     private BundleInfo addExportedPackages( MavenProject project, Collection packages )
    416     {
    417         BundleInfo bundleInfo = new BundleInfo();
    418         for ( Iterator it = packages.iterator(); it.hasNext(); )
    419         {
    420             String packageName = ( String ) it.next();
    421             bundleInfo.addExportedPackage( packageName, project.getArtifact() );
    422         }
    423         return bundleInfo;
    424     }
    425
    426
    427     private String getArtifactKey( Artifact artifact )
    428     {
    429         return artifact.getGroupId() + ":" + artifact.getArtifactId();
    430     }
    431
    432
    433     private String getBundleName( Artifact artifact )
    434     {
    435         return getMaven2OsgiConverter().getBundleFileName( artifact );
    436     }
    437
    438
    439     private boolean alreadyBundled( Artifact artifact )
    440     {
    441         return getBuiltFile( artifact ) != null;
    442     }
    443
    444
    445     /**
    446      * Use previously built bundles when available.
    447      *
    448      * @param artifact
    449      */
    450     protected File getFile( final Artifact artifact )
    451     {
    452         File bundle = getBuiltFile( artifact );
    453
    454         if ( bundle != null )
    455         {
    456             getLog().debug( "Using previously built OSGi bundle for " + artifact + " in " + bundle );
    457             return bundle;
    458         }
    459         return super.getFile( artifact );
    460     }
    461
    462
    463     private File getBuiltFile( final Artifact artifact )
    464     {
    465         File bundle = null;
    466
    467         /* if bundle was already built use it instead of jar from repo */
    468         File outputFile = getOutputFile( artifact );
    469         if ( outputFile.exists() )
    470         {
    471             bundle = outputFile;
    472         }
    473
    474         /*
    475          * Find snapshots in output folder, eg. 2.1-SNAPSHOT will match 2.1.0.20070207_193904_2
    476          * TODO there has to be another way to do this using Maven libs
    477          */
    478         if ( ( bundle == null ) && artifact.isSnapshot() )
    479         {
    480             final File buildDirectory = new File( getBuildDirectory() );
    481             if ( !buildDirectory.exists() )
    482             {
    483                 buildDirectory.mkdirs();
    484             }
    485             File[] files = buildDirectory.listFiles( new FilenameFilter()
    486             {
    487                 public boolean accept( File dir, String name )
    488                 {
    489                     if ( dir.equals( buildDirectory ) && snapshotMatch( artifact, name ) )
    490                     {
    491                         return true;
    492                     }
    493                     return false;
    494                 }
    495             } );
    496             if ( files.length > 1 )
    497             {
    498                 throw new RuntimeException( "More than one previously built bundle matches for artifact " + artifact
    499                     + " : " + Arrays.asList( files ) );
    500             }
    501             if ( files.length == 1 )
    502             {
    503                 bundle = files[0];
    504             }
    505         }
    506
    507         return bundle;
    508     }
    509
    510
    511     /**
    512      * Check that the bundleName provided correspond to the artifact provided.
    513      * Used to determine when the bundle name is a timestamped snapshot and the artifact is a snapshot not timestamped.
    514      *
    515      * @param artifact artifact with snapshot version
    516      * @param bundleName bundle file name
    517      * @return if both represent the same artifact and version, forgetting about the snapshot timestamp
    518      */
    519     protected boolean snapshotMatch( Artifact artifact, String bundleName )
    520     {
    521         String artifactBundleName = getBundleName( artifact );
    522         int i = artifactBundleName.indexOf( "SNAPSHOT" );
    523         if ( i < 0 )
    524         {
    525             return false;
    526         }
    527         artifactBundleName = artifactBundleName.substring( 0, i );
    528
    529         if ( bundleName.startsWith( artifactBundleName ) )
    530         {
    531             /* it's the same artifact groupId and artifactId */
    532             String timestamp = bundleName.substring( artifactBundleName.length(), bundleName.lastIndexOf( ".jar" ) );
    533             Matcher m = SNAPSHOT_VERSION_PATTERN.matcher( timestamp );
    534             return m.matches();
    535         }
    536         return false;
    537     }
    538
    539
    540     protected File getOutputFile( Artifact artifact )
    541     {
    542         return new File( getOutputDirectory(), getBundleName( artifact ) );
    543     }
    544
    545
    546     private Artifact resolveArtifact( Artifact artifact ) throws MojoExecutionException, ArtifactNotFoundException
    547     {
    548         VersionRange versionRange;
    549         if ( artifact.getVersion() != null )
    550         {
    551             versionRange = VersionRange.createFromVersion( artifact.getVersion() );
    552         }
    553         else
    554         {
    555             versionRange = artifact.getVersionRange();
    556         }
    557
    558         /*
    559          * there's a bug with ArtifactFactory#createDependencyArtifact(String, String, VersionRange,
    560          * String, String, String) that ignores the scope parameter, that's why we use the one with
    561          * the extra null parameter
    562          */
    563         Artifact resolvedArtifact = m_factory.createDependencyArtifact( artifact.getGroupId(),
    564             artifact.getArtifactId(), versionRange, artifact.getType(), artifact.getClassifier(), artifact.getScope(),
    565             null );
    566
    567         try
    568         {
    569             m_artifactResolver.resolve( resolvedArtifact, remoteRepositories, localRepository );
    570         }
    571         catch ( ArtifactResolutionException e )
    572         {
    573             throw new MojoExecutionException( "Error resolving artifact " + resolvedArtifact, e );
    574         }
    575
    576         return resolvedArtifact;
    577     }
    578
    579
    580     /**
    581      * Log what packages are exported in more than one bundle
    582      */
    583     protected void logDuplicatedPackages( BundleInfo bundleInfo )
    584     {
    585         Map duplicatedExports = bundleInfo.getDuplicatedExports();
    586
    587         for ( Iterator it = duplicatedExports.entrySet().iterator(); it.hasNext(); )
    588         {
    589             Map.Entry entry = ( Map.Entry ) it.next();
    590             String packageName = ( String ) entry.getKey();
    591             Collection artifacts = ( Collection ) entry.getValue();
    592
    593             getLog().warn( "Package " + packageName + " is exported in more than a bundle: " );
    594             for ( Iterator it2 = artifacts.iterator(); it2.hasNext(); )
    595             {
    596                 Artifact artifact = ( Artifact ) it2.next();
    597                 getLog().warn( "  " + artifact );
    598             }
    599
    600         }
    601     }
    602 }