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

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git


The following commit(s) were added to refs/heads/master by this push:
     new 39bc9c1ff CAY-2952 Unstable project extension ordering
39bc9c1ff is described below

commit 39bc9c1ff198e00f9faff0645bcb9827bdaf116d
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sun May 24 17:32:53 2026 -0400

    CAY-2952 Unstable project extension ordering
---
 RELEASE-NOTES.txt                                  |  1 +
 .../org/apache/cayenne/gen/xml/CgenExtension.java  |  5 ++++
 .../cayenne/dbsync/xml/DbImportExtension.java      |  4 +++
 .../apache/cayenne/project/FileProjectSaver.java   |  9 ++++---
 .../project/extension/ProjectExtension.java        |  8 ++++++
 .../project/extension/info/InfoExtension.java      |  5 ++++
 .../extension/validation/ValidationExtension.java  |  5 ++++
 .../cayenne/project/FileProjectSaverTest.java      | 29 ++++++++++++++++++++++
 8 files changed, 63 insertions(+), 3 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index c94bd087c..879dbf7cd 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -78,6 +78,7 @@ CAY-2931 Modeler: default encoding detected as UTF8, should 
be UTF-8
 CAY-2934 Modeler: dropdown data trunction
 CAY-2944 MergerTokenFactoryProvider does not resolve adapter subclasses
 CAY-2951 Newly reverse-engineered entities are excluded from CGen
+CAY-2952 Unstable project extension ordering
 
 ----------------------------------
 Release: 5.0-M1
diff --git 
a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenExtension.java 
b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenExtension.java
index 374819942..b4cd865c6 100644
--- a/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenExtension.java
+++ b/cayenne-cgen/src/main/java/org/apache/cayenne/gen/xml/CgenExtension.java
@@ -51,4 +51,9 @@ public class CgenExtension implements ProjectExtension {
     public ConfigurationNodeVisitor<String> createNamingDelegate() {
         return new BaseNamingDelegate();
     }
+
+    @Override
+    public int order() {
+        return 30;
+    }
 }
diff --git 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/xml/DbImportExtension.java
 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/xml/DbImportExtension.java
index 5732061c7..4b24ab66a 100644
--- 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/xml/DbImportExtension.java
+++ 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/xml/DbImportExtension.java
@@ -53,4 +53,8 @@ public class DbImportExtension implements ProjectExtension {
         return new BaseNamingDelegate();
     }
 
+    @Override
+    public int order() {
+        return 20;
+    }
 }
diff --git 
a/cayenne-project/src/main/java/org/apache/cayenne/project/FileProjectSaver.java
 
b/cayenne-project/src/main/java/org/apache/cayenne/project/FileProjectSaver.java
index e81c8a88f..80dd2ee20 100644
--- 
a/cayenne-project/src/main/java/org/apache/cayenne/project/FileProjectSaver.java
+++ 
b/cayenne-project/src/main/java/org/apache/cayenne/project/FileProjectSaver.java
@@ -42,6 +42,7 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.List;
 
 /**
@@ -68,9 +69,11 @@ public class FileProjectSaver implements ProjectSaver {
         // this is not configurable yet... probably doesn't have to be
         fileEncoding = "UTF-8";
 
-        this.extensions = extensions;
-        Collection<SaverDelegate> delegates = new 
ArrayList<>(extensions.size());
-        for (ProjectExtension extension : extensions) {
+        List<ProjectExtension> sorted = new ArrayList<>(extensions);
+        sorted.sort(Comparator.comparingInt(ProjectExtension::order));
+        this.extensions = sorted;
+        Collection<SaverDelegate> delegates = new ArrayList<>(sorted.size());
+        for (ProjectExtension extension : sorted) {
             delegates.add(extension.createSaverDelegate());
         }
         delegate = new CompoundSaverDelegate(delegates);
diff --git 
a/cayenne-project/src/main/java/org/apache/cayenne/project/extension/ProjectExtension.java
 
b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/ProjectExtension.java
index 2c1253f98..425fdfcbe 100644
--- 
a/cayenne-project/src/main/java/org/apache/cayenne/project/extension/ProjectExtension.java
+++ 
b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/ProjectExtension.java
@@ -50,4 +50,12 @@ public interface ProjectExtension {
 
     ConfigurationNodeVisitor<String> createNamingDelegate();
 
+    /**
+     * Controls the relative position of this extension's XML elements within 
a saved file.
+     * Extensions with lower values are written first. Each known extension 
must return a unique value.
+     *
+     * @since 5.0
+     */
+    int order();
+
 }
