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