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