View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.archiver;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.net.URI;
25  import java.net.URL;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.nio.file.Paths;
29  import java.nio.file.attribute.FileTime;
30  import java.time.Instant;
31  import java.time.format.DateTimeParseException;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collections;
35  import java.util.Comparator;
36  import java.util.HashMap;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Properties;
40  import java.util.Set;
41  import java.util.TreeSet;
42  import java.util.jar.Attributes;
43  import java.util.jar.JarFile;
44  import java.util.jar.Manifest;
45  import java.util.stream.Stream;
46  import java.util.zip.ZipEntry;
47  
48  import org.apache.maven.artifact.Artifact;
49  import org.apache.maven.artifact.handler.ArtifactHandler;
50  import org.apache.maven.artifact.handler.DefaultArtifactHandler;
51  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
52  import org.apache.maven.execution.MavenSession;
53  import org.apache.maven.model.Build;
54  import org.apache.maven.model.Model;
55  import org.apache.maven.model.Organization;
56  import org.apache.maven.project.MavenProject;
57  import org.codehaus.plexus.archiver.jar.JarArchiver;
58  import org.codehaus.plexus.archiver.jar.ManifestException;
59  import org.junit.jupiter.api.Test;
60  import org.junit.jupiter.api.condition.EnabledForJreRange;
61  import org.junit.jupiter.api.condition.JRE;
62  import org.junit.jupiter.params.ParameterizedTest;
63  import org.junit.jupiter.params.provider.CsvSource;
64  import org.junit.jupiter.params.provider.EmptySource;
65  import org.junit.jupiter.params.provider.NullAndEmptySource;
66  import org.junit.jupiter.params.provider.ValueSource;
67  
68  import static org.assertj.core.api.Assertions.assertThat;
69  import static org.assertj.core.api.Assertions.assertThatCode;
70  import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
71  import static org.mockito.Mockito.mock;
72  import static org.mockito.Mockito.when;
73  
74  class MavenArchiverTest {
75      static class ArtifactComparator implements Comparator<Artifact> {
76          public int compare(Artifact o1, Artifact o2) {
77              return o1.getArtifactId().compareTo(o2.getArtifactId());
78          }
79  
80          public boolean equals(Object o) {
81              return false;
82          }
83      }
84  
85      @ParameterizedTest
86      @EmptySource
87      @ValueSource(
88              strings = {
89                  ".",
90                  "dash-is-invalid",
91                  "plus+is+invalid",
92                  "colon:is:invalid",
93                  "new.class",
94                  "123.at.start.is.invalid",
95                  "digit.at.123start.is.invalid"
96              })
97      void testInvalidModuleNames(String value) {
98          assertThat(MavenArchiver.isValidModuleName(value)).isFalse();
99      }
100 
101     @ParameterizedTest
102     @ValueSource(strings = {"a", "a.b", "a_b", "trailing0.digits123.are456.ok789", "UTF8.chars.are.okay.äëïöüẍ", "ℤ€ℕ"})
103     void testValidModuleNames(String value) {
104         assertThat(MavenArchiver.isValidModuleName(value)).isTrue();
105     }
106 
107     @Test
108     void testGetManifestExtensionList() throws Exception {
109         MavenArchiver archiver = new MavenArchiver();
110 
111         MavenSession session = getDummySession();
112 
113         Model model = new Model();
114         model.setArtifactId("dummy");
115 
116         MavenProject project = new MavenProject(model);
117         // we need to sort the artifacts for test purposes
118         Set<Artifact> artifacts = new TreeSet<>(new ArtifactComparator());
119         project.setArtifacts(artifacts);
120 
121         // there should be a mock or a setter for this field.
122         ManifestConfiguration config = new ManifestConfiguration() {
123             public boolean isAddExtensions() {
124                 return true;
125             }
126         };
127 
128         Manifest manifest = archiver.getManifest(session, project, config);
129 
130         assertThat(manifest.getMainAttributes()).isNotNull();
131 
132         assertThat(manifest.getMainAttributes().getValue("Extension-List")).isNull();
133 
134         Artifact artifact1 = mock(Artifact.class);
135         when(artifact1.getGroupId()).thenReturn("org.apache.dummy");
136         when(artifact1.getArtifactId()).thenReturn("dummy1");
137         when(artifact1.getVersion()).thenReturn("1.0");
138         when(artifact1.getType()).thenReturn("dll");
139         when(artifact1.getScope()).thenReturn("compile");
140 
141         artifacts.add(artifact1);
142 
143         manifest = archiver.getManifest(session, project, config);
144 
145         assertThat(manifest.getMainAttributes().getValue("Extension-List")).isNull();
146 
147         Artifact artifact2 = mock(Artifact.class);
148         when(artifact2.getGroupId()).thenReturn("org.apache.dummy");
149         when(artifact2.getArtifactId()).thenReturn("dummy2");
150         when(artifact2.getVersion()).thenReturn("1.0");
151         when(artifact2.getType()).thenReturn("jar");
152         when(artifact2.getScope()).thenReturn("compile");
153 
154         artifacts.add(artifact2);
155 
156         manifest = archiver.getManifest(session, project, config);
157 
158         assertThat(manifest.getMainAttributes().getValue("Extension-List")).isEqualTo("dummy2");
159 
160         Artifact artifact3 = mock(Artifact.class);
161         when(artifact3.getGroupId()).thenReturn("org.apache.dummy");
162         when(artifact3.getArtifactId()).thenReturn("dummy3");
163         when(artifact3.getVersion()).thenReturn("1.0");
164         when(artifact3.getType()).thenReturn("jar");
165         when(artifact3.getScope()).thenReturn("test");
166 
167         artifacts.add(artifact3);
168 
169         manifest = archiver.getManifest(session, project, config);
170 
171         assertThat(manifest.getMainAttributes().getValue("Extension-List")).isEqualTo("dummy2");
172 
173         Artifact artifact4 = mock(Artifact.class);
174         when(artifact4.getGroupId()).thenReturn("org.apache.dummy");
175         when(artifact4.getArtifactId()).thenReturn("dummy4");
176         when(artifact4.getVersion()).thenReturn("1.0");
177         when(artifact4.getType()).thenReturn("jar");
178         when(artifact4.getScope()).thenReturn("compile");
179 
180         artifacts.add(artifact4);
181 
182         manifest = archiver.getManifest(session, project, config);
183 
184         assertThat(manifest.getMainAttributes().getValue("Extension-List")).isEqualTo("dummy2 dummy4");
185     }
186 
187     @Test
188     void testMultiClassPath() throws Exception {
189         final File tempFile = File.createTempFile("maven-archiver-test-", ".jar");
190 
191         try {
192             MavenArchiver archiver = new MavenArchiver();
193 
194             MavenSession session = getDummySession();
195 
196             Model model = new Model();
197             model.setArtifactId("dummy");
198 
199             MavenProject project = new MavenProject(model) {
200                 public List<String> getRuntimeClasspathElements() {
201                     return Collections.singletonList(tempFile.getAbsolutePath());
202                 }
203             };
204 
205             // there should be a mock or a setter for this field.
206             ManifestConfiguration manifestConfig = new ManifestConfiguration() {
207                 public boolean isAddClasspath() {
208                     return true;
209                 }
210             };
211 
212             MavenArchiveConfiguration archiveConfiguration = new MavenArchiveConfiguration();
213             archiveConfiguration.setManifest(manifestConfig);
214             archiveConfiguration.addManifestEntry("Class-Path", "help/");
215 
216             Manifest manifest = archiver.getManifest(session, project, archiveConfiguration);
217             String classPath = manifest.getMainAttributes().getValue("Class-Path");
218             assertThat(classPath)
219                     .as("User specified Class-Path entry was not prepended to manifest")
220                     .startsWith("help/")
221                     .as("Class-Path generated by addClasspath was not appended to manifest")
222                     .endsWith(tempFile.getName());
223         } finally {
224             // noinspection ResultOfMethodCallIgnored
225             tempFile.delete();
226         }
227     }
228 
229     @Test
230     void testRecreation() throws Exception {
231         File jarFile = new File("target/test/dummy.jar");
232         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
233 
234         MavenArchiver archiver = getMavenArchiver(jarArchiver);
235 
236         MavenSession session = getDummySession();
237         MavenProject project = getDummyProject();
238 
239         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
240         config.setForced(false);
241 
242         Path directory = Paths.get("target", "maven-archiver");
243         if (Files.exists(directory)) {
244             try (Stream<Path> paths = Files.walk(directory)) {
245                 paths.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
246             }
247         }
248 
249         archiver.createArchive(session, project, config);
250         assertThat(jarFile).exists();
251 
252         long history = System.currentTimeMillis() - 60000L;
253         jarFile.setLastModified(history);
254         long time = jarFile.lastModified();
255 
256         try (Stream<Path> paths = Files.walk(directory)) {
257             FileTime fileTime = FileTime.fromMillis(time);
258             paths.forEach(path -> assertThatCode(() -> Files.setLastModifiedTime(path, fileTime))
259                     .doesNotThrowAnyException());
260         }
261 
262         archiver.createArchive(session, project, config);
263 
264         config.setForced(true);
265         archiver.createArchive(session, project, config);
266         // I'm not sure if it could only be greater than time or if it is sufficient to be greater or equal..
267         assertThat(jarFile.lastModified()).isGreaterThanOrEqualTo(time);
268     }
269 
270     @Test
271     void testNotGenerateImplementationVersionForMANIFESTMF() throws Exception {
272         File jarFile = new File("target/test/dummy.jar");
273         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
274 
275         MavenArchiver archiver = getMavenArchiver(jarArchiver);
276 
277         MavenSession session = getDummySession();
278         MavenProject project = getDummyProject();
279 
280         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
281         config.setForced(true);
282         config.getManifest().setAddDefaultImplementationEntries(false);
283         archiver.createArchive(session, project, config);
284         assertThat(jarFile).exists();
285 
286         try (JarFile jar = new JarFile(jarFile)) {
287             assertThat(jar.getManifest().getMainAttributes())
288                     .doesNotContainKey(Attributes.Name.IMPLEMENTATION_VERSION); // "Implementation-Version"
289         }
290     }
291 
292     @Test
293     void testGenerateImplementationVersionForMANIFESTMF() throws Exception {
294         File jarFile = new File("target/test/dummy.jar");
295         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
296 
297         MavenArchiver archiver = getMavenArchiver(jarArchiver);
298 
299         MavenSession session = getDummySession();
300         MavenProject project = getDummyProject();
301 
302         String ls = System.getProperty("line.separator");
303         project.setDescription("foo " + ls + " bar ");
304         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
305         config.setForced(true);
306         config.getManifest().setAddDefaultImplementationEntries(true);
307         config.addManifestEntry("Description", project.getDescription());
308         archiver.createArchive(session, project, config);
309         assertThat(jarFile).exists();
310 
311         try (JarFile jar = new JarFile(jarFile)) {
312             assertThat(jar.getManifest().getMainAttributes())
313                     .containsKey(Attributes.Name.IMPLEMENTATION_VERSION)
314                     .containsEntry(Attributes.Name.IMPLEMENTATION_VERSION, "0.1.1");
315         }
316     }
317 
318     private MavenArchiver getMavenArchiver(JarArchiver jarArchiver) {
319         MavenArchiver archiver = new MavenArchiver();
320         archiver.setArchiver(jarArchiver);
321         archiver.setOutputFile(jarArchiver.getDestFile());
322         return archiver;
323     }
324 
325     @Test
326     void testDashesInClassPath_MSHARED_134() throws Exception {
327         File jarFile = new File("target/test/dummyWithDashes.jar");
328         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
329 
330         MavenArchiver archiver = getMavenArchiver(jarArchiver);
331 
332         MavenSession session = getDummySession();
333         MavenProject project = getDummyProject();
334 
335         Set<Artifact> artifacts =
336                 getArtifacts(getMockArtifact1(), getArtifactWithDot(), getMockArtifact2(), getMockArtifact3());
337 
338         project.setArtifacts(artifacts);
339 
340         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
341         config.setForced(false);
342 
343         final ManifestConfiguration mftConfig = config.getManifest();
344         mftConfig.setMainClass("org.apache.maven.Foo");
345         mftConfig.setAddClasspath(true);
346         mftConfig.setAddExtensions(true);
347         mftConfig.setClasspathPrefix("./lib/");
348 
349         archiver.createArchive(session, project, config);
350         assertThat(jarFile).exists();
351     }
352 
353     @Test
354     void testDashesInClassPath_MSHARED_182() throws Exception {
355         File jarFile = new File("target/test/dummy.jar");
356         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
357         MavenArchiver archiver = getMavenArchiver(jarArchiver);
358 
359         MavenSession session = getDummySession();
360         MavenProject project = getDummyProject();
361 
362         Set<Artifact> artifacts =
363                 getArtifacts(getMockArtifact1(), getArtifactWithDot(), getMockArtifact2(), getMockArtifact3());
364 
365         project.setArtifacts(artifacts);
366 
367         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
368         config.setForced(false);
369 
370         final ManifestConfiguration mftConfig = config.getManifest();
371         mftConfig.setMainClass("org.apache.maven.Foo");
372         mftConfig.setAddClasspath(true);
373         mftConfig.setAddExtensions(true);
374         mftConfig.setClasspathPrefix("./lib/");
375         config.addManifestEntry("Key1", "value1");
376         config.addManifestEntry("key2", "value2");
377 
378         archiver.createArchive(session, project, config);
379         assertThat(jarFile).exists();
380         final Attributes mainAttributes = getJarFileManifest(jarFile).getMainAttributes();
381         assertThat(mainAttributes.getValue("Key1")).isEqualTo("value1");
382         assertThat(mainAttributes.getValue("Key2")).isEqualTo("value2");
383     }
384 
385     @Test
386     void testCarriageReturnInManifestEntry() throws Exception {
387         File jarFile = new File("target/test/dummy.jar");
388         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
389 
390         MavenArchiver archiver = getMavenArchiver(jarArchiver);
391 
392         MavenSession session = getDummySession();
393         MavenProject project = getDummyProject();
394 
395         String ls = System.getProperty("line.separator");
396         project.setDescription("foo " + ls + " bar ");
397         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
398         config.setForced(true);
399         config.getManifest().setAddDefaultImplementationEntries(true);
400         config.addManifestEntry("Description", project.getDescription());
401         // config.addManifestEntry( "EntryWithTab", " foo tab " + ( '\u0009' ) + ( '\u0009' ) // + " bar tab" + ( //
402         // '\u0009' // ) );
403         archiver.createArchive(session, project, config);
404         assertThat(jarFile).exists();
405 
406         final Manifest manifest = getJarFileManifest(jarFile);
407         Attributes attributes = manifest.getMainAttributes();
408         assertThat(project.getDescription().indexOf(ls)).isGreaterThan(0);
409         Attributes.Name description = new Attributes.Name("Description");
410         String value = attributes.getValue(description);
411         assertThat(value).isNotNull();
412         assertThat(value.indexOf(ls)).isLessThanOrEqualTo(0);
413     }
414 
415     @Test
416     void testDeprecatedCreateArchiveAPI() throws Exception {
417         File jarFile = new File("target/test/dummy.jar");
418         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
419 
420         MavenArchiver archiver = getMavenArchiver(jarArchiver);
421 
422         MavenProject project = getDummyProject();
423         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
424         config.setForced(true);
425         config.getManifest().setAddDefaultImplementationEntries(true);
426         config.getManifest().setAddDefaultSpecificationEntries(true);
427 
428         MavenSession session = getDummySessionWithoutMavenVersion();
429         archiver.createArchive(session, project, config);
430         assertThat(jarFile).exists();
431         Attributes manifest = getJarFileManifest(jarFile).getMainAttributes();
432 
433         // no version number
434         assertThat(manifest)
435                 .containsEntry(new Attributes.Name("Created-By"), "Maven Archiver")
436                 .containsEntry(Attributes.Name.SPECIFICATION_TITLE, "archiver test")
437                 .containsEntry(Attributes.Name.SPECIFICATION_VERSION, "0.1")
438                 .containsEntry(Attributes.Name.SPECIFICATION_VENDOR, "Apache")
439                 .containsEntry(Attributes.Name.IMPLEMENTATION_TITLE, "archiver test")
440                 .containsEntry(Attributes.Name.IMPLEMENTATION_VERSION, "0.1.1")
441                 .containsEntry(Attributes.Name.IMPLEMENTATION_VENDOR, "Apache")
442                 .containsEntry(new Attributes.Name("Build-Jdk-Spec"), System.getProperty("java.specification.version"));
443     }
444 
445     @Test
446     void testMinimalManifestEntries() throws Exception {
447         File jarFile = new File("target/test/dummy.jar");
448         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
449 
450         MavenArchiver archiver = getMavenArchiver(jarArchiver);
451 
452         MavenSession session = getDummySession();
453         MavenProject project = getDummyProject();
454         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
455         config.setForced(true);
456         config.getManifest().setAddDefaultEntries(false);
457 
458         archiver.createArchive(session, project, config);
459         assertThat(jarFile).exists();
460 
461         final Manifest jarFileManifest = getJarFileManifest(jarFile);
462         Attributes manifest = jarFileManifest.getMainAttributes();
463 
464         assertThat(manifest).hasSize(1).containsOnlyKeys(new Attributes.Name("Manifest-Version"));
465         assertThat(manifest.getValue("Manifest-Version")).isEqualTo("1.0");
466     }
467 
468     @Test
469     void testManifestEntries() throws Exception {
470         File jarFile = new File("target/test/dummy.jar");
471         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
472 
473         MavenArchiver archiver = getMavenArchiver(jarArchiver);
474 
475         MavenSession session = getDummySession();
476         MavenProject project = getDummyProject();
477         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
478         config.setForced(true);
479         config.getManifest().setAddDefaultImplementationEntries(true);
480         config.getManifest().setAddDefaultSpecificationEntries(true);
481         config.getManifest().setAddBuildEnvironmentEntries(true);
482 
483         Map<String, String> manifestEntries = new HashMap<>();
484         manifestEntries.put("foo", "bar");
485         manifestEntries.put("first-name", "olivier");
486         manifestEntries.put("Automatic-Module-Name", "org.apache.maven.archiver");
487         manifestEntries.put("keyWithEmptyValue", null);
488         config.setManifestEntries(manifestEntries);
489 
490         ManifestSection manifestSection = new ManifestSection();
491         manifestSection.setName("UserSection");
492         manifestSection.addManifestEntry("key", "value");
493         List<ManifestSection> manifestSections = new ArrayList<>();
494         manifestSections.add(manifestSection);
495         config.setManifestSections(manifestSections);
496         config.getManifest().setMainClass("org.apache.maven.Foo");
497         archiver.createArchive(session, project, config);
498         assertThat(jarFile).exists();
499 
500         final Manifest jarFileManifest = getJarFileManifest(jarFile);
501         Attributes manifest = jarFileManifest.getMainAttributes();
502 
503         // no version number
504         assertThat(manifest)
505                 .containsEntry(new Attributes.Name("Created-By"), "Maven Archiver")
506                 .containsEntry(
507                         new Attributes.Name("Build-Tool"),
508                         session.getSystemProperties().get("maven.build.version"))
509                 .containsEntry(
510                         new Attributes.Name("Build-Jdk"),
511                         String.format("%s (%s)", System.getProperty("java.version"), System.getProperty("java.vendor")))
512                 .containsEntry(
513                         new Attributes.Name("Build-Os"),
514                         String.format(
515                                 "%s (%s; %s)",
516                                 System.getProperty("os.name"),
517                                 System.getProperty("os.version"),
518                                 System.getProperty("os.arch")))
519                 .containsEntry(Attributes.Name.SPECIFICATION_TITLE, "archiver test")
520                 .containsEntry(Attributes.Name.SPECIFICATION_VERSION, "0.1")
521                 .containsEntry(Attributes.Name.SPECIFICATION_VENDOR, "Apache")
522                 .containsEntry(Attributes.Name.IMPLEMENTATION_TITLE, "archiver test")
523                 .containsEntry(Attributes.Name.IMPLEMENTATION_VERSION, "0.1.1")
524                 .containsEntry(Attributes.Name.IMPLEMENTATION_VENDOR, "Apache")
525                 .containsEntry(Attributes.Name.MAIN_CLASS, "org.apache.maven.Foo")
526                 .containsEntry(new Attributes.Name("foo"), "bar")
527                 .containsEntry(new Attributes.Name("first-name"), "olivier");
528 
529         assertThat(manifest.getValue("Automatic-Module-Name")).isEqualTo("org.apache.maven.archiver");
530 
531         assertThat(manifest)
532                 .containsEntry(new Attributes.Name("Build-Jdk-Spec"), System.getProperty("java.specification.version"));
533 
534         assertThat(manifest.getValue(new Attributes.Name("keyWithEmptyValue"))).isEmpty();
535         assertThat(manifest).containsKey(new Attributes.Name("keyWithEmptyValue"));
536 
537         manifest = jarFileManifest.getAttributes("UserSection");
538 
539         assertThat(manifest).containsEntry(new Attributes.Name("key"), "value");
540     }
541 
542     @Test
543     void testManifestWithInvalidAutomaticModuleNameThrowsOnCreateArchive() throws Exception {
544         File jarFile = new File("target/test/dummy.jar");
545         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
546 
547         MavenArchiver archiver = getMavenArchiver(jarArchiver);
548 
549         MavenSession session = getDummySession();
550         MavenProject project = getDummyProject();
551         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
552 
553         Map<String, String> manifestEntries = new HashMap<>();
554         manifestEntries.put("Automatic-Module-Name", "123.in-valid.new.name");
555         config.setManifestEntries(manifestEntries);
556 
557         try {
558             archiver.createArchive(session, project, config);
559         } catch (ManifestException e) {
560             assertThat(e.getMessage()).isEqualTo("Invalid automatic module name: '123.in-valid.new.name'");
561         }
562     }
563 
564     /*
565      * Test to make sure that manifest sections are present in the manifest prior to the archive has been created.
566      */
567     @Test
568     void testManifestSections() throws Exception {
569         MavenArchiver archiver = new MavenArchiver();
570 
571         MavenSession session = getDummySession();
572 
573         MavenProject project = getDummyProject();
574         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
575 
576         ManifestSection manifestSection = new ManifestSection();
577         manifestSection.setName("SectionOne");
578         manifestSection.addManifestEntry("key", "value");
579         List<ManifestSection> manifestSections = new ArrayList<>();
580         manifestSections.add(manifestSection);
581         config.setManifestSections(manifestSections);
582 
583         Manifest manifest = archiver.getManifest(session, project, config);
584 
585         Attributes section = manifest.getAttributes("SectionOne");
586         assertThat(section)
587                 .as("The section is not present in the manifest as it should be.")
588                 .isNotNull();
589 
590         String attribute = section.getValue("key");
591         assertThat(attribute)
592                 .as("The attribute we are looking for is not present in the section.")
593                 .isNotNull()
594                 .as("The value of the attribute is wrong.")
595                 .isEqualTo("value");
596     }
597 
598     @Test
599     void testDefaultClassPathValue() throws Exception {
600         MavenSession session = getDummySession();
601         MavenProject project = getDummyProject();
602         File jarFile = new File("target/test/dummy.jar");
603         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
604 
605         MavenArchiver archiver = getMavenArchiver(jarArchiver);
606 
607         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
608         config.setForced(true);
609         config.getManifest().setAddDefaultImplementationEntries(true);
610         config.getManifest().setAddDefaultSpecificationEntries(true);
611         config.getManifest().setMainClass("org.apache.maven.Foo");
612         config.getManifest().setAddClasspath(true);
613         config.getManifest().setClasspathLayoutType(ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM);
614         config.getManifest()
615                 .setCustomClasspathLayout(
616                         "${artifact.artifactId}-${artifact.version}${dashClassifier?}.${artifact.extension}");
617         archiver.createArchive(session, project, config);
618         assertThat(jarFile).exists();
619         final Manifest manifest = getJarFileManifest(jarFile);
620         String classPath = manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
621         assertThat(classPath).isNotNull();
622         assertThat(classPath.split(" "))
623                 .containsExactly("dummy1-1.0.jar", "dummy2-1.5.jar", "dummy3-2.0-classifier.jar");
624     }
625 
626     private void deleteAndAssertNotPresent(File jarFile) {
627         jarFile.delete();
628         assertThat(jarFile).doesNotExist();
629     }
630 
631     @Test
632     void testDefaultClassPathValue_WithSnapshot() throws Exception {
633         MavenSession session = getDummySession();
634         MavenProject project = getDummyProjectWithSnapshot();
635         File jarFile = new File("target/test/dummy.jar");
636         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
637 
638         MavenArchiver archiver = getMavenArchiver(jarArchiver);
639 
640         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
641         config.setForced(true);
642         config.getManifest().setAddDefaultImplementationEntries(true);
643         config.getManifest().setAddDefaultSpecificationEntries(true);
644         config.getManifest().setMainClass("org.apache.maven.Foo");
645         config.getManifest().setAddClasspath(true);
646         config.getManifest().setClasspathLayoutType(ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM);
647         config.getManifest()
648                 .setCustomClasspathLayout(
649                         "${artifact.artifactId}-${artifact.version}${dashClassifier?}.${artifact.extension}");
650         archiver.createArchive(session, project, config);
651         assertThat(jarFile).exists();
652 
653         final Manifest manifest = getJarFileManifest(jarFile);
654         String classPath = manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
655         assertThat(classPath).isNotNull();
656         assertThat(classPath.split(" "))
657                 .containsExactly("dummy1-1.1-20081022.112233-1.jar", "dummy2-1.5.jar", "dummy3-2.0-classifier.jar");
658     }
659 
660     @Test
661     void testMavenRepoClassPathValue() throws Exception {
662         MavenSession session = getDummySession();
663         MavenProject project = getDummyProject();
664         File jarFile = new File("target/test/dummy.jar");
665         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
666 
667         MavenArchiver archiver = getMavenArchiver(jarArchiver);
668 
669         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
670         config.setForced(true);
671         config.getManifest().setAddDefaultImplementationEntries(true);
672         config.getManifest().setAddDefaultSpecificationEntries(true);
673         config.getManifest().setMainClass("org.apache.maven.Foo");
674         config.getManifest().setAddClasspath(true);
675         config.getManifest().setUseUniqueVersions(true);
676         config.getManifest().setClasspathLayoutType(ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_REPOSITORY);
677         archiver.createArchive(session, project, config);
678         assertThat(jarFile).exists();
679         Manifest manifest = archiver.getManifest(session, project, config);
680         String[] classPathEntries =
681                 new String(manifest.getMainAttributes().getValue("Class-Path").getBytes()).split(" ");
682         assertThat(classPathEntries)
683                 .containsExactly(
684                         "org/apache/dummy/dummy1/1.0.1/dummy1-1.0.jar",
685                         "org/apache/dummy/foo/dummy2/1.5/dummy2-1.5.jar",
686                         "org/apache/dummy/bar/dummy3/2.0/dummy3-2.0-classifier.jar");
687 
688         String classPath = getJarFileManifest(jarFile).getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
689         assertThat(classPath).isNotNull();
690         assertThat(classPath.split(" "))
691                 .containsExactly(
692                         "org/apache/dummy/dummy1/1.0.1/dummy1-1.0.jar",
693                         "org/apache/dummy/foo/dummy2/1.5/dummy2-1.5.jar",
694                         "org/apache/dummy/bar/dummy3/2.0/dummy3-2.0-classifier.jar");
695     }
696 
697     @Test
698     void shouldCreateArchiveWithSimpleClassPathLayoutWhileSettingSimpleLayoutExplicit() throws Exception {
699         MavenSession session = getDummySession();
700         MavenProject project = getDummyProject();
701         File jarFile = new File("target/test/dummy-explicit-simple.jar");
702         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
703 
704         MavenArchiver archiver = getMavenArchiver(jarArchiver);
705 
706         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
707         config.setForced(true);
708         config.getManifest().setAddDefaultImplementationEntries(true);
709         config.getManifest().setAddDefaultSpecificationEntries(true);
710         config.getManifest().setMainClass("org.apache.maven.Foo");
711         config.getManifest().setAddClasspath(true);
712         config.getManifest().setClasspathPrefix("lib");
713         config.getManifest().setClasspathLayoutType(ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_SIMPLE);
714 
715         archiver.createArchive(session, project, config);
716         assertThat(jarFile).exists();
717         Manifest manifest = archiver.getManifest(session, project, config);
718         String[] classPathEntries =
719                 new String(manifest.getMainAttributes().getValue("Class-Path").getBytes()).split(" ");
720         assertThat(classPathEntries)
721                 .containsExactly("lib/dummy1-1.0.jar", "lib/dummy2-1.5.jar", "lib/dummy3-2.0-classifier.jar");
722 
723         String classPath = getJarFileManifest(jarFile).getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
724 
725         assertThat(classPath).isNotNull();
726         assertThat(classPath.split(" "))
727                 .containsExactly("lib/dummy1-1.0.jar", "lib/dummy2-1.5.jar", "lib/dummy3-2.0-classifier.jar");
728     }
729 
730     @Test
731     void shouldCreateArchiveCustomerLayoutSimple() throws Exception {
732         MavenSession session = getDummySession();
733         MavenProject project = getDummyProject();
734         File jarFile = new File("target/test/dummy-custom-layout-simple.jar");
735         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
736 
737         MavenArchiver archiver = getMavenArchiver(jarArchiver);
738 
739         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
740         config.setForced(true);
741         config.getManifest().setAddDefaultImplementationEntries(true);
742         config.getManifest().setAddDefaultSpecificationEntries(true);
743         config.getManifest().setMainClass("org.apache.maven.Foo");
744         config.getManifest().setAddClasspath(true);
745         config.getManifest().setClasspathPrefix("lib");
746         config.getManifest().setClasspathLayoutType(ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM);
747         config.getManifest().setCustomClasspathLayout(MavenArchiver.SIMPLE_LAYOUT);
748 
749         archiver.createArchive(session, project, config);
750         assertThat(jarFile).exists();
751         Manifest manifest = archiver.getManifest(session, project, config);
752         String[] classPathEntries =
753                 new String(manifest.getMainAttributes().getValue("Class-Path").getBytes()).split(" ");
754         assertThat(classPathEntries)
755                 .containsExactly("lib/dummy1-1.0.jar", "lib/dummy2-1.5.jar", "lib/dummy3-2.0-classifier.jar");
756 
757         String classPath = getJarFileManifest(jarFile).getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
758 
759         assertThat(classPath).isNotNull();
760         assertThat(classPath.split(" "))
761                 .containsExactly("lib/dummy1-1.0.jar", "lib/dummy2-1.5.jar", "lib/dummy3-2.0-classifier.jar");
762     }
763 
764     @Test
765     void shouldCreateArchiveCustomLayoutSimpleNonUnique() throws Exception {
766         MavenSession session = getDummySession();
767         MavenProject project = getDummyProject();
768         File jarFile = new File("target/test/dummy-custom-layout-simple-non-unique.jar");
769         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
770 
771         MavenArchiver archiver = getMavenArchiver(jarArchiver);
772 
773         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
774         config.setForced(true);
775         config.getManifest().setAddDefaultImplementationEntries(true);
776         config.getManifest().setAddDefaultSpecificationEntries(true);
777         config.getManifest().setMainClass("org.apache.maven.Foo");
778         config.getManifest().setAddClasspath(true);
779         config.getManifest().setClasspathPrefix("lib");
780         config.getManifest().setClasspathLayoutType(ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM);
781         config.getManifest().setCustomClasspathLayout(MavenArchiver.SIMPLE_LAYOUT_NONUNIQUE);
782 
783         archiver.createArchive(session, project, config);
784         assertThat(jarFile).exists();
785         Manifest manifest = archiver.getManifest(session, project, config);
786         String[] classPathEntries =
787                 new String(manifest.getMainAttributes().getValue("Class-Path").getBytes()).split(" ");
788         assertThat(classPathEntries)
789                 .containsExactly("lib/dummy1-1.0.1.jar", "lib/dummy2-1.5.jar", "lib/dummy3-2.0-classifier.jar");
790 
791         String classPath = getJarFileManifest(jarFile).getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
792 
793         assertThat(classPath).isNotNull();
794         assertThat(classPath.split(" "))
795                 .containsExactly("lib/dummy1-1.0.1.jar", "lib/dummy2-1.5.jar", "lib/dummy3-2.0-classifier.jar");
796     }
797 
798     @Test
799     void shouldCreateArchiveCustomLayoutRepository() throws Exception {
800         MavenSession session = getDummySession();
801         MavenProject project = getDummyProject();
802         File jarFile = new File("target/test/dummy-custom-layout-repo.jar");
803         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
804 
805         MavenArchiver archiver = getMavenArchiver(jarArchiver);
806 
807         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
808         config.setForced(true);
809         config.getManifest().setAddDefaultImplementationEntries(true);
810         config.getManifest().setAddDefaultSpecificationEntries(true);
811         config.getManifest().setMainClass("org.apache.maven.Foo");
812         config.getManifest().setAddClasspath(true);
813         config.getManifest().setClasspathPrefix("lib");
814         config.getManifest().setClasspathLayoutType(ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM);
815         config.getManifest().setCustomClasspathLayout(MavenArchiver.REPOSITORY_LAYOUT);
816 
817         archiver.createArchive(session, project, config);
818         assertThat(jarFile).exists();
819         Manifest manifest = archiver.getManifest(session, project, config);
820         String[] classPathEntries =
821                 new String(manifest.getMainAttributes().getValue("Class-Path").getBytes()).split(" ");
822         assertThat(classPathEntries)
823                 .containsExactly(
824                         "lib/org/apache/dummy/dummy1/1.0.1/dummy1-1.0.jar",
825                         "lib/org/apache/dummy/foo/dummy2/1.5/dummy2-1.5.jar",
826                         "lib/org/apache/dummy/bar/dummy3/2.0/dummy3-2.0-classifier.jar");
827 
828         String classPath = getJarFileManifest(jarFile).getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
829 
830         assertThat(classPath).isNotNull();
831         assertThat(classPath.split(" "))
832                 .containsExactly(
833                         "lib/org/apache/dummy/dummy1/1.0.1/dummy1-1.0.jar",
834                         "lib/org/apache/dummy/foo/dummy2/1.5/dummy2-1.5.jar",
835                         "lib/org/apache/dummy/bar/dummy3/2.0/dummy3-2.0-classifier.jar");
836     }
837 
838     @Test
839     void shouldCreateArchiveCustomLayoutRepositoryNonUnique() throws Exception {
840         MavenSession session = getDummySession();
841         MavenProject project = getDummyProject();
842         File jarFile = new File("target/test/dummy-custom-layout-repo-non-unique.jar");
843         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
844 
845         MavenArchiver archiver = getMavenArchiver(jarArchiver);
846 
847         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
848         config.setForced(true);
849         config.getManifest().setAddDefaultImplementationEntries(true);
850         config.getManifest().setAddDefaultSpecificationEntries(true);
851         config.getManifest().setMainClass("org.apache.maven.Foo");
852         config.getManifest().setAddClasspath(true);
853         config.getManifest().setClasspathPrefix("lib");
854         config.getManifest().setClasspathLayoutType(ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM);
855         config.getManifest().setCustomClasspathLayout(MavenArchiver.REPOSITORY_LAYOUT_NONUNIQUE);
856 
857         archiver.createArchive(session, project, config);
858         assertThat(jarFile).exists();
859         Manifest manifest = archiver.getManifest(session, project, config);
860         String[] classPathEntries =
861                 new String(manifest.getMainAttributes().getValue("Class-Path").getBytes()).split(" ");
862         assertThat(classPathEntries)
863                 .containsExactly(
864                         "lib/org/apache/dummy/dummy1/1.0.1/dummy1-1.0.1.jar",
865                         "lib/org/apache/dummy/foo/dummy2/1.5/dummy2-1.5.jar",
866                         "lib/org/apache/dummy/bar/dummy3/2.0/dummy3-2.0-classifier.jar");
867 
868         String classPath = getJarFileManifest(jarFile).getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
869 
870         assertThat(classPath).isNotNull();
871         assertThat(classPath.split(" "))
872                 .containsExactly(
873                         "lib/org/apache/dummy/dummy1/1.0.1/dummy1-1.0.1.jar",
874                         "lib/org/apache/dummy/foo/dummy2/1.5/dummy2-1.5.jar",
875                         "lib/org/apache/dummy/bar/dummy3/2.0/dummy3-2.0-classifier.jar");
876     }
877 
878     @Test
879     void shouldCreateArchiveWithSimpleClassPathLayoutUsingDefaults() throws Exception {
880         MavenSession session = getDummySession();
881         MavenProject project = getDummyProject();
882         File jarFile = new File("target/test/dummy-defaults.jar");
883         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
884 
885         MavenArchiver archiver = getMavenArchiver(jarArchiver);
886 
887         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
888         config.setForced(true);
889         config.getManifest().setAddDefaultImplementationEntries(true);
890         config.getManifest().setAddDefaultSpecificationEntries(true);
891         config.getManifest().setMainClass("org.apache.maven.Foo");
892         config.getManifest().setAddClasspath(true);
893         config.getManifest().setClasspathPrefix("lib");
894 
895         archiver.createArchive(session, project, config);
896         assertThat(jarFile).exists();
897         Manifest manifest = archiver.getManifest(session, project, config);
898         String[] classPathEntries =
899                 new String(manifest.getMainAttributes().getValue("Class-Path").getBytes()).split(" ");
900         assertThat(classPathEntries)
901                 .containsExactly("lib/dummy1-1.0.jar", "lib/dummy2-1.5.jar", "lib/dummy3-2.0-classifier.jar");
902 
903         String classPath = getJarFileManifest(jarFile).getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
904         assertThat(classPath).isNotNull();
905         assertThat(classPath.split(" "))
906                 .containsExactly("lib/dummy1-1.0.jar", "lib/dummy2-1.5.jar", "lib/dummy3-2.0-classifier.jar");
907     }
908 
909     @Test
910     void testMavenRepoClassPathValue_WithSnapshot() throws Exception {
911         MavenSession session = getDummySession();
912         MavenProject project = getDummyProjectWithSnapshot();
913         File jarFile = new File("target/test/dummy.jar");
914         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
915 
916         MavenArchiver archiver = getMavenArchiver(jarArchiver);
917 
918         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
919         config.setForced(true);
920         config.getManifest().setAddDefaultImplementationEntries(true);
921         config.getManifest().setAddDefaultSpecificationEntries(true);
922         config.getManifest().setMainClass("org.apache.maven.Foo");
923         config.getManifest().setAddClasspath(true);
924         config.getManifest().setClasspathLayoutType(ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_REPOSITORY);
925         archiver.createArchive(session, project, config);
926         assertThat(jarFile).exists();
927 
928         Manifest manifest = archiver.getManifest(session, project, config);
929         String[] classPathEntries =
930                 new String(manifest.getMainAttributes().getValue("Class-Path").getBytes()).split(" ");
931         assertThat(classPathEntries)
932                 .containsExactly(
933                         "org/apache/dummy/dummy1/1.1-SNAPSHOT/dummy1-1.1-20081022.112233-1.jar",
934                         "org/apache/dummy/foo/dummy2/1.5/dummy2-1.5.jar",
935                         "org/apache/dummy/bar/dummy3/2.0/dummy3-2.0-classifier.jar");
936 
937         String classPath = getJarFileManifest(jarFile).getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
938         assertThat(classPath).isNotNull();
939         assertThat(classPath.split(" "))
940                 .containsExactly(
941                         "org/apache/dummy/dummy1/1.1-SNAPSHOT/dummy1-1.1-20081022.112233-1.jar",
942                         "org/apache/dummy/foo/dummy2/1.5/dummy2-1.5.jar",
943                         "org/apache/dummy/bar/dummy3/2.0/dummy3-2.0-classifier.jar");
944     }
945 
946     @Test
947     void testCustomClassPathValue() throws Exception {
948         MavenSession session = getDummySession();
949         MavenProject project = getDummyProject();
950         File jarFile = new File("target/test/dummy.jar");
951         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
952 
953         MavenArchiver archiver = getMavenArchiver(jarArchiver);
954 
955         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
956         config.setForced(true);
957         config.getManifest().setAddDefaultImplementationEntries(true);
958         config.getManifest().setAddDefaultSpecificationEntries(true);
959         config.getManifest().setMainClass("org.apache.maven.Foo");
960         config.getManifest().setAddClasspath(true);
961         config.getManifest().setClasspathLayoutType(ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM);
962         config.getManifest()
963                 .setCustomClasspathLayout(
964                         "${artifact.groupIdPath}/${artifact.artifactId}/${artifact.version}/TEST-${artifact.artifactId}-${artifact.version}${dashClassifier?}.${artifact.extension}");
965         archiver.createArchive(session, project, config);
966         assertThat(jarFile).exists();
967         Manifest manifest = archiver.getManifest(session, project, config);
968         String[] classPathEntries =
969                 new String(manifest.getMainAttributes().getValue("Class-Path").getBytes()).split(" ");
970         assertThat(classPathEntries)
971                 .containsExactly(
972                         "org/apache/dummy/dummy1/1.0/TEST-dummy1-1.0.jar",
973                         "org/apache/dummy/foo/dummy2/1.5/TEST-dummy2-1.5.jar",
974                         "org/apache/dummy/bar/dummy3/2.0/TEST-dummy3-2.0-classifier.jar");
975 
976         final Manifest manifest1 = getJarFileManifest(jarFile);
977         String classPath = manifest1.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
978         assertThat(classPath).isNotNull();
979         assertThat(classPath.split(" "))
980                 .containsExactly(
981                         "org/apache/dummy/dummy1/1.0/TEST-dummy1-1.0.jar",
982                         "org/apache/dummy/foo/dummy2/1.5/TEST-dummy2-1.5.jar",
983                         "org/apache/dummy/bar/dummy3/2.0/TEST-dummy3-2.0-classifier.jar");
984     }
985 
986     @Test
987     void testCustomClassPathValue_WithSnapshotResolvedVersion() throws Exception {
988         MavenSession session = getDummySession();
989         MavenProject project = getDummyProjectWithSnapshot();
990         File jarFile = new File("target/test/dummy.jar");
991         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
992         MavenArchiver archiver = getMavenArchiver(jarArchiver);
993 
994         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
995         config.setForced(true);
996         config.getManifest().setAddDefaultImplementationEntries(true);
997         config.getManifest().setAddDefaultSpecificationEntries(true);
998         config.getManifest().setMainClass("org.apache.maven.Foo");
999         config.getManifest().setAddClasspath(true);
1000         config.getManifest().setClasspathLayoutType(ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM);
1001         config.getManifest()
1002                 .setCustomClasspathLayout(
1003                         "${artifact.groupIdPath}/${artifact.artifactId}/${artifact.baseVersion}/TEST-${artifact.artifactId}-${artifact.version}${dashClassifier?}.${artifact.extension}");
1004         archiver.createArchive(session, project, config);
1005         assertThat(jarFile).exists();
1006 
1007         Manifest manifest = archiver.getManifest(session, project, config);
1008         String[] classPathEntries =
1009                 new String(manifest.getMainAttributes().getValue("Class-Path").getBytes()).split(" ");
1010         assertThat(classPathEntries)
1011                 .containsExactly(
1012                         "org/apache/dummy/dummy1/1.1-SNAPSHOT/TEST-dummy1-1.1-20081022.112233-1.jar",
1013                         "org/apache/dummy/foo/dummy2/1.5/TEST-dummy2-1.5.jar",
1014                         "org/apache/dummy/bar/dummy3/2.0/TEST-dummy3-2.0-classifier.jar");
1015 
1016         String classPath = getJarFileManifest(jarFile).getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
1017         assertThat(classPath).isNotNull();
1018         assertThat(classPath.split(" "))
1019                 .containsExactly(
1020                         "org/apache/dummy/dummy1/1.1-SNAPSHOT/TEST-dummy1-1.1-20081022.112233-1.jar",
1021                         "org/apache/dummy/foo/dummy2/1.5/TEST-dummy2-1.5.jar",
1022                         "org/apache/dummy/bar/dummy3/2.0/TEST-dummy3-2.0-classifier.jar");
1023     }
1024 
1025     @Test
1026     void testCustomClassPathValue_WithSnapshotForcingBaseVersion() throws Exception {
1027         MavenSession session = getDummySession();
1028         MavenProject project = getDummyProjectWithSnapshot();
1029         File jarFile = new File("target/test/dummy.jar");
1030         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
1031 
1032         MavenArchiver archiver = getMavenArchiver(jarArchiver);
1033 
1034         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
1035         config.setForced(true);
1036         config.getManifest().setAddDefaultImplementationEntries(true);
1037         config.getManifest().setAddDefaultSpecificationEntries(true);
1038         config.getManifest().setMainClass("org.apache.maven.Foo");
1039         config.getManifest().setAddClasspath(true);
1040         config.getManifest().setClasspathLayoutType(ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM);
1041         config.getManifest()
1042                 .setCustomClasspathLayout(
1043                         "${artifact.groupIdPath}/${artifact.artifactId}/${artifact.baseVersion}/TEST-${artifact.artifactId}-${artifact.baseVersion}${dashClassifier?}.${artifact.extension}");
1044         archiver.createArchive(session, project, config);
1045         assertThat(jarFile).exists();
1046         Manifest manifest = archiver.getManifest(session, project, config);
1047         String[] classPathEntries =
1048                 new String(manifest.getMainAttributes().getValue("Class-Path").getBytes()).split(" ");
1049         assertThat(classPathEntries[0]).isEqualTo("org/apache/dummy/dummy1/1.1-SNAPSHOT/TEST-dummy1-1.1-SNAPSHOT.jar");
1050         assertThat(classPathEntries[1]).isEqualTo("org/apache/dummy/foo/dummy2/1.5/TEST-dummy2-1.5.jar");
1051         assertThat(classPathEntries[2]).isEqualTo("org/apache/dummy/bar/dummy3/2.0/TEST-dummy3-2.0-classifier.jar");
1052 
1053         String classPath = getJarFileManifest(jarFile).getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
1054         assertThat(classPath).isNotNull();
1055         assertThat(classPath.split(" "))
1056                 .containsExactly(
1057                         "org/apache/dummy/dummy1/1.1-SNAPSHOT/TEST-dummy1-1.1-SNAPSHOT.jar",
1058                         "org/apache/dummy/foo/dummy2/1.5/TEST-dummy2-1.5.jar",
1059                         "org/apache/dummy/bar/dummy3/2.0/TEST-dummy3-2.0-classifier.jar");
1060     }
1061 
1062     @Test
1063     void testDefaultPomProperties() throws Exception {
1064         MavenSession session = getDummySession();
1065         MavenProject project = getDummyProject();
1066         File jarFile = new File("target/test/dummy.jar");
1067         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
1068 
1069         MavenArchiver archiver = getMavenArchiver(jarArchiver);
1070 
1071         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
1072         config.setForced(true);
1073         archiver.createArchive(session, project, config);
1074         assertThat(jarFile).exists();
1075 
1076         final String groupId = project.getGroupId();
1077         final String artifactId = project.getArtifactId();
1078         final String version = project.getVersion();
1079 
1080         JarFile virtJarFile = new JarFile(jarFile);
1081         ZipEntry pomPropertiesEntry =
1082                 virtJarFile.getEntry("META-INF/maven/" + groupId + "/" + artifactId + "/pom.properties");
1083         assertThat(pomPropertiesEntry).isNotNull();
1084 
1085         try (InputStream is = virtJarFile.getInputStream(pomPropertiesEntry)) {
1086             Properties p = new Properties();
1087             p.load(is);
1088 
1089             assertThat(p.getProperty("groupId")).isEqualTo(groupId);
1090             assertThat(p.getProperty("artifactId")).isEqualTo(artifactId);
1091             assertThat(p.getProperty("version")).isEqualTo(version);
1092         }
1093         virtJarFile.close();
1094     }
1095 
1096     @Test
1097     void testCustomPomProperties() throws Exception {
1098         MavenSession session = getDummySession();
1099         MavenProject project = getDummyProject();
1100         File jarFile = new File("target/test/dummy.jar");
1101         JarArchiver jarArchiver = getCleanJarArchiver(jarFile);
1102 
1103         MavenArchiver archiver = getMavenArchiver(jarArchiver);
1104 
1105         File customPomPropertiesFile = new File("src/test/resources/custom-pom.properties");
1106         MavenArchiveConfiguration config = new MavenArchiveConfiguration();
1107         config.setForced(true);
1108         config.setPomPropertiesFile(customPomPropertiesFile);
1109         archiver.createArchive(session, project, config);
1110         assertThat(jarFile).exists();
1111 
1112         final String groupId = project.getGroupId();
1113         final String artifactId = project.getArtifactId();
1114         final String version = project.getVersion();
1115 
1116         try (JarFile virtJarFile = new JarFile(jarFile)) {
1117             ZipEntry pomPropertiesEntry =
1118                     virtJarFile.getEntry("META-INF/maven/" + groupId + "/" + artifactId + "/pom.properties");
1119             assertThat(pomPropertiesEntry).isNotNull();
1120 
1121             try (InputStream is = virtJarFile.getInputStream(pomPropertiesEntry)) {
1122                 Properties p = new Properties();
1123                 p.load(is);
1124 
1125                 assertThat(p.getProperty("groupId")).isEqualTo(groupId);
1126                 assertThat(p.getProperty("artifactId")).isEqualTo(artifactId);
1127                 assertThat(p.getProperty("version")).isEqualTo(version);
1128                 assertThat(p.getProperty("build.revision")).isEqualTo("1337");
1129                 assertThat(p.getProperty("build.branch")).isEqualTo("tags/0.1.1");
1130             }
1131         }
1132     }
1133 
1134     private JarArchiver getCleanJarArchiver(File jarFile) {
1135         deleteAndAssertNotPresent(jarFile);
1136         JarArchiver jarArchiver = new JarArchiver();
1137         jarArchiver.setDestFile(jarFile);
1138         return jarArchiver;
1139     }
1140 
1141     // ----------------------------------------
1142     // common methods for testing
1143     // ----------------------------------------
1144 
1145     private MavenProject getDummyProject() throws Exception {
1146         MavenProject project = getMavenProject();
1147 
1148         Artifact artifact = mock(Artifact.class);
1149         when(artifact.getGroupId()).thenReturn("org.apache.dummy");
1150         when(artifact.getArtifactId()).thenReturn("dummy");
1151         when(artifact.getVersion()).thenReturn("0.1.1");
1152         when(artifact.getBaseVersion()).thenReturn("0.1.2");
1153         when(artifact.getSelectedVersion()).thenReturn(new DefaultArtifactVersion("0.1.1"));
1154         when(artifact.getType()).thenReturn("jar");
1155         when(artifact.getArtifactHandler()).thenReturn(new DefaultArtifactHandler("jar"));
1156         project.setArtifact(artifact);
1157 
1158         Set<Artifact> artifacts = getArtifacts(getMockArtifact1Release(), getMockArtifact2(), getMockArtifact3());
1159         project.setArtifacts(artifacts);
1160 
1161         return project;
1162     }
1163 
1164     private MavenProject getMavenProject() {
1165         Model model = new Model();
1166         model.setGroupId("org.apache.dummy");
1167         model.setArtifactId("dummy");
1168         model.setVersion("0.1.1");
1169 
1170         final MavenProject project = new MavenProject(model);
1171         project.setExtensionArtifacts(Collections.emptySet());
1172         project.setRemoteArtifactRepositories(Collections.emptyList());
1173         project.setPluginArtifactRepositories(Collections.emptyList());
1174         project.setName("archiver test");
1175 
1176         File pomFile = new File("src/test/resources/pom.xml");
1177         project.setFile(pomFile);
1178 
1179         Build build = new Build();
1180         build.setDirectory("target");
1181         build.setOutputDirectory("target");
1182         project.setBuild(build);
1183 
1184         Organization organization = new Organization();
1185         organization.setName("Apache");
1186         project.setOrganization(organization);
1187         return project;
1188     }
1189 
1190     private Artifact getMockArtifact3() {
1191         Artifact artifact = mock(Artifact.class);
1192         when(artifact.getGroupId()).thenReturn("org.apache.dummy.bar");
1193         when(artifact.getArtifactId()).thenReturn("dummy3");
1194         when(artifact.getVersion()).thenReturn("2.0");
1195         when(artifact.getType()).thenReturn("jar");
1196         when(artifact.getScope()).thenReturn("runtime");
1197         when(artifact.getClassifier()).thenReturn("classifier");
1198         File file = getClasspathFile(artifact.getArtifactId() + "-" + artifact.getVersion() + ".jar");
1199         when(artifact.getFile()).thenReturn(file);
1200         ArtifactHandler artifactHandler = mock(ArtifactHandler.class);
1201         when(artifactHandler.isAddedToClasspath()).thenReturn(true);
1202         when(artifactHandler.getExtension()).thenReturn("jar");
1203         when(artifact.getArtifactHandler()).thenReturn(artifactHandler);
1204         return artifact;
1205     }
1206 
1207     private MavenProject getDummyProjectWithSnapshot() throws Exception {
1208         MavenProject project = getMavenProject();
1209 
1210         Artifact artifact = mock(Artifact.class);
1211         when(artifact.getGroupId()).thenReturn("org.apache.dummy");
1212         when(artifact.getArtifactId()).thenReturn("dummy");
1213         when(artifact.getVersion()).thenReturn("0.1.1");
1214         when(artifact.getBaseVersion()).thenReturn("0.1.1");
1215         when(artifact.getSelectedVersion()).thenReturn(new DefaultArtifactVersion("0.1.1"));
1216         when(artifact.getType()).thenReturn("jar");
1217         when(artifact.getScope()).thenReturn("compile");
1218         when(artifact.getArtifactHandler()).thenReturn(new DefaultArtifactHandler("jar"));
1219         project.setArtifact(artifact);
1220 
1221         Set<Artifact> artifacts = getArtifacts(getMockArtifact1(), getMockArtifact2(), getMockArtifact3());
1222         project.setArtifacts(artifacts);
1223 
1224         return project;
1225     }
1226 
1227     private Artifact getMockArtifact2() {
1228         Artifact artifact = mock(Artifact.class);
1229         when(artifact.getGroupId()).thenReturn("org.apache.dummy.foo");
1230         when(artifact.getArtifactId()).thenReturn("dummy2");
1231         when(artifact.getVersion()).thenReturn("1.5");
1232         when(artifact.getType()).thenReturn("jar");
1233         when(artifact.getScope()).thenReturn("runtime");
1234         File file = getClasspathFile(artifact.getArtifactId() + "-" + artifact.getVersion() + ".jar");
1235         when(artifact.getFile()).thenReturn(file);
1236         ArtifactHandler artifactHandler = mock(ArtifactHandler.class);
1237         when(artifactHandler.isAddedToClasspath()).thenReturn(true);
1238         when(artifactHandler.getExtension()).thenReturn("jar");
1239         when(artifact.getArtifactHandler()).thenReturn(artifactHandler);
1240         return artifact;
1241     }
1242 
1243     private Artifact getArtifactWithDot() {
1244         Artifact artifact = mock(Artifact.class);
1245         when(artifact.getGroupId()).thenReturn("org.apache.dummy.foo");
1246         when(artifact.getArtifactId()).thenReturn("dummy.dot");
1247         when(artifact.getVersion()).thenReturn("1.5");
1248         when(artifact.getScope()).thenReturn("runtime");
1249         when(artifact.getArtifactHandler()).thenReturn(new DefaultArtifactHandler("jar"));
1250         return artifact;
1251     }
1252 
1253     private Artifact getMockArtifact1() {
1254         Artifact artifact = mock(Artifact.class);
1255         when(artifact.getGroupId()).thenReturn("org.apache.dummy");
1256         when(artifact.getArtifactId()).thenReturn("dummy1");
1257         when(artifact.getVersion()).thenReturn("1.1-20081022.112233-1");
1258         when(artifact.getBaseVersion()).thenReturn("1.1-SNAPSHOT");
1259         when(artifact.getType()).thenReturn("jar");
1260         when(artifact.getScope()).thenReturn("runtime");
1261         File file = getClasspathFile(artifact.getArtifactId() + "-" + artifact.getVersion() + ".jar");
1262         when(artifact.getFile()).thenReturn(file);
1263         ArtifactHandler artifactHandler = mock(ArtifactHandler.class);
1264         when(artifactHandler.isAddedToClasspath()).thenReturn(true);
1265         when(artifactHandler.getExtension()).thenReturn("jar");
1266         when(artifact.getArtifactHandler()).thenReturn(artifactHandler);
1267         return artifact;
1268     }
1269 
1270     private Artifact getMockArtifact1Release() {
1271         Artifact artifact = mock(Artifact.class);
1272         when(artifact.getGroupId()).thenReturn("org.apache.dummy");
1273         when(artifact.getArtifactId()).thenReturn("dummy1");
1274         when(artifact.getVersion()).thenReturn("1.0");
1275         when(artifact.getBaseVersion()).thenReturn("1.0.1");
1276         when(artifact.getType()).thenReturn("jar");
1277         when(artifact.getScope()).thenReturn("runtime");
1278         File file = getClasspathFile(artifact.getArtifactId() + "-" + artifact.getVersion() + ".jar");
1279         when(artifact.getFile()).thenReturn(file);
1280         ArtifactHandler artifactHandler = mock(ArtifactHandler.class);
1281         when(artifactHandler.isAddedToClasspath()).thenReturn(true);
1282         when(artifactHandler.getExtension()).thenReturn("jar");
1283         when(artifact.getArtifactHandler()).thenReturn(artifactHandler);
1284         return artifact;
1285     }
1286 
1287     private File getClasspathFile(String file) {
1288         URL resource = Thread.currentThread().getContextClassLoader().getResource(file);
1289         if (resource == null) {
1290             throw new IllegalStateException(
1291                     "Cannot retrieve java.net.URL for file: " + file + " on the current test classpath.");
1292         }
1293 
1294         URI uri = new File(resource.getPath()).toURI().normalize();
1295 
1296         return new File(uri.getPath().replaceAll("%20", " "));
1297     }
1298 
1299     private MavenSession getDummySession() {
1300         Properties systemProperties = new Properties();
1301         systemProperties.put("maven.version", "3.1.1");
1302         systemProperties.put(
1303                 "maven.build.version",
1304                 "Apache Maven 3.1.1 (0728685237757ffbf44136acec0402957f723d9a; 2013-09-17 17:22:22+0200)");
1305 
1306         return getDummySession(systemProperties);
1307     }
1308 
1309     private MavenSession getDummySessionWithoutMavenVersion() {
1310         return getDummySession(new Properties());
1311     }
1312 
1313     private MavenSession getDummySession(Properties systemProperties) {
1314         MavenSession session = mock(MavenSession.class);
1315         when(session.getSystemProperties()).thenReturn(systemProperties);
1316         return session;
1317     }
1318 
1319     private Set<Artifact> getArtifacts(Artifact... artifacts) {
1320         Set<Artifact> result = new TreeSet<>(new ArtifactComparator());
1321         result.addAll(Arrays.asList(artifacts));
1322         return result;
1323     }
1324 
1325     public Manifest getJarFileManifest(File jarFile) throws IOException {
1326         try (JarFile jar = new JarFile(jarFile)) {
1327             return jar.getManifest();
1328         }
1329     }
1330 
1331     @Test
1332     void testParseOutputTimestamp() {
1333         MavenArchiver archiver = new MavenArchiver();
1334 
1335         assertThat(archiver.parseOutputTimestamp(null)).isNull();
1336         assertThat(archiver.parseOutputTimestamp("")).isNull();
1337         assertThat(archiver.parseOutputTimestamp(".")).isNull();
1338         assertThat(archiver.parseOutputTimestamp(" ")).isNull();
1339         assertThat(archiver.parseOutputTimestamp("_")).isNull();
1340         assertThat(archiver.parseOutputTimestamp("-")).isNull();
1341         assertThat(archiver.parseOutputTimestamp("/")).isNull();
1342         assertThat(archiver.parseOutputTimestamp("!")).isNull();
1343         assertThat(archiver.parseOutputTimestamp("*")).isNull();
1344 
1345         assertThat(archiver.parseOutputTimestamp("1570300662").getTime()).isEqualTo(1570300662000L);
1346         assertThat(archiver.parseOutputTimestamp("0").getTime()).isZero();
1347         assertThat(archiver.parseOutputTimestamp("1").getTime()).isEqualTo(1000L);
1348 
1349         assertThat(archiver.parseOutputTimestamp("2019-10-05T18:37:42Z").getTime())
1350                 .isEqualTo(1570300662000L);
1351         assertThat(archiver.parseOutputTimestamp("2019-10-05T20:37:42+02:00").getTime())
1352                 .isEqualTo(1570300662000L);
1353         assertThat(archiver.parseOutputTimestamp("2019-10-05T16:37:42-02:00").getTime())
1354                 .isEqualTo(1570300662000L);
1355 
1356         // These must result in IAE because we expect extended ISO format only (ie with - separator for date and
1357         // : separator for timezone), hence the XXX SimpleDateFormat for tz offset
1358         // X SimpleDateFormat accepts timezone without separator while date has separator, which is a mix between
1359         // basic (no separators, both for date and timezone) and extended (separator for both)
1360         assertThatExceptionOfType(IllegalArgumentException.class)
1361                 .isThrownBy(() -> archiver.parseOutputTimestamp("2019-10-05T20:37:42+0200"));
1362         assertThatExceptionOfType(IllegalArgumentException.class)
1363                 .isThrownBy(() -> archiver.parseOutputTimestamp("2019-10-05T20:37:42-0200"));
1364     }
1365 
1366     @ParameterizedTest
1367     @NullAndEmptySource
1368     @ValueSource(strings = {".", " ", "_", "-", "T", "/", "!", "!", "*", "ñ"})
1369     void testEmptyParseOutputTimestampInstant(String value) {
1370         // Empty optional if null or 1 char
1371         assertThat(MavenArchiver.parseBuildOutputTimestamp(value)).isEmpty();
1372     }
1373 
1374     @ParameterizedTest
1375     @CsvSource({
1376         "0,0",
1377         "1,1",
1378         "9,9",
1379         "1570300662,1570300662",
1380         "2147483648,2147483648",
1381         "2019-10-05T18:37:42Z,1570300662",
1382         "2019-10-05T20:37:42+02:00,1570300662",
1383         "2019-10-05T16:37:42-02:00,1570300662",
1384         "1988-02-22T15:23:47.76598Z,572541827",
1385         "2011-12-03T10:15:30+01:00,1322903730",
1386         "1980-01-01T00:00:02Z,315532802",
1387         "2099-12-31T23:59:59Z,4102444799"
1388     })
1389     void testParseOutputTimestampInstant(String value, long expected) {
1390         assertThat(MavenArchiver.parseBuildOutputTimestamp(value)).contains(Instant.ofEpochSecond(expected));
1391     }
1392 
1393     @ParameterizedTest
1394     @ValueSource(
1395             strings = {
1396                 "2019-10-05T20:37:42+0200",
1397                 "2019-10-05T20:37:42-0200",
1398                 "2019-10-05T25:00:00Z",
1399                 "2019-10-05",
1400                 "XYZ",
1401                 "Tue, 3 Jun 2008 11:05:30 GMT",
1402                 "2011-12-03T10:15:30+01:00[Europe/Paris]"
1403             })
1404     void testThrownParseOutputTimestampInstant(String outputTimestamp) {
1405         // Invalid parsing
1406         assertThatExceptionOfType(IllegalArgumentException.class)
1407                 .isThrownBy(() -> MavenArchiver.parseBuildOutputTimestamp(outputTimestamp))
1408                 .withCauseInstanceOf(DateTimeParseException.class);
1409     }
1410 
1411     @ParameterizedTest
1412     @ValueSource(
1413             strings = {
1414                 "1980-01-01T00:00:01Z",
1415                 "2100-01-01T00:00Z",
1416                 "2100-02-28T23:59:59Z",
1417                 "2099-12-31T23:59:59-01:00",
1418                 "1980-01-01T00:15:35+01:00",
1419                 "1980-01-01T10:15:35+14:00"
1420             })
1421     void testThrownParseOutputTimestampInvalidRange(String outputTimestamp) {
1422         // date is not within the valid range 1980-01-01T00:00:02Z to 2099-12-31T23:59:59Z
1423         assertThatExceptionOfType(IllegalArgumentException.class)
1424                 .isThrownBy(() -> MavenArchiver.parseBuildOutputTimestamp(outputTimestamp))
1425                 .withMessageContaining("is not within the valid range 1980-01-01T00:00:02Z to 2099-12-31T23:59:59Z");
1426     }
1427 
1428     @ParameterizedTest
1429     @CsvSource({
1430         "2011-12-03T10:15:30+01,1322903730",
1431         "2019-10-05T20:37:42+02,1570300662",
1432         "2011-12-03T10:15:30+06,1322885730",
1433         "1988-02-22T20:37:42+06,572539062"
1434     })
1435     @EnabledForJreRange(min = JRE.JAVA_9)
1436     void testShortOffset(String value, long expected) {
1437         assertThat(MavenArchiver.parseBuildOutputTimestamp(value)).contains(Instant.ofEpochSecond(expected));
1438     }
1439 }