You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by mi...@apache.org on 2022/02/27 11:10:24 UTC

[maven] branch master updated: [MNG-5180] Versioning's snapshot version list is not included in metadata merge

This is an automated email from the ASF dual-hosted git repository.

michaelo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new 1e33a57  [MNG-5180] Versioning's snapshot version list is not included in metadata merge
1e33a57 is described below

commit 1e33a572643628e7742eda2d469e4bd04b880523
Author: Tomi Pakarinen <to...@resys.io>
AuthorDate: Fri Aug 28 22:35:15 2020 +0300

    [MNG-5180] Versioning's snapshot version list is not included in metadata merge
    
    Co-authored-by: Konrad Windszus <kw...@apache.org>
    
    This closes #681
---
 maven-repository-metadata/pom.xml                  |   5 +
 .../src/main/mdo/metadata.mdo                      |  50 +++-
 .../artifact/repository/metadata/MetadataTest.java | 289 +++++++++++++++++++++
 3 files changed, 339 insertions(+), 5 deletions(-)

diff --git a/maven-repository-metadata/pom.xml b/maven-repository-metadata/pom.xml
index 28fbbf8..d989e0b 100644
--- a/maven-repository-metadata/pom.xml
+++ b/maven-repository-metadata/pom.xml
@@ -38,6 +38,11 @@ under the License.
       <groupId>org.codehaus.plexus</groupId>
       <artifactId>plexus-utils</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.apache.maven.resolver</groupId>
+      <artifactId>maven-resolver-api</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/maven-repository-metadata/src/main/mdo/metadata.mdo b/maven-repository-metadata/src/main/mdo/metadata.mdo
index ddaeb0a..6ce381d 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.
         <codeSegment>
           <version>1.0.0+</version>
           <code><![CDATA[
+    private String getSnapshotVersionKey( SnapshotVersion sv )
+    {
+        return sv.getClassifier() + ":" + sv.getExtension();
+    }
+
     public boolean merge( Metadata sourceMetadata )
     {
         boolean changed = false;
@@ -178,11 +183,13 @@ under the License.
                 Snapshot snapshot = versioning.getSnapshot();
                 if ( snapshot != null )
                 {
+                    boolean updateSnapshotVersions = false;
                     if ( s == null )
                     {
                         s = new Snapshot();
                         v.setSnapshot( s );
                         changed = true;
+                        updateSnapshotVersions = true;
                     }
 
                     // overwrite
@@ -191,6 +198,7 @@ under the License.
                     {
                         s.setTimestamp( snapshot.getTimestamp() );
                         changed = true;
+                        updateSnapshotVersions = true;
                     }
                     if ( s.getBuildNumber() != snapshot.getBuildNumber() )
                     {
@@ -202,6 +210,34 @@ under the License.
                         s.setLocalCopy( snapshot.isLocalCopy() );
                         changed = true;
                     }
+                    if ( updateSnapshotVersions )
+                    {
+                        java.util.Map<String, SnapshotVersion> 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<SnapshotVersion>( versions.values() ) );
+                        }
+
+                        changed = true;
+                    }
                 }
             }
         }
@@ -241,7 +277,7 @@ under the License.
           <name>lastUpdated</name>
           <version>1.0.0+</version>
           <type>String</type>
-          <description>When the metadata was last updated (both "groupId/artifactId" and "groupId/artifactId/version" directories)</description>
+          <description>When the metadata was last updated (both "groupId/artifactId" and "groupId/artifactId/version" directories). The timestamp is expressed using UTC in the format yyyyMMddHHmmss.</description>
         </field>
         <field xdoc.separator="blank">
           <name>snapshot</name>
@@ -254,7 +290,7 @@ under the License.
         <field>
           <name>snapshotVersions</name>
           <version>1.1.0+</version>
-          <description>Information for each sub-artifact available in this artifact snapshot.</description>
+          <description>Information for each sub-artifact available in this artifact snapshot. This is only the most recent SNAPSHOT for each unique extension/classifier combination.</description>
           <association>
             <type>SnapshotVersion</type>
             <multiplicity>*</multiplicity>
@@ -289,7 +325,7 @@ under the License.
         <field>
           <name>timestamp</name>
           <version>1.0.0+</version>
-          <description>The time it was deployed</description>
+          <description>The timestamp when this version was deployed. The timestamp is expressed using UTC in the format yyyyMMdd.HHmmss.</description>
           <type>String</type>
         </field>
         <field>
@@ -316,26 +352,30 @@ under the License.
           <name>classifier</name>
           <version>1.1.0+</version>
           <type>String</type>
-          <description>The classifier of the sub-artifact.</description>
+          <description>The classifier of the sub-artifact. Each classifier and extension pair may only appear once.</description>
           <defaultValue></defaultValue>
+          <identifier>true</identifier>
         </field>
         <field>
           <name>extension</name>
           <version>1.1.0+</version>
           <type>String</type>
-          <description>The file extension of the sub-artifact.</description>
+          <description>The file extension of the sub-artifact. Each classifier and extension pair may only appear once.</description>
+          <identifier>true</identifier>
         </field>
         <field xml.tagName="value">
           <name>version</name>
           <version>1.1.0+</version>
           <type>String</type>
           <description>The resolved snapshot version of the sub-artifact.</description>
+          <identifier>true</identifier>
         </field>
         <field>
           <name>updated</name>
           <version>1.1.0+</version>
           <type>String</type>
           <description>The timestamp when this version information was last updated. The timestamp is expressed using UTC in the format yyyyMMddHHmmss.</description>
+          <identifier>true</identifier>
         </field>
       </fields>
     </class>
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 0000000..ccdadbf
--- /dev/null
+++ b/maven-repository-metadata/src/test/java/org/apache/maven/artifact/repository/metadata/MetadataTest.java
@@ -0,0 +1,289 @@
+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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+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.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class MetadataTest
+{
+
+    Artifact artifact;
+
+    Metadata target;
+
+    @BeforeEach
+    void before()
+    {
+        artifact = new DefaultArtifact( "myGroup:myArtifact:1.0-SNAPSHOT" );
+        target = createMetadataFromArtifact( artifact );
+    }
+
+    /*--- START test common metadata ---*/
+    @Test
+    void mergeEmptyMetadata()
+        throws Exception
+    {
+        Metadata metadata = new Metadata();
+        assertFalse( metadata.merge( new Metadata() ) );
+    }
+
+    @Test
+    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
+    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
+    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
+    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
+    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
+    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
+    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
+    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