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