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