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