diff --git 
a/cayenne-project/src/main/java/org/apache/cayenne/project/extension/info/InfoExtension.java
 
b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/info/InfoExtension.java
index 20aa6c92a..5d40a9215 100644
--- 
a/cayenne-project/src/main/java/org/apache/cayenne/project/extension/info/InfoExtension.java
+++ 
b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/info/InfoExtension.java
@@ -56,4 +56,9 @@ public class InfoExtension implements ProjectExtension {
     public ConfigurationNodeVisitor<String> createNamingDelegate() {
         return new BaseNamingDelegate();
     }
+
+    @Override
+    public int order() {
+        return 10;
+    }
 }
diff --git 
a/cayenne-project/src/main/java/org/apache/cayenne/project/extension/validation/ValidationExtension.java
 
b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/validation/ValidationExtension.java
index ac816df91..b8af97fbf 100644
--- 
a/cayenne-project/src/main/java/org/apache/cayenne/project/extension/validation/ValidationExtension.java
+++ 
b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/validation/ValidationExtension.java
@@ -48,4 +48,9 @@ public class ValidationExtension implements ProjectExtension {
     public ConfigurationNodeVisitor<String> createNamingDelegate() {
         return new BaseNamingDelegate();
     }
+
+    @Override
+    public int order() {
+        return 40;
+    }
 }
diff --git 
a/cayenne-project/src/test/java/org/apache/cayenne/project/FileProjectSaverTest.java
 
b/cayenne-project/src/test/java/org/apache/cayenne/project/FileProjectSaverTest.java
index 37fb24a4b..e44555f56 100644
--- 
a/cayenne-project/src/test/java/org/apache/cayenne/project/FileProjectSaverTest.java
+++ 
b/cayenne-project/src/test/java/org/apache/cayenne/project/FileProjectSaverTest.java
@@ -19,6 +19,7 @@
 package org.apache.cayenne.project;
 
 import org.apache.cayenne.configuration.ConfigurationNameMapper;
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.configuration.ConfigurationTree;
 import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
@@ -27,7 +28,10 @@ import org.apache.cayenne.di.DIBootstrap;
 import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.di.Module;
 import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.project.extension.BaseSaverDelegate;
+import org.apache.cayenne.project.extension.LoaderDelegate;
 import org.apache.cayenne.project.extension.ProjectExtension;
+import org.apache.cayenne.project.extension.SaverDelegate;
 import org.apache.cayenne.resource.URLResource;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -43,6 +47,7 @@ import java.io.File;
 import java.net.URL;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -135,6 +140,30 @@ public class FileProjectSaverTest {
         assertEquals("C", xpath.evaluate("@name", mapRefs.item(2)));
     }
 
+    @Test
+    public void extensionsAreSortedByOrder() {
+        ProjectExtension ext10 = stubExtension(10);
+        ProjectExtension ext20 = stubExtension(20);
+        ProjectExtension ext30 = stubExtension(30);
+
+        // supply in reverse order — saver must still store them sorted 
ascending
+        FileProjectSaver s1 = new FileProjectSaver(List.of(ext30, ext20, 
ext10));
+        assertEquals(List.of(ext10, ext20, ext30), s1.extensions);
+
+        // supply in random order
+        FileProjectSaver s2 = new FileProjectSaver(List.of(ext20, ext10, 
ext30));
+        assertEquals(List.of(ext10, ext20, ext30), s2.extensions);
+    }
+
+    private static ProjectExtension stubExtension(int order) {
+        return new ProjectExtension() {
+            @Override public LoaderDelegate createLoaderDelegate() { return 
null; }
+            @Override public SaverDelegate createSaverDelegate() { return new 
BaseSaverDelegate(); }
+            @Override public ConfigurationNodeVisitor<String> 
createNamingDelegate() { return null; }
+            @Override public int order() { return order; }
+        };
+    }
+
     /**
      * Regression test for CAY-1780: relative fragments (e.g. ./../) in target
      * file path must be resolved correctly.

Reply via email to