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.artifact.repository.metadata;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.text.DateFormat;
24  import java.text.SimpleDateFormat;
25  import java.util.Date;
26  import java.util.GregorianCalendar;
27  import java.util.TimeZone;
28  
29  import org.apache.maven.metadata.v4.MetadataStaxReader;
30  import org.apache.maven.metadata.v4.MetadataStaxWriter;
31  import org.eclipse.aether.artifact.Artifact;
32  import org.eclipse.aether.artifact.DefaultArtifact;
33  import org.junit.jupiter.api.BeforeEach;
34  import org.junit.jupiter.api.Test;
35  
36  import static org.junit.jupiter.api.Assertions.assertEquals;
37  import static org.junit.jupiter.api.Assertions.assertFalse;
38  import static org.junit.jupiter.api.Assertions.assertNotNull;
39  import static org.junit.jupiter.api.Assertions.assertTrue;
40  
41  class MetadataTest {
42  
43      Artifact artifact;
44  
45      Metadata target;
46  
47      @BeforeEach
48      void before() {
49          artifact = new DefaultArtifact("myGroup:myArtifact:1.0-SNAPSHOT");
50          target = createMetadataFromArtifact(artifact);
51      }
52  
53      /*--- START test common metadata ---*/
54      @Test
55      void mergeEmptyMetadata() throws Exception {
56          Metadata metadata = new Metadata();
57          assertFalse(metadata.merge(new Metadata()));
58      }
59  
60      @Test
61      void mergeDifferentGAV() throws Exception {
62          // merge implicitly assumes that merge is only called on the same GAV and does not perform any validation here!
63          Metadata source = new Metadata();
64          source.setArtifactId("source-artifact");
65          source.setGroupId("source-group");
66          source.setVersion("2.0");
67          assertFalse(target.merge(source));
68          assertEquals("myArtifact", target.getArtifactId());
69          assertEquals("myGroup", target.getGroupId());
70          assertEquals("1.0-SNAPSHOT", target.getVersion());
71      }
72      /*--- END test common metadata ---*/
73  
74      /*--- START test "groupId/artifactId/version" metadata ---*/
75      @Test
76      void mergeSnapshotWithEmptyList() throws Exception {
77          Snapshot snapshot = new Snapshot();
78          snapshot.setBuildNumber(3);
79          snapshot.setTimestamp("20200710.072412");
80          target.getVersioning().setSnapshot(snapshot);
81          target.getVersioning().setLastUpdated("20200921071745");
82          SnapshotVersion sv = new SnapshotVersion();
83          sv.setClassifier("sources");
84          sv.setExtension("jar");
85          sv.setUpdated("20200710072412");
86          target.getVersioning().addSnapshotVersion(sv);
87  
88          Metadata source = createMetadataFromArtifact(artifact);
89          // nothing should be actually changed, but still merge returns true
90          assertTrue(target.merge(source));
91  
92          // NOTE! Merge updates last updated to source
93          assertEquals("20200921071745", source.getVersioning().getLastUpdated());
94  
95          assertEquals("myArtifact", target.getArtifactId());
96          assertEquals("myGroup", target.getGroupId());
97  
98          assertEquals(3, target.getVersioning().getSnapshot().getBuildNumber());
99          assertEquals("20200710.072412", target.getVersioning().getSnapshot().getTimestamp());
100 
101         assertEquals(1, target.getVersioning().getSnapshotVersions().size());
102         assertEquals(
103                 "sources", target.getVersioning().getSnapshotVersions().get(0).getClassifier());
104         assertEquals("jar", target.getVersioning().getSnapshotVersions().get(0).getExtension());
105         assertEquals(
106                 "20200710072412",
107                 target.getVersioning().getSnapshotVersions().get(0).getUpdated());
108     }
109 
110     @Test
111     void mergeWithSameSnapshotWithDifferentVersionsAndNewerLastUpdated() {
112         Metadata source = createMetadataFromArtifact(artifact);
113         Date before = new Date(System.currentTimeMillis() - 5000);
114         Date after = new Date(System.currentTimeMillis());
115         addSnapshotVersion(target.getVersioning(), "jar", before, "1", 1);
116         SnapshotVersion sv2 =
117                 addSnapshotVersion(source.getVersioning(), "jar", after, "1.0-" + formatDate(after, true) + "-2", 2);
118         SnapshotVersion sv3 =
119                 addSnapshotVersion(source.getVersioning(), "pom", after, "1.0-" + formatDate(after, true) + "-2", 2);
120         assertTrue(target.merge(source));
121         Versioning actualVersioning = target.getVersioning();
122         assertEquals(2, actualVersioning.getSnapshotVersions().size());
123         assertEquals(sv2, actualVersioning.getSnapshotVersions().get(0));
124         assertEquals(sv3, actualVersioning.getSnapshotVersions().get(1));
125         assertEquals(formatDate(after, false), actualVersioning.getLastUpdated());
126         assertEquals(formatDate(after, true), actualVersioning.getSnapshot().getTimestamp());
127         assertEquals(2, actualVersioning.getSnapshot().getBuildNumber());
128     }
129 
130     @Test
131     void mergeWithSameSnapshotWithDifferentVersionsAndOlderLastUpdated() {
132         Metadata source = createMetadataFromArtifact(artifact);
133         Date before = new Date(System.currentTimeMillis() - 5000);
134         Date after = new Date(System.currentTimeMillis());
135         SnapshotVersion sv1 = addSnapshotVersion(target.getVersioning(), after, artifact);
136         addSnapshotVersion(source.getVersioning(), before, artifact);
137         // nothing should be updated, as the target was already updated at a later date than source
138         assertFalse(target.merge(source));
139         assertEquals(1, target.getVersioning().getSnapshotVersions().size());
140         assertEquals(sv1, target.getVersioning().getSnapshotVersions().get(0));
141         assertEquals(formatDate(after, false), target.getVersioning().getLastUpdated());
142         assertEquals(
143                 formatDate(after, true), target.getVersioning().getSnapshot().getTimestamp());
144     }
145 
146     @Test
147     void mergeWithSameSnapshotWithSameVersionAndTimestamp() {
148         Metadata source = createMetadataFromArtifact(artifact);
149         Date date = new Date();
150         addSnapshotVersion(target.getVersioning(), date, artifact);
151         SnapshotVersion sv1 = addSnapshotVersion(source.getVersioning(), date, artifact);
152         // although nothing has changed merge returns true, as the last modified date is equal
153         // TODO: improve merge here?
154         assertTrue(target.merge(source));
155         assertEquals(1, target.getVersioning().getSnapshotVersions().size());
156         assertEquals(sv1, target.getVersioning().getSnapshotVersions().get(0));
157         assertEquals(formatDate(date, false), target.getVersioning().getLastUpdated());
158         assertEquals(
159                 formatDate(date, true), target.getVersioning().getSnapshot().getTimestamp());
160     }
161 
162     @Test
163     void mergeLegacyWithSnapshotLegacy() {
164         Metadata source = createMetadataFromArtifact(artifact);
165         Date before = new Date(System.currentTimeMillis() - 5000);
166         Date after = new Date(System.currentTimeMillis());
167         // legacy metadata did not have "versioning.snapshotVersions"
168         addSnapshotVersionLegacy(target.getVersioning(), before, 1);
169         addSnapshotVersionLegacy(source.getVersioning(), after, 2);
170         // although nothing has changed merge returns true, as the last modified date is equal
171         // TODO: improve merge here?
172         assertTrue(target.merge(source));
173         assertEquals(0, target.getVersioning().getSnapshotVersions().size());
174         assertEquals(formatDate(after, false), target.getVersioning().getLastUpdated());
175         assertEquals(
176                 formatDate(after, true), target.getVersioning().getSnapshot().getTimestamp());
177     }
178 
179     @Test
180     void mergeLegacyWithSnapshot() {
181         Metadata source = createMetadataFromArtifact(artifact);
182         Date before = new Date(System.currentTimeMillis() - 5000);
183         Date after = new Date(System.currentTimeMillis());
184         // legacy metadata did not have "versioning.snapshotVersions"
185         addSnapshotVersionLegacy(target.getVersioning(), before, 1);
186         addSnapshotVersion(source.getVersioning(), after, artifact);
187         // although nothing has changed merge returns true, as the last modified date is equal
188         // TODO: improve merge here?
189         assertTrue(target.merge(source));
190         // never convert from legacy format to v1.1 format
191         assertEquals(0, target.getVersioning().getSnapshotVersions().size());
192         assertEquals(formatDate(after, false), target.getVersioning().getLastUpdated());
193         assertEquals(
194                 formatDate(after, true), target.getVersioning().getSnapshot().getTimestamp());
195     }
196 
197     @Test
198     void mergeWithSnapshotLegacy() {
199         Metadata source = createMetadataFromArtifact(artifact);
200         Date before = new Date(System.currentTimeMillis() - 5000);
201         Date after = new Date(System.currentTimeMillis());
202         addSnapshotVersion(target.getVersioning(), before, artifact);
203         // legacy metadata did not have "versioning.snapshotVersions"
204         addSnapshotVersionLegacy(source.getVersioning(), after, 2);
205         // although nothing has changed merge returns true, as the last modified date is equal
206         // TODO: improve merge here?
207         assertTrue(target.merge(source));
208         // the result must be legacy format as well
209         assertEquals(0, target.getVersioning().getSnapshotVersions().size());
210         assertEquals(formatDate(after, false), target.getVersioning().getLastUpdated());
211         assertEquals(
212                 formatDate(after, true), target.getVersioning().getSnapshot().getTimestamp());
213         assertEquals(2, target.getVersioning().getSnapshot().getBuildNumber());
214     }
215     /*-- END test "groupId/artifactId/version" metadata ---*/
216 
217     @Test
218     void testRoundtrip() throws Exception {
219         Metadata source = new Metadata(org.apache.maven.api.metadata.Metadata.newBuilder(
220                         createMetadataFromArtifact(artifact).getDelegate(), true)
221                 .modelEncoding("UTF-16")
222                 .build());
223         ByteArrayOutputStream baos = new ByteArrayOutputStream();
224         new MetadataStaxWriter().write(baos, source.getDelegate());
225         Metadata source2 =
226                 new Metadata(new MetadataStaxReader().read(new ByteArrayInputStream(baos.toByteArray()), true));
227         assertNotNull(source2);
228     }
229 
230     /*-- START helper methods to populate metadata objects ---*/
231     private static final String SNAPSHOT = "SNAPSHOT";
232 
233     private static final String DEFAULT_SNAPSHOT_TIMESTAMP_FORMAT = "yyyyMMdd.HHmmss";
234 
235     private static final String DEFAULT_DATE_FORMAT = "yyyyMMddHHmmss";
236 
237     private static String formatDate(Date date, boolean forSnapshotTimestamp) {
238         // logic from metadata.mdo, class "Versioning"
239         TimeZone timezone = TimeZone.getTimeZone("UTC");
240         DateFormat fmt =
241                 new SimpleDateFormat(forSnapshotTimestamp ? DEFAULT_SNAPSHOT_TIMESTAMP_FORMAT : DEFAULT_DATE_FORMAT);
242         fmt.setCalendar(new GregorianCalendar());
243         fmt.setTimeZone(timezone);
244         return fmt.format(date);
245     }
246 
247     private static Metadata createMetadataFromArtifact(Artifact artifact) {
248         Metadata metadata = new Metadata();
249         metadata.setArtifactId(artifact.getArtifactId());
250         metadata.setGroupId(artifact.getGroupId());
251         metadata.setVersion(artifact.getVersion());
252         metadata.setVersioning(new Versioning());
253         return metadata;
254     }
255 
256     private static SnapshotVersion addSnapshotVersion(Versioning versioning, Date timestamp, Artifact artifact) {
257         int buildNumber = 1;
258         // this generates timestamped versions like maven-resolver-provider:
259         // https://github.com/apache/maven/blob/03df5f7c639db744a3597c7175c92c8e2a27767b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/RemoteSnapshotMetadata.java#L79
260         String version = artifact.getVersion();
261         String qualifier = formatDate(timestamp, true) + '-' + buildNumber;
262         version = version.substring(0, version.length() - SNAPSHOT.length()) + qualifier;
263         return addSnapshotVersion(versioning, artifact.getExtension(), timestamp, version, buildNumber);
264     }
265 
266     private static SnapshotVersion addSnapshotVersion(
267             Versioning versioning, String extension, Date timestamp, String version, int buildNumber) {
268         Snapshot snapshot = new Snapshot();
269         snapshot.setBuildNumber(buildNumber);
270         snapshot.setTimestamp(formatDate(timestamp, true));
271 
272         SnapshotVersion sv = new SnapshotVersion();
273         sv.setExtension(extension);
274         sv.setVersion(version);
275         sv.setUpdated(formatDate(timestamp, false));
276         versioning.addSnapshotVersion(sv);
277 
278         // make the new snapshot the current one
279         versioning.setSnapshot(snapshot);
280         versioning.setLastUpdatedTimestamp(timestamp);
281         return sv;
282     }
283 
284     // the format written by Maven 2
285     // (https://maven.apache.org/ref/2.2.1/maven-repository-metadata/repository-metadata.html)
286     private static void addSnapshotVersionLegacy(Versioning versioning, Date timestamp, int buildNumber) {
287         Snapshot snapshot = new Snapshot();
288         snapshot.setBuildNumber(buildNumber);
289         snapshot.setTimestamp(formatDate(timestamp, true));
290 
291         versioning.setSnapshot(snapshot);
292         versioning.setLastUpdatedTimestamp(timestamp);
293     }
294     /*-- END helper methods to populate metadata objects ---*/
295 }