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