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.artifact.versioning.ArtifactVersion;
25  import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
26  import org.apache.maven.execution.MavenSession;
27  import org.apache.maven.project.MavenProject;
28  import org.codehaus.plexus.archiver.jar.JarArchiver;
29  import org.codehaus.plexus.archiver.jar.Manifest;
30  import org.codehaus.plexus.archiver.jar.ManifestException;
31  import org.codehaus.plexus.interpolation.InterpolationException;
32  import org.codehaus.plexus.interpolation.Interpolator;
33  import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
34  import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
35  import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource;
36  import org.codehaus.plexus.interpolation.RecursionInterceptor;
37  import org.codehaus.plexus.interpolation.StringSearchInterpolator;
38  import org.codehaus.plexus.interpolation.ValueSource;
39  import org.apache.maven.shared.utils.PropertyUtils;
40  import org.apache.maven.shared.utils.StringUtils;
41  
42  import javax.lang.model.SourceVersion;
43  import java.io.File;
44  import java.io.IOException;
45  import java.util.ArrayList;
46  import java.util.Collections;
47  import java.util.List;
48  import java.util.Map;
49  import java.util.Properties;
50  import java.util.Set;
51  import java.util.jar.Attributes;
52  
53  /**
54   * @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
55   * @author kama
56   * @version $Revision$ $Date$
57   */
58  public class MavenArchiver
59  {
60  
61      private static final String CREATED_BY = "Maven Archiver";
62  
63      /**
64       * The simply layout.
65       */
66      public static final String SIMPLE_LAYOUT =
67          "${artifact.artifactId}-${artifact.version}${dashClassifier?}.${artifact.extension}";
68  
69      /**
70       * Repository layout.
71       */
72      public static final String REPOSITORY_LAYOUT =
73          "${artifact.groupIdPath}/${artifact.artifactId}/" + "${artifact.baseVersion}/${artifact.artifactId}-"
74              + "${artifact.version}${dashClassifier?}.${artifact.extension}";
75  
76      /**
77       * simple layout non unique.
78       */
79      public static final String SIMPLE_LAYOUT_NONUNIQUE =
80          "${artifact.artifactId}-${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";
81  
82      /**
83       * Repository layout non unique.
84       */
85      public static final String REPOSITORY_LAYOUT_NONUNIQUE =
86          "${artifact.groupIdPath}/${artifact.artifactId}/" + "${artifact.baseVersion}/${artifact.artifactId}-"
87              + "${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";
88  
89      private static final List<String> ARTIFACT_EXPRESSION_PREFIXES;
90  
91      static
92      {
93          List<String> artifactExpressionPrefixes = new ArrayList<String>();
94          artifactExpressionPrefixes.add( "artifact." );
95  
96          ARTIFACT_EXPRESSION_PREFIXES = artifactExpressionPrefixes;
97      }
98  
99      static boolean isValidModuleName( String name )
100     {
101         return SourceVersion.isName( name );
102     }
103 
104     private JarArchiver archiver;
105 
106     private File archiveFile;
107 
108     /**
109      * @param session The Maven Session.
110      * @param project The Maven Project.
111      * @param config The MavenArchiveConfiguration
112      * @return The {@link Manifest}
113      * @throws ManifestException In case of a failure.
114      * @throws DependencyResolutionRequiredException Resolution failure.
115      */
116     public Manifest getManifest( MavenSession session, MavenProject project, MavenArchiveConfiguration config )
117         throws ManifestException, DependencyResolutionRequiredException
118     {
119         boolean hasManifestEntries = !config.isManifestEntriesEmpty();
120         Map<String, String> entries =
121             hasManifestEntries ? config.getManifestEntries() : Collections.<String, String>emptyMap();
122 
123         Manifest manifest = getManifest( session, project, config.getManifest(), entries );
124 
125         // any custom manifest entries in the archive configuration manifest?
126         if ( hasManifestEntries )
127         {
128 
129             for ( Map.Entry<String, String> entry : entries.entrySet() )
130             {
131                 String key = entry.getKey();
132                 String value = entry.getValue();
133                 Manifest.Attribute attr = manifest.getMainSection().getAttribute( key );
134                 if ( key.equals( Attributes.Name.CLASS_PATH.toString() ) && attr != null )
135                 {
136                     // Merge the user-supplied Class-Path value with the programmatically
137                     // created Class-Path. Note that the user-supplied value goes first
138                     // so that resources there will override any in the standard Class-Path.
139                     attr.setValue( value + " " + attr.getValue() );
140                 }
141                 else
142                 {
143                     addManifestAttribute( manifest, key, value );
144                 }
145             }
146         }
147 
148         // any custom manifest sections in the archive configuration manifest?
149         if ( !config.isManifestSectionsEmpty() )
150         {
151             for ( ManifestSection section : config.getManifestSections() )
152             {
153                 Manifest.Section theSection = new Manifest.Section();
154                 theSection.setName( section.getName() );
155 
156                 if ( !section.isManifestEntriesEmpty() )
157                 {
158                     Map<String, String> sectionEntries = section.getManifestEntries();
159 
160                     for ( Map.Entry<String, String> entry : sectionEntries.entrySet() )
161                     {
162                         String key = entry.getKey();
163                         String value = entry.getValue();
164                         Manifest.Attribute attr = new Manifest.Attribute( key, value );
165                         theSection.addConfiguredAttribute( attr );
166                     }
167                 }
168 
169                 manifest.addConfiguredSection( theSection );
170             }
171         }
172 
173         return manifest;
174     }
175 
176     /**
177      * Return a pre-configured manifest
178      *
179      * @param project {@link MavenProject}
180      * @param config {@link ManifestConfiguration}
181      * @return {@link Manifest}
182      * @throws ManifestException Manifest exception.
183      * @throws DependencyResolutionRequiredException Dependency resolution exception.
184      */
185     // TODO Add user attributes list and user groups list
186     public Manifest getManifest( MavenProject project, ManifestConfiguration config )
187         throws ManifestException, DependencyResolutionRequiredException
188     {
189         return getManifest( null, project, config, Collections.<String, String>emptyMap() );
190     }
191 
192     /**
193      * @param mavenSession {@link MavenSession}
194      * @param project {@link MavenProject}
195      * @param config {@link ManifestConfiguration}
196      * @return {@link Manifest}
197      * @throws ManifestException The manifest exception.
198      * @throws DependencyResolutionRequiredException The dependency resolution required exception.
199      */
200     public Manifest getManifest( MavenSession mavenSession, MavenProject project, ManifestConfiguration config )
201         throws ManifestException, DependencyResolutionRequiredException
202     {
203         return getManifest( mavenSession, project, config, Collections.<String, String>emptyMap() );
204     }
205 
206     private void addManifestAttribute( Manifest manifest, Map<String, String> map, String key, String value )
207         throws ManifestException
208     {
209         if ( map.containsKey( key ) )
210         {
211             return; // The map value will be added later
212         }
213         addManifestAttribute( manifest, key, value );
214     }
215 
216     private void addManifestAttribute( Manifest manifest, String key, String value )
217         throws ManifestException
218     {
219         if ( !StringUtils.isEmpty( value ) )
220         {
221             Manifest.Attribute attr = new Manifest.Attribute( key, value );
222             manifest.addConfiguredAttribute( attr );
223         }
224         else
225         {
226             // if the value is empty we have create an entry with an empty string
227             // to prevent null print in the manifest file
228             Manifest.Attribute attr = new Manifest.Attribute( key, "" );
229             manifest.addConfiguredAttribute( attr );
230         }
231     }
232 
233     /**
234      * @param session {@link MavenSession}
235      * @param project {@link MavenProject}
236      * @param config {@link ManifestConfiguration}
237      * @param entries The entries.
238      * @return {@link Manifest}
239      * @throws ManifestException The manifest exception.
240      * @throws DependencyResolutionRequiredException The dependency resolution required exception.
241      */
242     protected Manifest getManifest( MavenSession session, MavenProject project, ManifestConfiguration config,
243                                     Map<String, String> entries )
244                                         throws ManifestException, DependencyResolutionRequiredException
245     {
246         // TODO: Should we replace "map" with a copy? Note, that we modify it!
247 
248         Manifest m = new Manifest();
249 
250         if ( config.isAddDefaultEntries() )
251         {
252             handleDefaultEntries( m, entries );
253         }
254 
255 
256         if ( config.isAddBuildEnvironmentEntries() )
257         {
258             handleBuildEnvironmentEntries( session, m, entries );
259         }
260 
261         if ( config.isAddClasspath() )
262         {
263             StringBuilder classpath = new StringBuilder();
264 
265             List<String> artifacts = project.getRuntimeClasspathElements();
266             String classpathPrefix = config.getClasspathPrefix();
267             String layoutType = config.getClasspathLayoutType();
268             String layout = config.getCustomClasspathLayout();
269 
270             Interpolator interpolator = new StringSearchInterpolator();
271 
272             for ( String artifactFile : artifacts )
273             {
274                 File f = new File( artifactFile );
275                 if ( f.getAbsoluteFile().isFile() )
276                 {
277                     Artifact artifact = findArtifactWithFile( project.getArtifacts(), f );
278 
279                     if ( classpath.length() > 0 )
280                     {
281                         classpath.append( " " );
282                     }
283                     classpath.append( classpathPrefix );
284 
285                     // NOTE: If the artifact or layout type (from config) is null, give up and use the file name by
286                     // itself.
287                     if ( artifact == null || layoutType == null )
288                     {
289                         classpath.append( f.getName() );
290                     }
291                     else
292                     {
293                         List<ValueSource> valueSources = new ArrayList<ValueSource>();
294 
295                         handleExtraExpression( artifact, valueSources );
296 
297                         for ( ValueSource vs : valueSources )
298                         {
299                             interpolator.addValueSource( vs );
300                         }
301 
302                         RecursionInterceptor recursionInterceptor =
303                             new PrefixAwareRecursionInterceptor( ARTIFACT_EXPRESSION_PREFIXES );
304 
305                         try
306                         {
307                             if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_SIMPLE.equals( layoutType ) )
308                             {
309                                 if ( config.isUseUniqueVersions() )
310                                 {
311                                     classpath.append( interpolator.interpolate( SIMPLE_LAYOUT, recursionInterceptor ) );
312                                 }
313                                 else
314                                 {
315                                     classpath.append( interpolator.interpolate( SIMPLE_LAYOUT_NONUNIQUE,
316                                                                                 recursionInterceptor ) );
317                                 }
318                             }
319                             else if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_REPOSITORY.equals( layoutType ) )
320                             {
321                                 // we use layout /$groupId[0]/../${groupId[n]/$artifactId/$version/{fileName}
322                                 // here we must find the Artifact in the project Artifacts to create the maven layout
323                                 if ( config.isUseUniqueVersions() )
324                                 {
325                                     classpath.append( interpolator.interpolate( REPOSITORY_LAYOUT,
326                                                                                 recursionInterceptor ) );
327                                 }
328                                 else
329                                 {
330                                     classpath.append( interpolator.interpolate( REPOSITORY_LAYOUT_NONUNIQUE,
331                                                                                 recursionInterceptor ) );
332                                 }
333                             }
334                             else if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM.equals( layoutType ) )
335                             {
336                                 if ( layout == null )
337                                 {
338                                     throw new ManifestException( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM
339                                         + " layout type was declared, but custom layout expression was not"
340                                         + " specified. Check your <archive><manifest><customLayout/> element." );
341                                 }
342 
343                                 classpath.append( interpolator.interpolate( layout, recursionInterceptor ) );
344                             }
345                             else
346                             {
347                                 throw new ManifestException( "Unknown classpath layout type: '" + layoutType
348                                     + "'. Check your <archive><manifest><layoutType/> element." );
349                             }
350                         }
351                         catch ( InterpolationException e )
352                         {
353                             ManifestException error =
354                                 new ManifestException( "Error interpolating artifact path for classpath entry: "
355                                     + e.getMessage() );
356 
357                             error.initCause( e );
358                             throw error;
359                         }
360                         finally
361                         {
362                             for ( ValueSource vs : valueSources )
363                             {
364                                 interpolator.removeValuesSource( vs );
365                             }
366                         }
367                     }
368                 }
369             }
370 
371             if ( classpath.length() > 0 )
372             {
373                 // Class-Path is special and should be added to manifest even if
374                 // it is specified in the manifestEntries section
375                 addManifestAttribute( m, "Class-Path", classpath.toString() );
376             }
377         }
378 
379         if ( config.isAddDefaultSpecificationEntries() )
380         {
381             handleSpecificationEntries( project, entries, m );
382         }
383 
384         if ( config.isAddDefaultImplementationEntries() )
385         {
386             handleImplementationEntries( project, entries, m );
387         }
388 
389         String mainClass = config.getMainClass();
390         if ( mainClass != null && !"".equals( mainClass ) )
391         {
392             addManifestAttribute( m, entries, "Main-Class", mainClass );
393         }
394 
395         if ( config.isAddExtensions() )
396         {
397             handleExtensions( project, entries, m );
398         }
399 
400         addCustomEntries( m, entries, config );
401 
402         return m;
403     }
404 
405     private void handleExtraExpression( Artifact artifact, List<ValueSource> valueSources )
406     {
407         valueSources.add( new PrefixedObjectValueSource( ARTIFACT_EXPRESSION_PREFIXES, artifact,
408                                                          true ) );
409         valueSources.add( new PrefixedObjectValueSource( ARTIFACT_EXPRESSION_PREFIXES,
410                                                          artifact.getArtifactHandler(), true ) );
411 
412         Properties extraExpressions = new Properties();
413         // FIXME: This query method SHOULD NOT affect the internal
414         // state of the artifact version, but it does.
415         if ( !artifact.isSnapshot() )
416         {
417             extraExpressions.setProperty( "baseVersion", artifact.getVersion() );
418         }
419 
420         extraExpressions.setProperty( "groupIdPath", artifact.getGroupId().replace( '.', '/' ) );
421         if ( StringUtils.isNotEmpty( artifact.getClassifier() ) )
422         {
423             extraExpressions.setProperty( "dashClassifier", "-" + artifact.getClassifier() );
424             extraExpressions.setProperty( "dashClassifier?", "-" + artifact.getClassifier() );
425         }
426         else
427         {
428             extraExpressions.setProperty( "dashClassifier", "" );
429             extraExpressions.setProperty( "dashClassifier?", "" );
430         }
431         valueSources.add( new PrefixedPropertiesValueSource( ARTIFACT_EXPRESSION_PREFIXES,
432                                                              extraExpressions, true ) );
433     }
434 
435     private void handleExtensions( MavenProject project, Map<String, String> entries, Manifest m )
436         throws ManifestException
437     {
438         // TODO: this is only for applets - should we distinguish them as a packaging?
439         StringBuilder extensionsList = new StringBuilder();
440         Set<Artifact> artifacts = (Set<Artifact>) project.getArtifacts();
441 
442         for ( Artifact artifact : artifacts )
443         {
444             if ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
445             {
446                 if ( "jar".equals( artifact.getType() ) )
447                 {
448                     if ( extensionsList.length() > 0 )
449                     {
450                         extensionsList.append( " " );
451                     }
452                     extensionsList.append( artifact.getArtifactId() );
453                 }
454             }
455         }
456 
457         if ( extensionsList.length() > 0 )
458         {
459             addManifestAttribute( m, entries, "Extension-List", extensionsList.toString() );
460         }
461 
462         for ( Object artifact1 : artifacts )
463         {
464             // TODO: the correct solution here would be to have an extension type, and to read
465             // the real extension values either from the artifact's manifest or some part of the POM
466             Artifact artifact = (Artifact) artifact1;
467             if ( "jar".equals( artifact.getType() ) )
468             {
469                 String artifactId = artifact.getArtifactId().replace( '.', '_' );
470                 String ename = artifactId + "-Extension-Name";
471                 addManifestAttribute( m, entries, ename, artifact.getArtifactId() );
472                 String iname = artifactId + "-Implementation-Version";
473                 addManifestAttribute( m, entries, iname, artifact.getVersion() );
474 
475                 if ( artifact.getRepository() != null )
476                 {
477                     iname = artifactId + "-Implementation-URL";
478                     String url = artifact.getRepository().getUrl() + "/" + artifact.toString();
479                     addManifestAttribute( m, entries, iname, url );
480                 }
481             }
482         }
483     }
484 
485     private void handleImplementationEntries( MavenProject project, Map<String, String> entries, Manifest m )
486         throws ManifestException
487     {
488         addManifestAttribute( m, entries, "Implementation-Title", project.getName() );
489         addManifestAttribute( m, entries, "Implementation-Version", project.getVersion() );
490 
491         if ( project.getOrganization() != null )
492         {
493             addManifestAttribute( m, entries, "Implementation-Vendor", project.getOrganization().getName() );
494         }
495     }
496 
497     private void handleSpecificationEntries( MavenProject project, Map<String, String> entries, Manifest m )
498         throws ManifestException
499     {
500         addManifestAttribute( m, entries, "Specification-Title", project.getName() );
501 
502         try
503         {
504             ArtifactVersion version = project.getArtifact().getSelectedVersion();
505             String specVersion = String.format( "%s.%s", version.getMajorVersion(), version.getMinorVersion() );
506             addManifestAttribute( m, entries, "Specification-Version", specVersion );
507         }
508         catch ( OverConstrainedVersionException e )
509         {
510             throw new ManifestException( "Failed to get selected artifact version to calculate"
511                 + " the specification version: " + e.getMessage() );
512         }
513 
514         if ( project.getOrganization() != null )
515         {
516             addManifestAttribute( m, entries, "Specification-Vendor", project.getOrganization().getName() );
517         }
518     }
519 
520     private void addCustomEntries( Manifest m, Map<String, String> entries, ManifestConfiguration config )
521         throws ManifestException
522     {
523         /*
524          * TODO: rethink this, it wasn't working Artifact projectArtifact = project.getArtifact(); if (
525          * projectArtifact.isSnapshot() ) { Manifest.Attribute buildNumberAttr = new Manifest.Attribute( "Build-Number",
526          * "" + project.getSnapshotDeploymentBuildNumber() ); m.addConfiguredAttribute( buildNumberAttr ); }
527          */
528         if ( config.getPackageName() != null )
529         {
530             addManifestAttribute( m, entries, "Package", config.getPackageName() );
531         }
532     }
533 
534     /**
535      * @return {@link JarArchiver}
536      */
537     public JarArchiver getArchiver()
538     {
539         return archiver;
540     }
541 
542     /**
543      * @param archiver {@link JarArchiver}
544      */
545     public void setArchiver( JarArchiver archiver )
546     {
547         this.archiver = archiver;
548     }
549 
550     /**
551      * @param outputFile Set output file.
552      */
553     public void setOutputFile( File outputFile )
554     {
555         archiveFile = outputFile;
556     }
557 
558     /**
559      * @param session {@link MavenSession}
560      * @param project {@link MavenProject}
561      * @param archiveConfiguration {@link MavenArchiveConfiguration}
562      * @throws org.codehaus.plexus.archiver.ArchiverException Archiver Exception.
563      * @throws ManifestException Manifest Exception.
564      * @throws IOException IO Exception.
565      * @throws DependencyResolutionRequiredException Dependency resolution exception.
566      */
567     public void createArchive( MavenSession session, MavenProject project,
568                                MavenArchiveConfiguration archiveConfiguration )
569                                    throws ManifestException, IOException,
570                                    DependencyResolutionRequiredException
571     {
572         // we have to clone the project instance so we can write out the pom with the deployment version,
573         // without impacting the main project instance...
574         MavenProject workingProject = null;
575         workingProject = (MavenProject) project.clone();
576 
577         boolean forced = archiveConfiguration.isForced();
578         if ( archiveConfiguration.isAddMavenDescriptor() )
579         {
580             // ----------------------------------------------------------------------
581             // We want to add the metadata for the project to the JAR in two forms:
582             //
583             // The first form is that of the POM itself. Applications that wish to
584             // access the POM for an artifact using maven tools they can.
585             //
586             // The second form is that of a properties file containing the basic
587             // top-level POM elements so that applications that wish to access
588             // POM information without the use of maven tools can do so.
589             // ----------------------------------------------------------------------
590 
591             if ( workingProject.getArtifact().isSnapshot() )
592             {
593                 workingProject.setVersion( workingProject.getArtifact().getVersion() );
594             }
595 
596             String groupId = workingProject.getGroupId();
597 
598             String artifactId = workingProject.getArtifactId();
599 
600             archiver.addFile( project.getFile(), "META-INF/maven/" + groupId + "/" + artifactId + "/pom.xml" );
601 
602             // ----------------------------------------------------------------------
603             // Create pom.properties file
604             // ----------------------------------------------------------------------
605 
606             File customPomPropertiesFile = archiveConfiguration.getPomPropertiesFile();
607             File dir = new File( workingProject.getBuild().getDirectory(), "maven-archiver" );
608             File pomPropertiesFile = new File( dir, "pom.properties" );
609 
610             new PomPropertiesUtil().createPomProperties( session, workingProject, archiver,
611                 customPomPropertiesFile, pomPropertiesFile, forced );
612         }
613 
614         // ----------------------------------------------------------------------
615         // Create the manifest
616         // ----------------------------------------------------------------------
617 
618         archiver.setMinimalDefaultManifest( true );
619 
620         File manifestFile = archiveConfiguration.getManifestFile();
621 
622         if ( manifestFile != null )
623         {
624             archiver.setManifest( manifestFile );
625         }
626 
627         Manifest manifest = getManifest( session, workingProject, archiveConfiguration );
628 
629         // Configure the jar
630         archiver.addConfiguredManifest( manifest );
631 
632         archiver.setCompress( archiveConfiguration.isCompress() );
633 
634         archiver.setRecompressAddedZips( archiveConfiguration.isRecompressAddedZips() );
635 
636         archiver.setIndex( archiveConfiguration.isIndex() );
637 
638         archiver.setDestFile( archiveFile );
639 
640         // make the archiver index the jars on the classpath, if we are adding that to the manifest
641         if ( archiveConfiguration.getManifest().isAddClasspath() )
642         {
643             List<String> artifacts = project.getRuntimeClasspathElements();
644             for ( String artifact : artifacts )
645             {
646                 File f = new File( artifact );
647                 archiver.addConfiguredIndexJars( f );
648             }
649         }
650 
651         archiver.setForced( forced );
652         if ( !archiveConfiguration.isForced() && archiver.isSupportingForced() )
653         {
654             // TODO Should issue a warning here, but how do we get a logger?
655             // TODO getLog().warn(
656             // "Forced build is disabled, but disabling the forced mode isn't supported by the archiver." );
657         }
658 
659         String automaticModuleName = manifest.getMainSection().getAttributeValue( "Automatic-Module-Name" );
660         if ( automaticModuleName != null )
661         {
662             if ( !isValidModuleName( automaticModuleName ) )
663             {
664                 throw new ManifestException( "Invalid automatic module name: '" + automaticModuleName + "'" );
665             }
666         }
667 
668         // create archive
669         archiver.createArchive();
670     }
671 
672     private void handleDefaultEntries( Manifest m, Map<String, String> entries )
673         throws ManifestException
674     {
675          String createdBy = CREATED_BY;
676          String archiverVersion = getArchiverVersion();
677          if ( archiverVersion != null )
678          {
679              createdBy += " " + archiverVersion;
680          }
681          addManifestAttribute( m, entries, "Created-By", createdBy );
682          addManifestAttribute( m, entries, "Build-Jdk-Spec", System.getProperty( "java.specification.version" ) );
683     }
684 
685     private void handleBuildEnvironmentEntries( MavenSession session, Manifest m, Map<String, String> entries )
686         throws ManifestException
687     {
688         addManifestAttribute( m, entries, "Build-Tool",
689             session != null ? session.getSystemProperties().getProperty( "maven.build.version" ) : "Apache Maven" );
690         addManifestAttribute( m, entries, "Build-Jdk", String.format( "%s (%s)", System.getProperty( "java.version" ),
691             System.getProperty( "java.vendor" ) ) );
692         addManifestAttribute( m, entries, "Build-Os", String.format( "%s (%s; %s)", System.getProperty( "os.name" ),
693             System.getProperty( "os.version" ), System.getProperty( "os.arch" ) ) );
694     }
695 
696     private Artifact findArtifactWithFile( Set<Artifact> artifacts, File file )
697     {
698         for ( Artifact artifact : artifacts )
699         {
700             // normally not null but we can check
701             if ( artifact.getFile() != null )
702             {
703                 if ( artifact.getFile().equals( file ) )
704                 {
705                     return artifact;
706                 }
707             }
708         }
709         return null;
710     }
711 
712     private static String getArchiverVersion()
713     {
714         final Properties properties = PropertyUtils.loadOptionalProperties( MavenArchiver.class.getResourceAsStream(
715             "/META-INF/maven/org.apache.maven/maven-archiver/pom.properties" ) );
716 
717         return properties.getProperty( "version" );
718     }
719 
720 }