1 package org.apache.maven.archiver;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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.text.DateFormat;
46 import java.text.ParseException;
47 import java.text.SimpleDateFormat;
48 import java.util.ArrayList;
49 import java.util.Collections;
50 import java.util.Date;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Properties;
54 import java.util.Set;
55 import java.util.jar.Attributes;
56
57
58
59
60
61
62 public class MavenArchiver
63 {
64
65 private static final String CREATED_BY = "Maven Archiver";
66
67
68
69
70 public static final String SIMPLE_LAYOUT =
71 "${artifact.artifactId}-${artifact.version}${dashClassifier?}.${artifact.extension}";
72
73
74
75
76 public static final String REPOSITORY_LAYOUT =
77 "${artifact.groupIdPath}/${artifact.artifactId}/" + "${artifact.baseVersion}/${artifact.artifactId}-"
78 + "${artifact.version}${dashClassifier?}.${artifact.extension}";
79
80
81
82
83 public static final String SIMPLE_LAYOUT_NONUNIQUE =
84 "${artifact.artifactId}-${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";
85
86
87
88
89 public static final String REPOSITORY_LAYOUT_NONUNIQUE =
90 "${artifact.groupIdPath}/${artifact.artifactId}/" + "${artifact.baseVersion}/${artifact.artifactId}-"
91 + "${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";
92
93 private static final List<String> ARTIFACT_EXPRESSION_PREFIXES;
94
95 static
96 {
97 List<String> artifactExpressionPrefixes = new ArrayList<String>();
98 artifactExpressionPrefixes.add( "artifact." );
99
100 ARTIFACT_EXPRESSION_PREFIXES = artifactExpressionPrefixes;
101 }
102
103 static boolean isValidModuleName( String name )
104 {
105 return SourceVersion.isName( name );
106 }
107
108 private JarArchiver archiver;
109
110 private File archiveFile;
111
112 private String createdBy;
113
114 private boolean buildJdkSpecDefaultEntry = true;
115
116
117
118
119
120
121
122
123 public Manifest getManifest( MavenSession session, MavenProject project, MavenArchiveConfiguration config )
124 throws ManifestException, DependencyResolutionRequiredException
125 {
126 boolean hasManifestEntries = !config.isManifestEntriesEmpty();
127 Map<String, String> entries =
128 hasManifestEntries ? config.getManifestEntries() : Collections.<String, String>emptyMap();
129
130 Manifest manifest = getManifest( session, project, config.getManifest(), entries );
131
132
133 if ( hasManifestEntries )
134 {
135
136 for ( Map.Entry<String, String> entry : entries.entrySet() )
137 {
138 String key = entry.getKey();
139 String value = entry.getValue();
140 Manifest.Attribute attr = manifest.getMainSection().getAttribute( key );
141 if ( key.equals( Attributes.Name.CLASS_PATH.toString() ) && attr != null )
142 {
143
144
145
146 attr.setValue( value + " " + attr.getValue() );
147 }
148 else
149 {
150 addManifestAttribute( manifest, key, value );
151 }
152 }
153 }
154
155
156 if ( !config.isManifestSectionsEmpty() )
157 {
158 for ( ManifestSection section : config.getManifestSections() )
159 {
160 Manifest.Section theSection = new Manifest.Section();
161 theSection.setName( section.getName() );
162
163 if ( !section.isManifestEntriesEmpty() )
164 {
165 Map<String, String> sectionEntries = section.getManifestEntries();
166
167 for ( Map.Entry<String, String> entry : sectionEntries.entrySet() )
168 {
169 String key = entry.getKey();
170 String value = entry.getValue();
171 Manifest.Attribute attr = new Manifest.Attribute( key, value );
172 theSection.addConfiguredAttribute( attr );
173 }
174 }
175
176 manifest.addConfiguredSection( theSection );
177 }
178 }
179
180 return manifest;
181 }
182
183
184
185
186
187
188
189
190
191
192
193 public Manifest getManifest( MavenProject project, ManifestConfiguration config )
194 throws ManifestException, DependencyResolutionRequiredException
195 {
196 return getManifest( null, project, config, Collections.<String, String>emptyMap() );
197 }
198
199
200
201
202
203
204
205
206
207 public Manifest getManifest( MavenSession mavenSession, MavenProject project, ManifestConfiguration config )
208 throws ManifestException, DependencyResolutionRequiredException
209 {
210 return getManifest( mavenSession, project, config, Collections.<String, String>emptyMap() );
211 }
212
213 private void addManifestAttribute( Manifest manifest, Map<String, String> map, String key, String value )
214 throws ManifestException
215 {
216 if ( map.containsKey( key ) )
217 {
218 return;
219 }
220 addManifestAttribute( manifest, key, value );
221 }
222
223 private void addManifestAttribute( Manifest manifest, String key, String value )
224 throws ManifestException
225 {
226 if ( !StringUtils.isEmpty( value ) )
227 {
228 Manifest.Attribute attr = new Manifest.Attribute( key, value );
229 manifest.addConfiguredAttribute( attr );
230 }
231 else
232 {
233
234
235 Manifest.Attribute attr = new Manifest.Attribute( key, "" );
236 manifest.addConfiguredAttribute( attr );
237 }
238 }
239
240
241
242
243
244
245
246
247
248
249 protected Manifest getManifest( MavenSession session, MavenProject project, ManifestConfiguration config,
250 Map<String, String> entries )
251 throws ManifestException, DependencyResolutionRequiredException
252 {
253
254
255 Manifest m = new Manifest();
256
257 if ( config.isAddDefaultEntries() )
258 {
259 handleDefaultEntries( m, entries );
260 }
261
262
263 if ( config.isAddBuildEnvironmentEntries() )
264 {
265 handleBuildEnvironmentEntries( session, m, entries );
266 }
267
268 if ( config.isAddClasspath() )
269 {
270 StringBuilder classpath = new StringBuilder();
271
272 List<String> artifacts = project.getRuntimeClasspathElements();
273 String classpathPrefix = config.getClasspathPrefix();
274 String layoutType = config.getClasspathLayoutType();
275 String layout = config.getCustomClasspathLayout();
276
277 Interpolator interpolator = new StringSearchInterpolator();
278
279 for ( String artifactFile : artifacts )
280 {
281 File f = new File( artifactFile );
282 if ( f.getAbsoluteFile().isFile() )
283 {
284 Artifact artifact = findArtifactWithFile( project.getArtifacts(), f );
285
286 if ( classpath.length() > 0 )
287 {
288 classpath.append( " " );
289 }
290 classpath.append( classpathPrefix );
291
292
293
294 if ( artifact == null || layoutType == null )
295 {
296 classpath.append( f.getName() );
297 }
298 else
299 {
300 List<ValueSource> valueSources = new ArrayList<ValueSource>();
301
302 handleExtraExpression( artifact, valueSources );
303
304 for ( ValueSource vs : valueSources )
305 {
306 interpolator.addValueSource( vs );
307 }
308
309 RecursionInterceptor recursionInterceptor =
310 new PrefixAwareRecursionInterceptor( ARTIFACT_EXPRESSION_PREFIXES );
311
312 try
313 {
314 if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_SIMPLE.equals( layoutType ) )
315 {
316 if ( config.isUseUniqueVersions() )
317 {
318 classpath.append( interpolator.interpolate( SIMPLE_LAYOUT, recursionInterceptor ) );
319 }
320 else
321 {
322 classpath.append( interpolator.interpolate( SIMPLE_LAYOUT_NONUNIQUE,
323 recursionInterceptor ) );
324 }
325 }
326 else if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_REPOSITORY.equals( layoutType ) )
327 {
328
329
330 if ( config.isUseUniqueVersions() )
331 {
332 classpath.append( interpolator.interpolate( REPOSITORY_LAYOUT,
333 recursionInterceptor ) );
334 }
335 else
336 {
337 classpath.append( interpolator.interpolate( REPOSITORY_LAYOUT_NONUNIQUE,
338 recursionInterceptor ) );
339 }
340 }
341 else if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM.equals( layoutType ) )
342 {
343 if ( layout == null )
344 {
345 throw new ManifestException( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM
346 + " layout type was declared, but custom layout expression was not"
347 + " specified. Check your <archive><manifest><customLayout/> element." );
348 }
349
350 classpath.append( interpolator.interpolate( layout, recursionInterceptor ) );
351 }
352 else
353 {
354 throw new ManifestException( "Unknown classpath layout type: '" + layoutType
355 + "'. Check your <archive><manifest><layoutType/> element." );
356 }
357 }
358 catch ( InterpolationException e )
359 {
360 ManifestException error =
361 new ManifestException( "Error interpolating artifact path for classpath entry: "
362 + e.getMessage() );
363
364 error.initCause( e );
365 throw error;
366 }
367 finally
368 {
369 for ( ValueSource vs : valueSources )
370 {
371 interpolator.removeValuesSource( vs );
372 }
373 }
374 }
375 }
376 }
377
378 if ( classpath.length() > 0 )
379 {
380
381
382 addManifestAttribute( m, "Class-Path", classpath.toString() );
383 }
384 }
385
386 if ( config.isAddDefaultSpecificationEntries() )
387 {
388 handleSpecificationEntries( project, entries, m );
389 }
390
391 if ( config.isAddDefaultImplementationEntries() )
392 {
393 handleImplementationEntries( project, entries, m );
394 }
395
396 String mainClass = config.getMainClass();
397 if ( mainClass != null && !"".equals( mainClass ) )
398 {
399 addManifestAttribute( m, entries, "Main-Class", mainClass );
400 }
401
402 if ( config.isAddExtensions() )
403 {
404 handleExtensions( project, entries, m );
405 }
406
407 addCustomEntries( m, entries, config );
408
409 return m;
410 }
411
412 private void handleExtraExpression( Artifact artifact, List<ValueSource> valueSources )
413 {
414 valueSources.add( new PrefixedObjectValueSource( ARTIFACT_EXPRESSION_PREFIXES, artifact,
415 true ) );
416 valueSources.add( new PrefixedObjectValueSource( ARTIFACT_EXPRESSION_PREFIXES,
417 artifact.getArtifactHandler(), true ) );
418
419 Properties extraExpressions = new Properties();
420
421
422 if ( !artifact.isSnapshot() )
423 {
424 extraExpressions.setProperty( "baseVersion", artifact.getVersion() );
425 }
426
427 extraExpressions.setProperty( "groupIdPath", artifact.getGroupId().replace( '.', '/' ) );
428 if ( StringUtils.isNotEmpty( artifact.getClassifier() ) )
429 {
430 extraExpressions.setProperty( "dashClassifier", "-" + artifact.getClassifier() );
431 extraExpressions.setProperty( "dashClassifier?", "-" + artifact.getClassifier() );
432 }
433 else
434 {
435 extraExpressions.setProperty( "dashClassifier", "" );
436 extraExpressions.setProperty( "dashClassifier?", "" );
437 }
438 valueSources.add( new PrefixedPropertiesValueSource( ARTIFACT_EXPRESSION_PREFIXES,
439 extraExpressions, true ) );
440 }
441
442 private void handleExtensions( MavenProject project, Map<String, String> entries, Manifest m )
443 throws ManifestException
444 {
445
446 StringBuilder extensionsList = new StringBuilder();
447 Set<Artifact> artifacts = (Set<Artifact>) project.getArtifacts();
448
449 for ( Artifact artifact : artifacts )
450 {
451 if ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
452 {
453 if ( "jar".equals( artifact.getType() ) )
454 {
455 if ( extensionsList.length() > 0 )
456 {
457 extensionsList.append( " " );
458 }
459 extensionsList.append( artifact.getArtifactId() );
460 }
461 }
462 }
463
464 if ( extensionsList.length() > 0 )
465 {
466 addManifestAttribute( m, entries, "Extension-List", extensionsList.toString() );
467 }
468
469 for ( Object artifact1 : artifacts )
470 {
471
472
473 Artifact artifact = (Artifact) artifact1;
474 if ( "jar".equals( artifact.getType() ) )
475 {
476 String artifactId = artifact.getArtifactId().replace( '.', '_' );
477 String ename = artifactId + "-Extension-Name";
478 addManifestAttribute( m, entries, ename, artifact.getArtifactId() );
479 String iname = artifactId + "-Implementation-Version";
480 addManifestAttribute( m, entries, iname, artifact.getVersion() );
481
482 if ( artifact.getRepository() != null )
483 {
484 iname = artifactId + "-Implementation-URL";
485 String url = artifact.getRepository().getUrl() + "/" + artifact.toString();
486 addManifestAttribute( m, entries, iname, url );
487 }
488 }
489 }
490 }
491
492 private void handleImplementationEntries( MavenProject project, Map<String, String> entries, Manifest m )
493 throws ManifestException
494 {
495 addManifestAttribute( m, entries, "Implementation-Title", project.getName() );
496 addManifestAttribute( m, entries, "Implementation-Version", project.getVersion() );
497
498 if ( project.getOrganization() != null )
499 {
500 addManifestAttribute( m, entries, "Implementation-Vendor", project.getOrganization().getName() );
501 }
502 }
503
504 private void handleSpecificationEntries( MavenProject project, Map<String, String> entries, Manifest m )
505 throws ManifestException
506 {
507 addManifestAttribute( m, entries, "Specification-Title", project.getName() );
508
509 try
510 {
511 ArtifactVersion version = project.getArtifact().getSelectedVersion();
512 String specVersion = String.format( "%s.%s", version.getMajorVersion(), version.getMinorVersion() );
513 addManifestAttribute( m, entries, "Specification-Version", specVersion );
514 }
515 catch ( OverConstrainedVersionException e )
516 {
517 throw new ManifestException( "Failed to get selected artifact version to calculate"
518 + " the specification version: " + e.getMessage() );
519 }
520
521 if ( project.getOrganization() != null )
522 {
523 addManifestAttribute( m, entries, "Specification-Vendor", project.getOrganization().getName() );
524 }
525 }
526
527 private void addCustomEntries( Manifest m, Map<String, String> entries, ManifestConfiguration config )
528 throws ManifestException
529 {
530
531
532
533
534
535 if ( config.getPackageName() != null )
536 {
537 addManifestAttribute( m, entries, "Package", config.getPackageName() );
538 }
539 }
540
541
542
543
544 public JarArchiver getArchiver()
545 {
546 return archiver;
547 }
548
549
550
551
552 public void setArchiver( JarArchiver archiver )
553 {
554 this.archiver = archiver;
555 }
556
557
558
559
560 public void setOutputFile( File outputFile )
561 {
562 archiveFile = outputFile;
563 }
564
565
566
567
568
569
570
571
572
573
574 public void createArchive( MavenSession session, MavenProject project,
575 MavenArchiveConfiguration archiveConfiguration )
576 throws ManifestException, IOException,
577 DependencyResolutionRequiredException
578 {
579
580
581 MavenProject workingProject = null;
582 workingProject = (MavenProject) project.clone();
583
584 boolean forced = archiveConfiguration.isForced();
585 if ( archiveConfiguration.isAddMavenDescriptor() )
586 {
587
588
589
590
591
592
593
594
595
596
597
598 if ( workingProject.getArtifact().isSnapshot() )
599 {
600 workingProject.setVersion( workingProject.getArtifact().getVersion() );
601 }
602
603 String groupId = workingProject.getGroupId();
604
605 String artifactId = workingProject.getArtifactId();
606
607 archiver.addFile( project.getFile(), "META-INF/maven/" + groupId + "/" + artifactId + "/pom.xml" );
608
609
610
611
612
613 File customPomPropertiesFile = archiveConfiguration.getPomPropertiesFile();
614 File dir = new File( workingProject.getBuild().getDirectory(), "maven-archiver" );
615 File pomPropertiesFile = new File( dir, "pom.properties" );
616
617 new PomPropertiesUtil().createPomProperties( session, workingProject, archiver,
618 customPomPropertiesFile, pomPropertiesFile, forced );
619 }
620
621
622
623
624
625 archiver.setMinimalDefaultManifest( true );
626
627 File manifestFile = archiveConfiguration.getManifestFile();
628
629 if ( manifestFile != null )
630 {
631 archiver.setManifest( manifestFile );
632 }
633
634 Manifest manifest = getManifest( session, workingProject, archiveConfiguration );
635
636
637 archiver.addConfiguredManifest( manifest );
638
639 archiver.setCompress( archiveConfiguration.isCompress() );
640
641 archiver.setRecompressAddedZips( archiveConfiguration.isRecompressAddedZips() );
642
643 archiver.setIndex( archiveConfiguration.isIndex() );
644
645 archiver.setDestFile( archiveFile );
646
647
648 if ( archiveConfiguration.getManifest().isAddClasspath() )
649 {
650 List<String> artifacts = project.getRuntimeClasspathElements();
651 for ( String artifact : artifacts )
652 {
653 File f = new File( artifact );
654 archiver.addConfiguredIndexJars( f );
655 }
656 }
657
658 archiver.setForced( forced );
659 if ( !archiveConfiguration.isForced() && archiver.isSupportingForced() )
660 {
661
662
663
664 }
665
666 String automaticModuleName = manifest.getMainSection().getAttributeValue( "Automatic-Module-Name" );
667 if ( automaticModuleName != null )
668 {
669 if ( !isValidModuleName( automaticModuleName ) )
670 {
671 throw new ManifestException( "Invalid automatic module name: '" + automaticModuleName + "'" );
672 }
673 }
674
675
676 archiver.createArchive();
677 }
678
679 private void handleDefaultEntries( Manifest m, Map<String, String> entries )
680 throws ManifestException
681 {
682 String createdBy = this.createdBy;
683 if ( createdBy == null )
684 {
685 createdBy = createdBy( CREATED_BY, "org.apache.maven", "maven-archiver" );
686 }
687 addManifestAttribute( m, entries, "Created-By", createdBy );
688 if ( buildJdkSpecDefaultEntry )
689 {
690 addManifestAttribute( m, entries, "Build-Jdk-Spec", System.getProperty( "java.specification.version" ) );
691 }
692 }
693
694 private void handleBuildEnvironmentEntries( MavenSession session, Manifest m, Map<String, String> entries )
695 throws ManifestException
696 {
697 addManifestAttribute( m, entries, "Build-Tool",
698 session != null ? session.getSystemProperties().getProperty( "maven.build.version" ) : "Apache Maven" );
699 addManifestAttribute( m, entries, "Build-Jdk", String.format( "%s (%s)", System.getProperty( "java.version" ),
700 System.getProperty( "java.vendor" ) ) );
701 addManifestAttribute( m, entries, "Build-Os", String.format( "%s (%s; %s)", System.getProperty( "os.name" ),
702 System.getProperty( "os.version" ), System.getProperty( "os.arch" ) ) );
703 }
704
705 private Artifact findArtifactWithFile( Set<Artifact> artifacts, File file )
706 {
707 for ( Artifact artifact : artifacts )
708 {
709
710 if ( artifact.getFile() != null )
711 {
712 if ( artifact.getFile().equals( file ) )
713 {
714 return artifact;
715 }
716 }
717 }
718 return null;
719 }
720
721 private static String getCreatedByVersion( String groupId, String artifactId )
722 {
723 final Properties properties = PropertyUtils.loadOptionalProperties( MavenArchiver.class.getResourceAsStream(
724 "/META-INF/maven/" + groupId + "/" + artifactId + "/pom.properties" ) );
725
726 return properties.getProperty( "version" );
727 }
728
729
730
731
732
733
734
735
736
737 public void setCreatedBy( String description, String groupId, String artifactId )
738 {
739 createdBy = createdBy( description, groupId, artifactId );
740 }
741
742 private String createdBy( String description, String groupId, String artifactId )
743 {
744 String createdBy = description;
745 String version = getCreatedByVersion( groupId, artifactId );
746 if ( version != null )
747 {
748 createdBy += " " + version;
749 }
750 return createdBy;
751 }
752
753
754
755
756
757
758
759
760 public void setBuildJdkSpecDefaultEntry( boolean buildJdkSpecDefaultEntry )
761 {
762 this.buildJdkSpecDefaultEntry = buildJdkSpecDefaultEntry;
763 }
764
765
766
767
768
769
770
771
772
773
774
775 public Date parseOutputTimestamp( String outputTimestamp )
776 {
777 if ( StringUtils.isNumeric( outputTimestamp ) && StringUtils.isNotEmpty( outputTimestamp ) )
778 {
779 return new Date( Long.parseLong( outputTimestamp ) * 1000 );
780 }
781
782 if ( outputTimestamp == null || outputTimestamp.length() < 2 )
783 {
784
785
786 return null;
787 }
788
789 DateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssXXX" );
790 try
791 {
792 return df.parse( outputTimestamp );
793 }
794 catch ( ParseException pe )
795 {
796 throw new IllegalArgumentException( "Invalid project.build.outputTimestamp value '" + outputTimestamp + "'",
797 pe );
798 }
799 }
800
801
802
803
804
805
806
807
808
809
810 public Date configureReproducible( String outputTimestamp )
811 {
812 Date outputDate = parseOutputTimestamp( outputTimestamp );
813 if ( outputDate != null )
814 {
815 getArchiver().configureReproducible( outputDate );
816 }
817 return outputDate;
818 }
819 }