diff --git a/maven-repository-metadata/pom.xml b/maven-repository-metadata/pom.xml
index 31c0418b0c98..a9951098471e 100644
--- a/maven-repository-metadata/pom.xml
+++ b/maven-repository-metadata/pom.xml
@@ -38,6 +38,11 @@ under the License.
org.codehaus.plexus
plexus-utils
+
+ org.apache.maven.resolver
+ maven-resolver-api
+ test
+
diff --git a/maven-repository-metadata/src/main/mdo/metadata.mdo b/maven-repository-metadata/src/main/mdo/metadata.mdo
index ddaeb0a56c26..6ce381d4a830 100644
--- a/maven-repository-metadata/src/main/mdo/metadata.mdo
+++ b/maven-repository-metadata/src/main/mdo/metadata.mdo
@@ -88,6 +88,11 @@ under the License.
1.0.0+
versions = new java.util.LinkedHashMap<>();
+ // never convert from legacy to new format if either source or target is legacy format
+ if ( !v.getSnapshotVersions().isEmpty() )
+ {
+ for ( SnapshotVersion sv : versioning.getSnapshotVersions() )
+ {
+ String key = getSnapshotVersionKey( sv );
+ versions.put( key, sv );
+ }
+ // never convert from legacy format
+ if ( !versions.isEmpty() )
+ {
+ for ( SnapshotVersion sv : v.getSnapshotVersions() )
+ {
+ String key = getSnapshotVersionKey( sv );
+ if ( !versions.containsKey( key ) )
+ {
+ versions.put( key, sv );
+ }
+ }
+ }
+ v.setSnapshotVersions( new java.util.ArrayList( versions.values() ) );
+ }
+
+ changed = true;
+ }
}
}
}
@@ -241,7 +277,7 @@ under the License.
lastUpdated
1.0.0+
String
- When the metadata was last updated (both "groupId/artifactId" and "groupId/artifactId/version" directories)
+ When the metadata was last updated (both "groupId/artifactId" and "groupId/artifactId/version" directories). The timestamp is expressed using UTC in the format yyyyMMddHHmmss.
snapshot
@@ -254,7 +290,7 @@ under the License.
snapshotVersions
1.1.0+
- Information for each sub-artifact available in this artifact snapshot.
+ Information for each sub-artifact available in this artifact snapshot. This is only the most recent SNAPSHOT for each unique extension/classifier combination.
SnapshotVersion
*
@@ -289,7 +325,7 @@ under the License.
timestamp
1.0.0+
- The time it was deployed
+ The timestamp when this version was deployed. The timestamp is expressed using UTC in the format yyyyMMdd.HHmmss.
String
@@ -316,26 +352,30 @@ under the License.
classifier
1.1.0+
String
- The classifier of the sub-artifact.
+ The classifier of the sub-artifact. Each classifier and extension pair may only appear once.
+ true
extension
1.1.0+
String
- The file extension of the sub-artifact.
+ The file extension of the sub-artifact. Each classifier and extension pair may only appear once.
+ true
version
1.1.0+
String
The resolved snapshot version of the sub-artifact.
+ true
updated
1.1.0+
String
The timestamp when this version information was last updated. The timestamp is expressed using UTC in the format yyyyMMddHHmmss.
+ true
diff --git a/maven-repository-metadata/src/test/java/org/apache/maven/artifact/repository/metadata/MetadataTest.java b/maven-repository-metadata/src/test/java/org/apache/maven/artifact/repository/metadata/MetadataTest.java
new file mode 100644
index 000000000000..1bf00b5ec1cf
--- /dev/null
+++ b/maven-repository-metadata/src/test/java/org/apache/maven/artifact/repository/metadata/MetadataTest.java
@@ -0,0 +1,290 @@
+package org.apache.maven.artifact.repository.metadata;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class MetadataTest
+{
+
+ Artifact artifact;
+
+ Metadata target;
+
+ @Before
+ public void before()
+ {
+ artifact = new DefaultArtifact( "myGroup:myArtifact:1.0-SNAPSHOT" );
+ target = createMetadataFromArtifact( artifact );
+ }
+
+ /*--- START test common metadata ---*/
+ @Test
+ public void mergeEmptyMetadata()
+ throws Exception
+ {
+ Metadata metadata = new Metadata();
+ assertFalse( metadata.merge( new Metadata() ) );
+ }
+
+ @Test
+ public void mergeDifferentGAV()
+ throws Exception
+ {
+ // merge implicitly assumes that merge is only called on the same GAV and does not perform any validation here!
+ Metadata source = new Metadata();
+ source.setArtifactId( "source-artifact" );
+ source.setGroupId( "source-group" );
+ source.setVersion( "2.0" );
+ assertFalse( target.merge( source ) );
+ assertEquals( "myArtifact", target.getArtifactId() );
+ assertEquals( "myGroup", target.getGroupId() );
+ assertEquals( "1.0-SNAPSHOT", target.getVersion() );
+ }
+ /*--- END test common metadata ---*/
+
+ /*--- START test "groupId/artifactId/version" metadata ---*/
+ @Test
+ public void mergeSnapshotWithEmptyList()
+ throws Exception
+ {
+ Snapshot snapshot = new Snapshot();
+ snapshot.setBuildNumber( 3 );
+ snapshot.setTimestamp( "20200710.072412" );
+ target.getVersioning().setSnapshot( snapshot );
+ target.getVersioning().setLastUpdated( "20200921071745" );
+ SnapshotVersion sv = new SnapshotVersion();
+ sv.setClassifier( "sources" );
+ sv.setExtension( "jar" );
+ sv.setUpdated( "20200710072412" );
+ target.getVersioning().addSnapshotVersion( sv );
+
+ Metadata source = createMetadataFromArtifact( artifact );
+ // nothing should be actually changed, but still merge returns true
+ assertTrue( target.merge( source ) );
+
+ // NOTE! Merge updates last updated to source
+ assertEquals( "20200921071745", source.getVersioning().getLastUpdated() );
+
+ assertEquals( "myArtifact", target.getArtifactId() );
+ assertEquals( "myGroup", target.getGroupId() );
+
+ assertEquals( 3, target.getVersioning().getSnapshot().getBuildNumber() );
+ assertEquals( "20200710.072412", target.getVersioning().getSnapshot().getTimestamp() );
+
+ assertEquals( 1, target.getVersioning().getSnapshotVersions().size() );
+ assertEquals( "sources", target.getVersioning().getSnapshotVersions().get( 0 ).getClassifier() );
+ assertEquals( "jar", target.getVersioning().getSnapshotVersions().get( 0 ).getExtension() );
+ assertEquals( "20200710072412", target.getVersioning().getSnapshotVersions().get( 0 ).getUpdated() );
+ }
+
+ @Test
+ public void mergeWithSameSnapshotWithDifferentVersionsAndNewerLastUpdated()
+ {
+ Metadata source = createMetadataFromArtifact( artifact );
+ Date before = new Date( System.currentTimeMillis() - 5000 );
+ Date after = new Date( System.currentTimeMillis() );
+ addSnapshotVersion( target.getVersioning(), "jar", before, "1", 1 );
+ SnapshotVersion sv2 =
+ addSnapshotVersion( source.getVersioning(), "jar", after, "1.0-" + formatDate( after, true ) + "-2", 2 );
+ SnapshotVersion sv3 =
+ addSnapshotVersion( source.getVersioning(), "pom", after, "1.0-" + formatDate( after, true ) + "-2", 2 );
+ assertTrue( target.merge( source ) );
+ Versioning actualVersioning = target.getVersioning();
+ assertEquals( 2, actualVersioning.getSnapshotVersions().size() );
+ assertEquals( sv2, actualVersioning.getSnapshotVersions().get( 0 ) );
+ assertEquals( sv3, actualVersioning.getSnapshotVersions().get( 1 ) );
+ assertEquals( formatDate( after, false ), actualVersioning.getLastUpdated() );
+ assertEquals( formatDate( after, true ), actualVersioning.getSnapshot().getTimestamp() );
+ assertEquals( 2, actualVersioning.getSnapshot().getBuildNumber() );
+ }
+
+ @Test
+ public void mergeWithSameSnapshotWithDifferentVersionsAndOlderLastUpdated()
+ {
+ Metadata source = createMetadataFromArtifact( artifact );
+ Date before = new Date( System.currentTimeMillis() - 5000 );
+ Date after = new Date( System.currentTimeMillis() );
+ SnapshotVersion sv1 = addSnapshotVersion( target.getVersioning(), after, artifact );
+ addSnapshotVersion( source.getVersioning(), before, artifact );
+ // nothing should be updated, as the target was already updated at a later date than source
+ assertFalse( target.merge( source ) );
+ assertEquals( 1, target.getVersioning().getSnapshotVersions().size() );
+ assertEquals( sv1, target.getVersioning().getSnapshotVersions().get( 0 ) );
+ assertEquals( formatDate( after, false ), target.getVersioning().getLastUpdated() );
+ assertEquals( formatDate( after, true ), target.getVersioning().getSnapshot().getTimestamp() );
+ }
+
+ @Test
+ public void mergeWithSameSnapshotWithSameVersionAndTimestamp()
+ {
+ Metadata source = createMetadataFromArtifact( artifact );
+ Date date = new Date();
+ addSnapshotVersion( target.getVersioning(), date, artifact );
+ SnapshotVersion sv1 = addSnapshotVersion( source.getVersioning(), date, artifact );
+ // although nothing has changed merge returns true, as the last modified date is equal
+ // TODO: improve merge here?
+ assertTrue( target.merge( source ) );
+ assertEquals( 1, target.getVersioning().getSnapshotVersions().size() );
+ assertEquals( sv1, target.getVersioning().getSnapshotVersions().get( 0 ) );
+ assertEquals( formatDate( date, false ), target.getVersioning().getLastUpdated() );
+ assertEquals( formatDate( date, true ), target.getVersioning().getSnapshot().getTimestamp() );
+ }
+
+ @Test
+ public void mergeLegacyWithSnapshotLegacy()
+ {
+ Metadata source = createMetadataFromArtifact( artifact );
+ Date before = new Date( System.currentTimeMillis() - 5000 );
+ Date after = new Date( System.currentTimeMillis() );
+ // legacy metadata did not have "versioning.snapshotVersions"
+ addSnapshotVersionLegacy( target.getVersioning(), before, 1 );
+ addSnapshotVersionLegacy( source.getVersioning(), after, 2 );
+ // although nothing has changed merge returns true, as the last modified date is equal
+ // TODO: improve merge here?
+ assertTrue( target.merge( source ) );
+ assertEquals( 0, target.getVersioning().getSnapshotVersions().size() );
+ assertEquals( formatDate( after, false ), target.getVersioning().getLastUpdated() );
+ assertEquals( formatDate( after, true ), target.getVersioning().getSnapshot().getTimestamp() );
+ }
+
+ @Test
+ public void mergeLegacyWithSnapshot()
+ {
+ Metadata source = createMetadataFromArtifact( artifact );
+ Date before = new Date( System.currentTimeMillis() - 5000 );
+ Date after = new Date( System.currentTimeMillis() );
+ // legacy metadata did not have "versioning.snapshotVersions"
+ addSnapshotVersionLegacy( target.getVersioning(), before, 1 );
+ addSnapshotVersion( source.getVersioning(), after, artifact );
+ // although nothing has changed merge returns true, as the last modified date is equal
+ // TODO: improve merge here?
+ assertTrue( target.merge( source ) );
+ // never convert from legacy format to v1.1 format
+ assertEquals( 0, target.getVersioning().getSnapshotVersions().size() );
+ assertEquals( formatDate( after, false ), target.getVersioning().getLastUpdated() );
+ assertEquals( formatDate( after, true ), target.getVersioning().getSnapshot().getTimestamp() );
+ }
+
+ @Test
+ public void mergeWithSnapshotLegacy()
+ {
+ Metadata source = createMetadataFromArtifact( artifact );
+ Date before = new Date( System.currentTimeMillis() - 5000 );
+ Date after = new Date( System.currentTimeMillis() );
+ addSnapshotVersion( target.getVersioning(), before, artifact );
+ // legacy metadata did not have "versioning.snapshotVersions"
+ addSnapshotVersionLegacy( source.getVersioning(), after, 2 );
+ // although nothing has changed merge returns true, as the last modified date is equal
+ // TODO: improve merge here?
+ assertTrue( target.merge( source ) );
+ // the result must be legacy format as well
+ assertEquals( 0, target.getVersioning().getSnapshotVersions().size() );
+ assertEquals( formatDate( after, false ), target.getVersioning().getLastUpdated() );
+ assertEquals( formatDate( after, true ), target.getVersioning().getSnapshot().getTimestamp() );
+ assertEquals( 2, target.getVersioning().getSnapshot().getBuildNumber() );
+ }
+ /*-- END test "groupId/artifactId/version" metadata ---*/
+
+ /*-- START helper methods to populate metadata objects ---*/
+ private static final String SNAPSHOT = "SNAPSHOT";
+
+ private static final String DEFAULT_SNAPSHOT_TIMESTAMP_FORMAT = "yyyyMMdd.HHmmss";
+
+ private static final String DEFAULT_DATE_FORMAT = "yyyyMMddHHmmss";
+
+ private static String formatDate( Date date, boolean forSnapshotTimestamp )
+ {
+ // logic from metadata.mdo, class "Versioning"
+ TimeZone timezone = TimeZone.getTimeZone( "UTC" );
+ DateFormat fmt =
+ new SimpleDateFormat( forSnapshotTimestamp ? DEFAULT_SNAPSHOT_TIMESTAMP_FORMAT : DEFAULT_DATE_FORMAT );
+ fmt.setCalendar( new GregorianCalendar() );
+ fmt.setTimeZone( timezone );
+ return fmt.format( date );
+ }
+
+ private static Metadata createMetadataFromArtifact( Artifact artifact )
+ {
+ Metadata metadata = new Metadata();
+ metadata.setArtifactId( artifact.getArtifactId() );
+ metadata.setGroupId( artifact.getGroupId() );
+ metadata.setVersion( artifact.getVersion() );
+ metadata.setVersioning( new Versioning() );
+ return metadata;
+ }
+
+ private static SnapshotVersion addSnapshotVersion( Versioning versioning, Date timestamp, Artifact artifact )
+ {
+ int buildNumber = 1;
+ // this generates timestamped versions like maven-resolver-provider:
+ // https://github.com/apache/maven/blob/03df5f7c639db744a3597c7175c92c8e2a27767b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/RemoteSnapshotMetadata.java#L79
+ String version = artifact.getVersion();
+ String qualifier = formatDate( timestamp, true ) + '-' + buildNumber;
+ version = version.substring( 0, version.length() - SNAPSHOT.length() ) + qualifier;
+ return addSnapshotVersion( versioning, artifact.getExtension(), timestamp, version, buildNumber );
+ }
+
+ private static SnapshotVersion addSnapshotVersion( Versioning versioning, String extension, Date timestamp,
+ String version, int buildNumber )
+ {
+ Snapshot snapshot = new Snapshot();
+ snapshot.setBuildNumber( buildNumber );
+ snapshot.setTimestamp( formatDate( timestamp, true ) );
+
+ SnapshotVersion sv = new SnapshotVersion();
+ sv.setExtension( extension );
+ sv.setVersion( version );
+ sv.setUpdated( formatDate( timestamp, false ) );
+ versioning.addSnapshotVersion( sv );
+
+ // make the new snapshot the current one
+ versioning.setSnapshot( snapshot );
+ versioning.setLastUpdatedTimestamp( timestamp );
+ return sv;
+ }
+
+ // the format written by Maven 2
+ // (https://maven.apache.org/ref/2.2.1/maven-repository-metadata/repository-metadata.html)
+ private static void addSnapshotVersionLegacy( Versioning versioning, Date timestamp, int buildNumber )
+ {
+ Snapshot snapshot = new Snapshot();
+ snapshot.setBuildNumber( buildNumber );
+ snapshot.setTimestamp( formatDate( timestamp, true ) );
+
+ versioning.setSnapshot( snapshot );
+ versioning.setLastUpdatedTimestamp( timestamp );
+ }
+ /*-- END helper methods to populate metadata objects ---*/
+}
\ No newline at end of file