View Javadoc

1   package org.apache.maven.archiver;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.DependencyResolutionRequiredException;
24  import org.apache.maven.project.MavenProject;
25  import org.codehaus.plexus.archiver.ArchiverException;
26  import org.codehaus.plexus.archiver.jar.JarArchiver;
27  import org.codehaus.plexus.archiver.jar.Manifest;
28  import org.codehaus.plexus.archiver.jar.ManifestException;
29  import org.codehaus.plexus.interpolation.InterpolationException;
30  import org.codehaus.plexus.interpolation.Interpolator;
31  import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
32  import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
33  import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource;
34  import org.codehaus.plexus.interpolation.RecursionInterceptor;
35  import org.codehaus.plexus.interpolation.StringSearchInterpolator;
36  import org.codehaus.plexus.interpolation.ValueSource;
37  import org.codehaus.plexus.util.StringUtils;
38  
39  import java.io.File;
40  import java.io.IOException;
41  import java.util.ArrayList;
42  import java.util.Collections;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.Properties;
47  import java.util.Set;
48  
49  /**
50   * @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
51   * @version $Revision: 1037536 $ $Date: 2010-11-21 20:40:03 +0100 (sø., 21 nov. 2010) $
52   */
53  public class MavenArchiver
54  {
55      
56      public static final String SIMPLE_LAYOUT = "${artifact.artifactId}-${artifact.version}${dashClassifier?}.${artifact.extension}";
57  
58      public static final String REPOSITORY_LAYOUT = "${artifact.groupIdPath}/${artifact.artifactId}/" +
59      		"${artifact.baseVersion}/${artifact.artifactId}-" +
60      		"${artifact.version}${dashClassifier?}.${artifact.extension}";
61      
62      public static final String SIMPLE_LAYOUT_NONUNIQUE = "${artifact.artifactId}-${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";
63  
64      public static final String REPOSITORY_LAYOUT_NONUNIQUE = "${artifact.groupIdPath}/${artifact.artifactId}/" +
65              "${artifact.baseVersion}/${artifact.artifactId}-" +
66              "${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";
67      
68      private static final List ARTIFACT_EXPRESSION_PREFIXES;
69      
70      static
71      {
72          List artifactExpressionPrefixes = new ArrayList();
73          artifactExpressionPrefixes.add( "artifact." );
74          
75          ARTIFACT_EXPRESSION_PREFIXES = artifactExpressionPrefixes;
76      }
77  
78      private JarArchiver archiver;
79  
80      private File archiveFile;
81  
82      /**
83       * Return a pre-configured manifest
84       *
85       * @todo Add user attributes list and user groups list
86       */
87      public Manifest getManifest( MavenProject project, MavenArchiveConfiguration config )
88          throws ManifestException, DependencyResolutionRequiredException
89      {
90          boolean hasManifestEntries = !config.isManifestEntriesEmpty();
91          Map entries = hasManifestEntries ? config.getManifestEntries() : Collections.EMPTY_MAP;
92          Manifest manifest = getManifest( project, config.getManifest(), entries );
93  
94          // any custom manifest entries in the archive configuration manifest?
95          if ( hasManifestEntries )
96          {
97              Set keys = entries.keySet();
98              for ( Iterator iter = keys.iterator(); iter.hasNext(); )
99              {
100                 String key = (String) iter.next();
101                 String value = (String) entries.get( key );
102                 Manifest.Attribute attr = manifest.getMainSection().getAttribute( key );
103                 if ( key.equals( "Class-Path" ) && attr != null )
104                 {
105                     // Merge the user-supplied Class-Path value with the programmatically
106                     // generated Class-Path.  Note that the user-supplied value goes first
107                     // so that resources there will override any in the standard Class-Path.
108                     attr.setValue( value + " " + attr.getValue() );
109                 }
110                 else
111                 {
112                     addManifestAttribute( manifest, key, value );
113                 }
114             }
115         }
116 
117         // any custom manifest sections in the archive configuration manifest?
118         if ( !config.isManifestSectionsEmpty() )
119         {
120             List sections = config.getManifestSections();
121             for ( Iterator iter = sections.iterator(); iter.hasNext(); )
122             {
123                 ManifestSection section = (ManifestSection) iter.next();
124                 Manifest.Section theSection = new Manifest.Section();
125                 theSection.setName( section.getName() );
126 
127                 if ( !section.isManifestEntriesEmpty() )
128                 {
129                     Map sectionEntries = section.getManifestEntries();
130                     Set keys = sectionEntries.keySet();
131                     for ( Iterator it = keys.iterator(); it.hasNext(); )
132                     {
133                         String key = (String) it.next();
134                         String value = (String) sectionEntries.get( key );
135                         Manifest.Attribute attr = new Manifest.Attribute( key, value );
136                         theSection.addConfiguredAttribute( attr );
137                     }
138                 }
139 
140                 manifest.addConfiguredSection( theSection );
141             }
142         }
143 
144         return manifest;
145     }
146 
147     /**
148      * Return a pre-configured manifest
149      *
150      * @todo Add user attributes list and user groups list
151      */
152     public Manifest getManifest( MavenProject project, ManifestConfiguration config )
153         throws ManifestException, DependencyResolutionRequiredException
154     {
155         return getManifest( project, config, Collections.EMPTY_MAP );
156     }
157 
158     private void addManifestAttribute( Manifest manifest, Map map, String key, String value )
159         throws ManifestException
160     {
161         if ( map.containsKey( key ) )
162         {
163             return;  // The map value will be added later
164         }
165         addManifestAttribute( manifest, key, value );
166     }
167 
168     private void addManifestAttribute( Manifest manifest, String key, String value )
169         throws ManifestException
170     {
171         if ( !StringUtils.isEmpty( value ) )
172         {
173             Manifest.Attribute attr = new Manifest.Attribute( key, value );
174             manifest.addConfiguredAttribute( attr );
175         }
176         else
177         {
178             // if the value is empty we have create an entry with an empty string 
179             // to prevent null print in the manifest file
180             Manifest.Attribute attr = new Manifest.Attribute( key, "" );
181             manifest.addConfiguredAttribute( attr );
182         }
183     }
184 
185     protected Manifest getManifest( MavenProject project, ManifestConfiguration config, Map entries )
186         throws ManifestException, DependencyResolutionRequiredException
187     {
188         // TODO: Should we replace "map" with a copy? Note, that we modify it!
189 
190         // Added basic entries
191         Manifest m = new Manifest();
192         addManifestAttribute( m, entries, "Created-By", "Apache Maven" );
193 
194         addCustomEntries( m, entries, config );
195 
196         if ( config.isAddClasspath() )
197         {
198             StringBuffer classpath = new StringBuffer();
199             
200             List artifacts = project.getRuntimeClasspathElements();
201             String classpathPrefix = config.getClasspathPrefix();
202             String layoutType = config.getClasspathLayoutType();
203             String layout = config.getCustomClasspathLayout();
204             
205             Interpolator interpolator = new StringSearchInterpolator();
206 
207             for ( Iterator iter = artifacts.iterator(); iter.hasNext(); )
208             {
209                 File f = new File( (String) iter.next() );
210                 if ( f.getAbsoluteFile().isFile() )
211                 {
212                     Artifact artifact = findArtifactWithFile( project.getArtifacts(), f );
213                     
214                     if ( classpath.length() > 0 )
215                     {
216                         classpath.append( " " );
217                     }
218                     classpath.append( classpathPrefix );
219                     
220                     // NOTE: If the artifact or layout type (from config) is null, give up and use the file name by itself.
221                     if ( artifact == null || layoutType == null )
222                     {
223                         classpath.append( f.getName() );
224                     }
225                     else
226                     {
227                         List valueSources = new ArrayList();
228                         valueSources.add( new PrefixedObjectValueSource( ARTIFACT_EXPRESSION_PREFIXES, artifact, true ) );
229                         valueSources.add( new PrefixedObjectValueSource( ARTIFACT_EXPRESSION_PREFIXES, artifact == null ? null : artifact.getArtifactHandler(), true ) );
230                         
231                         Properties extraExpressions = new Properties();
232                         if ( artifact != null )
233                         {
234                             // FIXME: This query method SHOULD NOT affect the internal
235                             // state of the artifact version, but it does.
236                             if ( !artifact.isSnapshot() )
237                             {
238                                 extraExpressions.setProperty( "baseVersion", artifact.getVersion() );
239                             }
240                             
241                             extraExpressions.setProperty( "groupIdPath", artifact.getGroupId().replace( '.', '/' ) );
242                             if ( StringUtils.isNotEmpty( artifact.getClassifier() ) )
243                             {
244                                 extraExpressions.setProperty( "dashClassifier", "-" + artifact.getClassifier() );
245                                 extraExpressions.setProperty( "dashClassifier?", "-" + artifact.getClassifier() );
246                             }
247                             else
248                             {
249                                 extraExpressions.setProperty( "dashClassifier", "" );
250                                 extraExpressions.setProperty( "dashClassifier?", "" );
251                             }
252                         }
253                         valueSources.add( new PrefixedPropertiesValueSource( ARTIFACT_EXPRESSION_PREFIXES, extraExpressions, true ) );
254                         
255                         for ( Iterator it = valueSources.iterator(); it.hasNext(); )
256                         {
257                             ValueSource vs = (ValueSource) it.next();
258                             interpolator.addValueSource( vs );
259                         }
260                         
261                         RecursionInterceptor recursionInterceptor = new PrefixAwareRecursionInterceptor( ARTIFACT_EXPRESSION_PREFIXES );
262                         
263                         try
264                         {
265                             if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_SIMPLE.equals( layoutType ) )
266                             {
267                                 if ( config.isUseUniqueVersions() )
268                                 {
269                                     classpath.append( interpolator.interpolate( SIMPLE_LAYOUT, recursionInterceptor ) );
270                                 }
271                                 else
272                                 {
273                                     classpath.append( interpolator.interpolate( SIMPLE_LAYOUT_NONUNIQUE, recursionInterceptor ) );
274                                 }
275                             }
276                             else if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_REPOSITORY.equals( layoutType ) )
277                             {
278                                 // we use layout /$groupId[0]/../${groupId[n]/$artifactId/$version/{fileName}
279                                 // here we must find the Artifact in the project Artifacts to generate the maven layout
280                                 if ( config.isUseUniqueVersions() )
281                                 {
282                                     classpath.append( interpolator.interpolate( REPOSITORY_LAYOUT, recursionInterceptor ) );
283                                 }
284                                 else
285                                 {
286                                     classpath.append( interpolator.interpolate( REPOSITORY_LAYOUT_NONUNIQUE, recursionInterceptor ) );
287                                 }
288                             }
289                             else if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM.equals( layoutType ) )
290                             {
291                                 if ( layout == null )
292                                 {
293                                     throw new ManifestException(
294                                                                  ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM
295                                                                      + " layout type was declared, but custom layout expression was not specified. Check your <archive><manifest><customLayout/> element." );
296                                 }
297                                 
298                                 classpath.append( interpolator.interpolate( layout, recursionInterceptor ) );
299                             }
300                             else
301                             {
302                                 throw new ManifestException( "Unknown classpath layout type: '" + layoutType
303                                     + "'. Check your <archive><manifest><layoutType/> element." );
304                             }
305                         }
306                         catch ( InterpolationException e )
307                         {
308                             ManifestException error =
309                                 new ManifestException( "Error interpolating artifact path for classpath entry: "
310                                     + e.getMessage() );
311                             
312                             error.initCause( e );
313                             throw error;
314                         }
315                         finally
316                         {
317                             for ( Iterator it = valueSources.iterator(); it.hasNext(); )
318                             {
319                                 ValueSource vs = (ValueSource) it.next();
320                                 interpolator.removeValuesSource( vs );
321                             }
322                         }
323                     }
324                 }
325             }
326 
327             if ( classpath.length() > 0 )
328             {
329                 // Class-Path is special and should be added to manifest even if
330                 // it is specified in the manifestEntries section
331                 addManifestAttribute( m, "Class-Path", classpath.toString() );
332             }
333         }
334 
335         if ( config.isAddDefaultSpecificationEntries() )
336         {
337             addManifestAttribute( m, entries, "Specification-Title", project.getName() );
338             addManifestAttribute( m, entries, "Specification-Version", project.getVersion() );
339 
340             if ( project.getOrganization() != null )
341             {
342                 addManifestAttribute( m, entries, "Specification-Vendor", project.getOrganization().getName() );
343             }
344         }
345 
346         if ( config.isAddDefaultImplementationEntries() )
347         {
348             addManifestAttribute( m, entries, "Implementation-Title", project.getName() );
349             addManifestAttribute( m, entries, "Implementation-Version", project.getVersion() );
350             // MJAR-5
351             addManifestAttribute( m, entries, "Implementation-Vendor-Id", project.getGroupId() );
352 
353             if ( project.getOrganization() != null )
354             {
355                 addManifestAttribute( m, entries, "Implementation-Vendor", project.getOrganization().getName() );
356             }
357         }
358 
359         String mainClass = config.getMainClass();
360         if ( mainClass != null && !"".equals( mainClass ) )
361         {
362             addManifestAttribute( m, entries, "Main-Class", mainClass );
363         }
364 
365         // Added extensions
366         if ( config.isAddExtensions() )
367         {
368             // TODO: this is only for applets - should we distinguish them as a packaging?
369             StringBuffer extensionsList = new StringBuffer();
370             Set artifacts = project.getArtifacts();
371 
372             for ( Iterator iter = artifacts.iterator(); iter.hasNext(); )
373             {
374                 Artifact artifact = (Artifact) iter.next();
375                 if ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
376                 {
377                     if ( "jar".equals( artifact.getType() ) )
378                     {
379                         if ( extensionsList.length() > 0 )
380                         {
381                             extensionsList.append( " " );
382                         }
383                         extensionsList.append( artifact.getArtifactId() );
384                     }
385                 }
386             }
387 
388             if ( extensionsList.length() > 0 )
389             {
390                 addManifestAttribute( m, entries, "Extension-List", extensionsList.toString() );
391             }
392 
393             for ( Iterator iter = artifacts.iterator(); iter.hasNext(); )
394             {
395                 // TODO: the correct solution here would be to have an extension type, and to read
396                 // the real extension values either from the artifact's manifest or some part of the POM
397                 Artifact artifact = (Artifact) iter.next();
398                 if ( "jar".equals( artifact.getType() ) )
399                 {
400                     String ename = artifact.getArtifactId() + "-Extension-Name";
401                     addManifestAttribute( m, entries, ename, artifact.getArtifactId() );
402                     String iname = artifact.getArtifactId() + "-Implementation-Version";
403                     addManifestAttribute( m, entries, iname, artifact.getVersion() );
404 
405                     if ( artifact.getRepository() != null )
406                     {
407                         iname = artifact.getArtifactId() + "-Implementation-URL";
408                         String url = artifact.getRepository().getUrl() + "/" + artifact.toString();
409                         addManifestAttribute( m, entries, iname, url );
410                     }
411                 }
412             }
413         }
414 
415         return m;
416     }
417 
418     private void addCustomEntries( Manifest m, Map entries, ManifestConfiguration config )
419         throws ManifestException
420     {
421         addManifestAttribute( m, entries, "Built-By", System.getProperty( "user.name" ) );
422         addManifestAttribute( m, entries, "Build-Jdk", System.getProperty( "java.version" ) );
423 
424 /* TODO: rethink this, it wasn't working
425         Artifact projectArtifact = project.getArtifact();
426 
427         if ( projectArtifact.isSnapshot() )
428         {
429             Manifest.Attribute buildNumberAttr = new Manifest.Attribute( "Build-Number", "" +
430                 project.getSnapshotDeploymentBuildNumber() );
431             m.addConfiguredAttribute( buildNumberAttr );
432         }
433 
434 */
435         if ( config.getPackageName() != null )
436         {
437             addManifestAttribute( m, entries, "Package", config.getPackageName() );
438         }
439     }
440 
441     public JarArchiver getArchiver()
442     {
443         return archiver;
444     }
445 
446     public void setArchiver( JarArchiver archiver )
447     {
448         this.archiver = archiver;
449     }
450 
451     public void setOutputFile( File outputFile )
452     {
453         archiveFile = outputFile;
454     }
455 
456     public void createArchive( MavenProject project, MavenArchiveConfiguration archiveConfiguration )
457         throws ArchiverException, ManifestException, IOException, DependencyResolutionRequiredException
458     {
459         // we have to clone the project instance so we can write out the pom with the deployment version,
460         // without impacting the main project instance...
461         // TODO use clone() in Maven 2.0.9+
462         MavenProject workingProject = new MavenProject( project );
463 
464         boolean forced = archiveConfiguration.isForced();
465         if ( archiveConfiguration.isAddMavenDescriptor() )
466         {
467             // ----------------------------------------------------------------------
468             // We want to add the metadata for the project to the JAR in two forms:
469             //
470             // The first form is that of the POM itself. Applications that wish to
471             // access the POM for an artifact using maven tools they can.
472             //
473             // The second form is that of a properties file containing the basic
474             // top-level POM elements so that applications that wish to access
475             // POM information without the use of maven tools can do so.
476             // ----------------------------------------------------------------------
477 
478             if ( workingProject.getArtifact().isSnapshot() )
479             {
480                 workingProject.setVersion( workingProject.getArtifact().getVersion() );
481             }
482 
483             String groupId = workingProject.getGroupId();
484 
485             String artifactId = workingProject.getArtifactId();
486 
487             archiver.addFile( project.getFile(), "META-INF/maven/" + groupId + "/" + artifactId + "/pom.xml" );
488 
489             // ----------------------------------------------------------------------
490             // Create pom.properties file
491             // ----------------------------------------------------------------------
492 
493             File pomPropertiesFile = archiveConfiguration.getPomPropertiesFile();
494             if ( pomPropertiesFile == null )
495             {
496                 File dir = new File( workingProject.getBuild().getDirectory(), "maven-archiver" );
497                 pomPropertiesFile = new File( dir, "pom.properties" );
498             }
499             new PomPropertiesUtil().createPomProperties( workingProject, archiver, pomPropertiesFile, forced );
500         }
501 
502         // ----------------------------------------------------------------------
503         // Create the manifest
504         // ----------------------------------------------------------------------
505 
506         File manifestFile = archiveConfiguration.getManifestFile();
507 
508         if ( manifestFile != null )
509         {
510             archiver.setManifest( manifestFile );
511         }
512 
513         Manifest manifest = getManifest( workingProject, archiveConfiguration );
514 
515         // Configure the jar
516         archiver.addConfiguredManifest( manifest );
517 
518         archiver.setCompress( archiveConfiguration.isCompress() );
519 
520         archiver.setIndex( archiveConfiguration.isIndex() );
521 
522         archiver.setDestFile( archiveFile );
523 
524         // make the archiver index the jars on the classpath, if we are adding that to the manifest
525         if ( archiveConfiguration.getManifest().isAddClasspath() )
526         {
527             List artifacts = project.getRuntimeClasspathElements();
528             for ( Iterator iter = artifacts.iterator(); iter.hasNext(); )
529             {
530                 File f = new File( (String) iter.next() );
531                 archiver.addConfiguredIndexJars( f );
532             }
533         }
534 
535         archiver.setForced( forced );
536         if ( !archiveConfiguration.isForced()  &&  archiver.isSupportingForced() )
537         {
538             // TODO Should issue a warning here, but how do we get a logger?
539             // TODO getLog().warn( "Forced build is disabled, but disabling the forced mode isn't supported by the archiver." );
540         }
541 
542         // create archive
543         archiver.createArchive();
544     }
545     
546     
547     private Artifact findArtifactWithFile( Set artifacts, File file )
548     {
549         for ( Iterator iterator = artifacts.iterator(); iterator.hasNext(); )
550         {
551             Artifact artifact = (Artifact) iterator.next();
552             // normally not null but we can check
553             if ( artifact.getFile() != null )
554             {
555                 if ( artifact.getFile().equals( file ) )
556                 {
557                     return artifact;
558                 }
559             }
560         }
561         return null;
562     }
563 }