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

commit 25dbec28a7cc1aebe6da77fc4fa0663f260a768d
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sun Jul 26 17:11:44 2026 +0300

    EntitySorter refactoring:
    
    1. Created via a factory explicitly. I would rather we have all services as 
singletons in CoreModule
    2. No more lazy reindex. Just do it explicitly
    3. EntityResolver should not hold EntitySorter (circlula relationship). Let 
the DataNode own it instead
---
 .../java/org/apache/cayenne/access/DataDomain.java | 19 ++---
 .../java/org/apache/cayenne/access/DataNode.java   | 21 ++++++
 .../org/apache/cayenne/access/DbGenerator.java     |  3 +-
 .../apache/cayenne/access/jdbc/BatchAction.java    |  2 +-
 .../cayenne/ashwood/AshwoodEntitySorter.java       | 81 ++++++----------------
 .../ashwood/AshwoodEntitySorterFactory.java        | 36 ++++++++++
 .../ashwood/WeightedAshwoodEntitySorter.java       | 27 ++++----
 .../cayenne/configuration/runtime/CoreModule.java  |  9 +--
 .../configuration/runtime/DataDomainProvider.java  | 14 ++--
 .../org/apache/cayenne/map/EntityResolver.java     | 23 ------
 .../java/org/apache/cayenne/map/EntitySorter.java  | 10 +--
 .../apache/cayenne/map/EntitySorterFactory.java    | 33 +++++++++
 .../org/apache/cayenne/access/MockDataNode.java    | 11 +++
 .../cayenne/ashwood/AshwoodEntitySorterIT.java     |  3 +-
 .../ashwood/AshwoodEntitySorter_InheritanceIT.java |  3 +-
 .../AshwoodEntitySorter_RelationshipsIT.java       |  3 +-
 .../ashwood/WeightedAshwoodEntitySorterIT.java     |  3 +-
 .../org/apache/cayenne/unit/DbSchemaManager.java   |  4 +-
 18 files changed, 163 insertions(+), 142 deletions(-)

diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DataDomain.java 
b/cayenne/src/main/java/org/apache/cayenne/access/DataDomain.java
index 2e2cd7fc9..0ff8754d5 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataDomain.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataDomain.java
@@ -60,7 +60,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
  * transparently routed to an appropriate DataNode.
  */
 public class DataDomain implements DataChannel {
-    
+
     protected final String name;
     protected final TransactionManager transactionManager;
     protected final TransactionFactory transactionFactory;
@@ -109,13 +109,10 @@ public class DataDomain implements DataChannel {
         this.syncFilters = new CopyOnWriteArrayList<>();
         this.nodesByDataMapName = new ConcurrentHashMap<>();
         this.nodes = new ConcurrentHashMap<>();
-
-        refreshEntitySorter();
     }
 
     /**
-     * Checks that Domain is not stopped. Throws DomainStoppedException
-     * otherwise.
+     * Throws DomainStoppedException if the domain was previously shut down.
      *
      * @since 3.0
      */
@@ -208,7 +205,7 @@ public class DataDomain implements DataChannel {
 
     public void addDataMap(DataMap dataMap) {
         getEntityResolver().addDataMap(dataMap);
-        refreshEntitySorter();
+        entitySorter.reindex();
     }
 
     /**
@@ -240,7 +237,7 @@ public class DataDomain implements DataChannel {
         // remove from EntityResolver
         getEntityResolver().removeDataMap(map);
 
-        refreshEntitySorter();
+        entitySorter.reindex();
     }
 
     /**
@@ -252,6 +249,7 @@ public class DataDomain implements DataChannel {
         DataNode removed = nodes.remove(nodeName);
         if (removed != null) {
             removed.setEntityResolver(null);
+            removed.setEntitySorter(null);
             nodesByDataMapName.values().removeIf(dataNode -> dataNode == 
removed);
         }
     }
@@ -279,6 +277,7 @@ public class DataDomain implements DataChannel {
         // add node to name->node map
         nodes.put(node.getName(), node);
         node.setEntityResolver(getEntityResolver());
+        node.setEntitySorter(entitySorter);
 
         // add node to "ent name->node" map
         for (DataMap map : node.getDataMaps()) {
@@ -450,12 +449,6 @@ public class DataDomain implements DataChannel {
         return queryCache;
     }
 
-    void refreshEntitySorter() {
-        if (entitySorter != null) {
-            entitySorter.setEntityResolver(getEntityResolver());
-        }
-    }
-
     /**
      * Returns an unmodifiable list of query filters registered with this 
DataDomain.
      * <p>
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java 
b/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java
index 36f0522b8..4072ecd5d 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java
@@ -32,6 +32,7 @@ import org.apache.cayenne.log.NoopSQLLogger;
 import org.apache.cayenne.log.SQLLogger;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.EntitySorter;
 import org.apache.cayenne.query.DeleteBatchQuery;
 import org.apache.cayenne.query.InsertBatchQuery;
 import org.apache.cayenne.query.Query;
@@ -61,6 +62,7 @@ public class DataNode {
     protected DbAdapter adapter;
     protected String dataSourceFactory;
     protected EntityResolver entityResolver;
+    protected EntitySorter entitySorter;
     protected SchemaUpdateStrategy schemaUpdateStrategy;
     protected Map<String, DataMap> dataMaps;
 
@@ -301,6 +303,25 @@ public class DataNode {
         this.entityResolver = entityResolver;
     }
 
+    /**
+     * Returns an EntitySorter that sorts the entities of this node based on 
their mutual dependencies.
+     *
+     * @since 5.0
+     */
+    public EntitySorter getEntitySorter() {
+        return entitySorter;
+    }
+
+    /**
+     * Sets EntitySorter. DataNode relies on an externally set sorter, so if 
the node is created outside of the
+     * DataDomain stack, a valid EntitySorter must be provided explicitly.
+     *
+     * @since 5.0
+     */
+    public void setEntitySorter(EntitySorter entitySorter) {
+        this.entitySorter = entitySorter;
+    }
+
     @Override
     public String toString() {
         return new ToStringBuilder(this).append("name", getName()).toString();
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DbGenerator.java 
b/cayenne/src/main/java/org/apache/cayenne/access/DbGenerator.java
index 074410c40..631bb2e47 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DbGenerator.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DbGenerator.java
@@ -535,8 +535,7 @@ public class DbGenerator {
 
                // sort table list
                if (tables.size() > 1) {
-                       EntitySorter sorter = new AshwoodEntitySorter();
-                       sorter.setEntityResolver(new 
EntityResolver(Collections.singleton(map)));
+                       EntitySorter sorter = new AshwoodEntitySorter(new 
EntityResolver(Collections.singleton(map)));
                        sorter.sortDbEntities(tables, false);
                }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java 
b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
index 1f21eeb11..fd4eba604 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
@@ -106,7 +106,7 @@ public class BatchAction extends BaseSQLAction {
         if (hasGeneratedKeys()) {
             // turn off batch mode if we generate keys but can't do so in a 
batch
             return supportsGeneratedKeys(true) &&
-                    
!dataNode.getEntityResolver().getEntitySorter().isReflexive(query.getDbEntity());
+                    
!dataNode.getEntitySorter().isReflexive(query.getDbEntity());
         }
 
         return true;
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/ashwood/AshwoodEntitySorter.java 
b/cayenne/src/main/java/org/apache/cayenne/ashwood/AshwoodEntitySorter.java
index 942f39b0a..7e206e83f 100644
--- a/cayenne/src/main/java/org/apache/cayenne/ashwood/AshwoodEntitySorter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/ashwood/AshwoodEntitySorter.java
@@ -48,6 +48,7 @@ import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.function.Function;
 
 /**
@@ -59,56 +60,32 @@ import java.util.function.Function;
  */
 public class AshwoodEntitySorter implements EntitySorter {
 
-       protected EntityResolver entityResolver;
-       protected Map<DbEntity, ComponentRecord> components;
-       protected Map<DbEntity, List<DbRelationship>> reflexiveDbEntities;
+       protected final EntityResolver entityResolver;
+
+       protected volatile Map<DbEntity, ComponentRecord> components;
+       protected volatile Map<DbEntity, List<DbRelationship>> 
reflexiveDbEntities;
 
        protected Comparator<DbEntity> dbEntityComparator;
        protected Comparator<ObjEntity> objEntityComparator;
 
-       private volatile boolean dirty;
-
-       public AshwoodEntitySorter() {
-               dbEntityComparator = new DbEntityComparator();
-               objEntityComparator = new ObjEntityComparator();
-               dirty = true;
-       }
+       public AshwoodEntitySorter(EntityResolver entityResolver) {
+               this.entityResolver = Objects.requireNonNull(entityResolver);
+               this.dbEntityComparator = new DbEntityComparator();
+               this.objEntityComparator = new ObjEntityComparator();
 
-       /**
-        * Reindexes internal sorter in a thread-safe manner.
-        */
-       protected void indexSorter() {
-
-               // correct double check locking per Joshua Bloch
-               // 
http://java.sun.com/developer/technicalArticles/Interviews/bloch_effective_08_qa.html
-               // (maybe we should use something like CountDownLatch or a 
Cyclic
-               // barrier
-               // instead?)
-
-               boolean localDirty = dirty;
-               if (localDirty) {
-                       synchronized (this) {
-                               localDirty = dirty;
-                               if (localDirty) {
-                                       doIndexSorter();
-                                       dirty = false;
-                               }
-                       }
-               }
+               reindex();
        }
 
        /**
-        * Reindexes internal sorter without synchronization.
+        * @since 5.0
         */
-       protected void doIndexSorter() {
-
+       @Override
+       public synchronized void reindex() {
                Map<DbEntity, List<DbRelationship>> reflexiveDbEntities = new 
HashMap<>();
                Digraph<DbEntity, List<DbAttribute>> referentialDigraph = new 
MapDigraph<>();
 
-               if (entityResolver != null) {
-                       for (DbEntity entity : entityResolver.getDbEntities()) {
-                               referentialDigraph.addVertex(entity);
-                       }
+               for (DbEntity entity : entityResolver.getDbEntities()) {
+                       referentialDigraph.addVertex(entity);
                }
 
                for (DbEntity destination : entityResolver.getDbEntities()) {
@@ -164,43 +141,30 @@ public class AshwoodEntitySorter implements EntitySorter {
                this.components = components;
        }
 
-       /**
-        * @since 3.1
-        */
-       @Override
-       public void setEntityResolver(EntityResolver entityResolver) {
-               this.entityResolver = entityResolver;
-               this.entityResolver.setEntitySorter(this);
-               this.dirty = true;
-       }
-
        @Override
        public void sortDbEntities(List<DbEntity> dbEntities, boolean 
deleteOrder) {
-               indexSorter();
                dbEntities.sort(getDbEntityComparator(deleteOrder));
        }
 
        @Override
        public void sortObjEntities(List<ObjEntity> objEntities, boolean 
deleteOrder) {
-               indexSorter();
                objEntities.sort(getObjEntityComparator(deleteOrder));
        }
 
        @SuppressWarnings("unchecked")
        @Override
        public void sortObjectsForEntity(ObjEntity objEntity, List<?> objects, 
boolean deleteOrder) {
-               if(objects == null || objects.size() == 0) {
+               if(objects == null || objects.isEmpty()) {
                        return;
                }
 
-               indexSorter();
                DbEntity dbEntity = objEntity.getDbEntity();
                // if no sorting is required
                if (!isReflexive(dbEntity)) {
                        return;
                }
 
-               Object probe = objects.get(0);
+               Object probe = objects.getFirst();
                if (probe instanceof DbRowOp) {
                        sortObjectsForEntity(objEntity, (List<DbRowOp>) 
objects, deleteOrder, DbRowOp::getObject);
                } else if(probe instanceof Persistent) {
@@ -220,7 +184,7 @@ public class AshwoodEntitySorter implements EntitySorter {
        }
 
        protected <E> Digraph<E, Boolean> buildDigraph(ObjEntity objEntity, 
List<E> objects, Function<E, Persistent> converter) {
-               EntityResolver resolver = 
converter.apply(objects.get(0)).getObjectContext().getEntityResolver();
+               EntityResolver resolver = 
converter.apply(objects.getFirst()).getObjectContext().getEntityResolver();
                ClassDescriptor descriptor = 
resolver.getClassDescriptor(objEntity.getName());
                String[] reflexiveRelNames = 
getReflexiveRelationshipsNames(objEntity);
 
@@ -307,7 +271,7 @@ public class AshwoodEntitySorter implements EntitySorter {
 
        protected Persistent findReflexiveMaster(Persistent object, 
ObjRelationship toOneRel, String targetEntityName) {
 
-               DbRelationship finalRel = toOneRel.getDbRelationships().get(0);
+               DbRelationship finalRel = 
toOneRel.getDbRelationships().getFirst();
                ObjectContext context = object.getObjectContext();
 
                // find committed snapshot - so we can't fetch from the context 
as it will return dirty snapshot;
@@ -321,11 +285,11 @@ public class AshwoodEntitySorter implements EntitySorter {
                ObjectIdQuery query = new ObjectIdQuery(object.getObjectId(), 
true, ObjectIdQuery.CACHE);
                QueryResponse response = context.getParent().onQuery(null, 
query);
                List<?> result = response.firstList();
-               if (result == null || result.size() == 0) {
+               if (result == null || result.isEmpty()) {
                        return null;
                }
 
-               DataRow snapshot = (DataRow) result.get(0);
+               DataRow snapshot = (DataRow) result.getFirst();
 
                ObjectId id = snapshot.createTargetObjectId(targetEntityName, 
finalRel);
 
@@ -336,13 +300,11 @@ public class AshwoodEntitySorter implements EntitySorter {
 
        @Override
        public Comparator<DbEntity> getDbEntityComparator() {
-               indexSorter();
                return dbEntityComparator;
        }
 
        @Override
        public Comparator<ObjEntity> getObjEntityComparator() {
-               indexSorter();
                return objEntityComparator;
        }
 
@@ -364,7 +326,6 @@ public class AshwoodEntitySorter implements EntitySorter {
 
        @Override
        public boolean isReflexive(DbEntity metadata) {
-               indexSorter();
                return reflexiveDbEntities.containsKey(metadata);
        }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/ashwood/AshwoodEntitySorterFactory.java
 
b/cayenne/src/main/java/org/apache/cayenne/ashwood/AshwoodEntitySorterFactory.java
new file mode 100644
index 000000000..fba1da39f
--- /dev/null
+++ 
b/cayenne/src/main/java/org/apache/cayenne/ashwood/AshwoodEntitySorterFactory.java
@@ -0,0 +1,36 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.
+ ****************************************************************/
+package org.apache.cayenne.ashwood;
+
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.EntitySorter;
+import org.apache.cayenne.map.EntitySorterFactory;
+
+/**
+ * A factory of {@link AshwoodEntitySorter}.
+ *
+ * @since 5.0
+ */
+public class AshwoodEntitySorterFactory implements EntitySorterFactory {
+
+    @Override
+    public EntitySorter createEntitySorter(EntityResolver entityResolver) {
+        return new AshwoodEntitySorter(entityResolver);
+    }
+}
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorter.java
 
b/cayenne/src/main/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorter.java
index 855983e76..d435c2753 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorter.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorter.java
@@ -18,14 +18,14 @@
  ****************************************************************/
 package org.apache.cayenne.ashwood;
 
-import java.util.Collections;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.ObjEntity;
+
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.ObjEntity;
-
 /**
  * EntitySorter that takes into account entity "weights", and otherwise 
delegating to
  * another (topological) sorter.
@@ -34,20 +34,23 @@ import org.apache.cayenne.map.ObjEntity;
  */
 public class WeightedAshwoodEntitySorter extends AshwoodEntitySorter {
 
-    private Comparator<DbEntity> weightedDbEntityComparator;
-    private Comparator<ObjEntity> weightedObjEntityComparator;
+    private final Comparator<DbEntity> weightedDbEntityComparator;
+    private final Comparator<ObjEntity> weightedObjEntityComparator;
+
+    protected volatile Map<DbEntity, Integer> entityWeights;
 
-    protected Map<DbEntity, Integer> entityWeights;
+    public WeightedAshwoodEntitySorter(EntityResolver entityResolver) {
+        super(entityResolver);
 
-    public WeightedAshwoodEntitySorter() {
+        // the comparators are stateless, reading the sorter fields only when 
actually comparing, so creating them
+        // after super() (i.e. after the initial indexing) is safe
         this.weightedDbEntityComparator = new WeightedDbEntityComparator();
         this.weightedObjEntityComparator = new WeightedObjEntityComparator();
-        this.entityWeights = Collections.emptyMap();
     }
 
     @Override
-    protected void doIndexSorter() {
-        super.doIndexSorter();
+    public synchronized void reindex() {
+        super.reindex();
 
         entityWeights = new HashMap<>();
 
@@ -66,7 +69,6 @@ public class WeightedAshwoodEntitySorter extends 
AshwoodEntitySorter {
         }
     }
 
-    @SuppressWarnings("unchecked")
     @Override
     protected Comparator<DbEntity> getDbEntityComparator(boolean 
dependantFirst) {
         Comparator<DbEntity> c = weightedDbEntityComparator;
@@ -76,7 +78,6 @@ public class WeightedAshwoodEntitySorter extends 
AshwoodEntitySorter {
         return c;
     }
 
-    @SuppressWarnings("unchecked")
     @Override
     protected Comparator<ObjEntity> getObjEntityComparator(boolean 
dependantFirst) {
         Comparator<ObjEntity> c = weightedObjEntityComparator;
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java
 
b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java
index dc785a235..185348de9 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java
@@ -50,7 +50,7 @@ import 
org.apache.cayenne.access.translator.sqltemplate.DefaultSQLTemplateTransl
 import 
org.apache.cayenne.access.translator.sqltemplate.DefaultTemplateContextFactory;
 import org.apache.cayenne.access.translator.sqltemplate.TemplateContextFactory;
 import org.apache.cayenne.access.types.*;
-import org.apache.cayenne.ashwood.AshwoodEntitySorter;
+import org.apache.cayenne.ashwood.AshwoodEntitySorterFactory;
 import org.apache.cayenne.cache.MapQueryCacheProvider;
 import org.apache.cayenne.cache.QueryCache;
 import org.apache.cayenne.commitlog.CommitLogFilter;
@@ -126,7 +126,7 @@ import org.apache.cayenne.event.EventManagerProvider;
 import org.apache.cayenne.event.NoopEventBridgeProvider;
 import org.apache.cayenne.log.SQLLogger;
 import org.apache.cayenne.log.Slf4jSQLLogger;
-import org.apache.cayenne.map.EntitySorter;
+import org.apache.cayenne.map.EntitySorterFactory;
 import 
org.apache.cayenne.reflect.generic.DefaultValueComparisonStrategyFactory;
 import org.apache.cayenne.reflect.generic.ValueComparisonStrategyFactory;
 import org.apache.cayenne.resource.ClassLoaderResourceLocator;
@@ -462,10 +462,7 @@ public class CoreModule implements Module {
 
         // a default DBAdapterFactory used to load custom and automatic 
DbAdapters
         binder.bind(DbAdapterFactory.class).to(DefaultDbAdapterFactory.class);
-
-        // binding AshwoodEntitySorter without scope, as this is a stateful 
object and is
-        // configured by the owning domain
-        
binder.bind(EntitySorter.class).to(AshwoodEntitySorter.class).withoutScope();
+        
binder.bind(EntitySorterFactory.class).to(AshwoodEntitySorterFactory.class);
 
         binder.bind(Key.get(BatchTranslator.class, 
BatchTranslator.INSERT)).to(InsertBatchTranslator.class);
         binder.bind(Key.get(BatchTranslator.class, 
BatchTranslator.UPDATE)).to(UpdateBatchTranslator.class);
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DataDomainProvider.java
 
b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DataDomainProvider.java
index 6c5758b2d..31f4b4d0f 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DataDomainProvider.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DataDomainProvider.java
@@ -45,6 +45,7 @@ import org.apache.cayenne.event.EventManager;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.EntitySorter;
+import org.apache.cayenne.map.EntitySorterFactory;
 import org.apache.cayenne.reflect.generic.ValueComparisonStrategyFactory;
 import org.apache.cayenne.resource.Resource;
 import org.apache.cayenne.resource.ResourceLocator;
@@ -128,7 +129,7 @@ public class DataDomainProvider implements 
Provider<DataDomain> {
     protected EventManager eventManager;
 
     @Inject
-    protected EntitySorter entitySorter;
+    protected EntitySorterFactory entitySorterFactory;
 
     @Override
     public DataDomain get() throws ConfigurationException {
@@ -147,6 +148,8 @@ public class DataDomainProvider implements 
Provider<DataDomain> {
     protected DataDomain createAndInitDataDomain() throws Exception {
 
         DataChannelDescriptor descriptor = loadDescriptor();
+        EntityResolver entityResolver = createEntityResolver(descriptor);
+        EntitySorter entitySorter = 
entitySorterFactory.createEntitySorter(entityResolver);
 
         DataDomain domain = new DataDomain(
                 descriptor.getName(),
@@ -157,11 +160,10 @@ public class DataDomainProvider implements 
Provider<DataDomain> {
                 eventManager,
                 entitySorter,
                 new NestedQueryCache(queryCache),
-                createEntityResolver(descriptor));
+                entityResolver);
 
         
domain.setMaxIdQualifierSize(runtimeProperties.getInt(Constants.MAX_ID_QUALIFIER_SIZE_PROPERTY,
 -1));
 
-
         Map<String, String> properties = descriptor.getProperties();
 
         boolean sharedCache = 
"true".equals(properties.getOrDefault(SHARED_CACHE_ENABLED_PROPERTY, 
SHARED_CACHE_ENABLED_DEFAULT));
@@ -213,12 +215,6 @@ public class DataDomainProvider implements 
Provider<DataDomain> {
         return domain;
     }
 
-    /**
-     * Creates an EntityResolver fully configured off the descriptor, so that 
the DataDomain can take it as a
-     * constructor argument.
-     *
-     * @since 5.0
-     */
     protected EntityResolver createEntityResolver(DataChannelDescriptor 
descriptor) {
 
         EntityResolver entityResolver = new EntityResolver();
diff --git a/cayenne/src/main/java/org/apache/cayenne/map/EntityResolver.java 
b/cayenne/src/main/java/org/apache/cayenne/map/EntityResolver.java
index 89edc83bf..f203e1427 100644
--- a/cayenne/src/main/java/org/apache/cayenne/map/EntityResolver.java
+++ b/cayenne/src/main/java/org/apache/cayenne/map/EntityResolver.java
@@ -93,17 +93,8 @@ public class EntityResolver implements MappingNamespace, 
Serializable {
     protected transient LifecycleCallbackRegistry callbackRegistry;
 
     protected transient ValueObjectTypeRegistry valueObjectTypeRegistry;
-
-    /**
-     * @since 4.2
-     */
     protected transient ValueComparisonStrategyFactory 
valueComparisonStrategyFactory;
 
-    /**
-     * @since 4.2
-     */
-    protected transient EntitySorter entitySorter;
-
     /**
      * @since 5.0
      */
@@ -547,20 +538,6 @@ public class EntityResolver implements MappingNamespace, 
Serializable {
         this.valueComparisonStrategyFactory = valueComparisonStrategyFactory;
     }
 
-    /**
-     * @since 4.2
-     */
-    public void setEntitySorter(EntitySorter entitySorter) {
-        this.entitySorter = entitySorter;
-    }
-
-    /**
-     * @since 4.2
-     */
-    public EntitySorter getEntitySorter() {
-        return entitySorter;
-    }
-
     /**
      * @since 5.0
      */
diff --git a/cayenne/src/main/java/org/apache/cayenne/map/EntitySorter.java 
b/cayenne/src/main/java/org/apache/cayenne/map/EntitySorter.java
index c29a33bda..6d2b16131 100644
--- a/cayenne/src/main/java/org/apache/cayenne/map/EntitySorter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/map/EntitySorter.java
@@ -30,12 +30,12 @@ import java.util.List;
 public interface EntitySorter {
 
     /**
-     * Sets EntityResolver for this sorter. All entities present in the 
resolver will be
-     * used to determine sort ordering.
-     * 
-     * @since 3.1
+     * Rebuilds the internal index off the current state of the sorter's 
EntityResolver. Called when the set of
+     * entities changes.
+     *
+     * @since 5.0
      */
-    void setEntityResolver(EntityResolver resolver);
+    void reindex();
 
     /**
      * Sorts a list of DbEntities.
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/map/EntitySorterFactory.java 
b/cayenne/src/main/java/org/apache/cayenne/map/EntitySorterFactory.java
new file mode 100644
index 000000000..6e3b38f00
--- /dev/null
+++ b/cayenne/src/main/java/org/apache/cayenne/map/EntitySorterFactory.java
@@ -0,0 +1,33 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://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.
+ ****************************************************************/
+package org.apache.cayenne.map;
+
+/**
+ * Creates {@link EntitySorter} instances. As a sorter is a stateful object 
indexed off a specific set of entities, it
+ * can not be shared.
+ *
+ * @since 5.0
+ */
+public interface EntitySorterFactory {
+
+    /**
+     * Creates an EntitySorter that sorts the entities of the provided 
EntityResolver.
+     */
+    EntitySorter createEntitySorter(EntityResolver entityResolver);
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/access/MockDataNode.java 
b/cayenne/src/test/java/org/apache/cayenne/access/MockDataNode.java
index db5522043..26d3b614d 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/MockDataNode.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/MockDataNode.java
@@ -30,6 +30,7 @@ import javax.sql.DataSource;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.EntitySorter;
 import org.apache.cayenne.query.Query;
 
 public class MockDataNode extends DataNode {
@@ -136,6 +137,11 @@ public class MockDataNode extends DataNode {
         return node.getEntityResolver();
     }
 
+    @Override
+    public EntitySorter getEntitySorter() {
+        return node.getEntitySorter();
+    }
+
     @Override
     public String getName() {
         return node.getName();
@@ -171,6 +177,11 @@ public class MockDataNode extends DataNode {
         node.setEntityResolver(entityResolver);
     }
 
+    @Override
+    public void setEntitySorter(EntitySorter entitySorter) {
+        node.setEntitySorter(entitySorter);
+    }
+
     @Override
     public void setName(String name) {
         node.setName(name);
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/ashwood/AshwoodEntitySorterIT.java 
b/cayenne/src/test/java/org/apache/cayenne/ashwood/AshwoodEntitySorterIT.java
index 5662ca992..5d73410a2 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/ashwood/AshwoodEntitySorterIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/ashwood/AshwoodEntitySorterIT.java
@@ -50,8 +50,7 @@ public class AshwoodEntitySorterIT {
        public void before() {
 
                this.resolver = env.context().getEntityResolver();
-               this.sorter = new AshwoodEntitySorter();
-               sorter.setEntityResolver(resolver);
+               this.sorter = new AshwoodEntitySorter(resolver);
 
                this.artist = resolver.getDbEntity("ARTIST");
                this.artistExhibit = resolver.getDbEntity("ARTIST_EXHIBIT");
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/ashwood/AshwoodEntitySorter_InheritanceIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/ashwood/AshwoodEntitySorter_InheritanceIT.java
index e8164b391..e447cf3ab 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/ashwood/AshwoodEntitySorter_InheritanceIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/ashwood/AshwoodEntitySorter_InheritanceIT.java
@@ -49,8 +49,7 @@ public class AshwoodEntitySorter_InheritanceIT extends 
PeopleTestBase {
                context = env.context();
 
                this.resolver = context.getEntityResolver();
-               this.sorter = new AshwoodEntitySorter();
-               sorter.setEntityResolver(resolver);
+               this.sorter = new AshwoodEntitySorter(resolver);
 
                this.address = resolver.getDbEntity("ADDRESS");
                this.clientCompany = resolver.getDbEntity("CLIENT_COMPANY");
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/ashwood/AshwoodEntitySorter_RelationshipsIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/ashwood/AshwoodEntitySorter_RelationshipsIT.java
index 489186881..44d32ca2f 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/ashwood/AshwoodEntitySorter_RelationshipsIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/ashwood/AshwoodEntitySorter_RelationshipsIT.java
@@ -46,8 +46,7 @@ public class AshwoodEntitySorter_RelationshipsIT {
 
        @BeforeEach
        public void setUp() throws Exception {
-               this.sorter = new AshwoodEntitySorter();
-               sorter.setEntityResolver(env.context().getEntityResolver());
+               this.sorter = new 
AshwoodEntitySorter(env.context().getEntityResolver());
 
                tRelationshipHelper = env.table("RELATIONSHIP_HELPER", 
"RELATIONSHIP_HELPER_ID", "NAME");
 
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorterIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorterIT.java
index b8c704edf..83b855e4d 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorterIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/ashwood/WeightedAshwoodEntitySorterIT.java
@@ -54,8 +54,7 @@ public class WeightedAshwoodEntitySorterIT {
 
         List<DbEntity> e2 = Arrays.asList(resolver.getDbEntity("SORT_DEP"), 
resolver.getDbEntity("SORT_ROOT"));
 
-        WeightedAshwoodEntitySorter sorter = new WeightedAshwoodEntitySorter();
-        sorter.setEntityResolver(resolver);
+        WeightedAshwoodEntitySorter sorter = new 
WeightedAshwoodEntitySorter(resolver);
 
         sorter.sortDbEntities(e1, false);
         assertEquals(eSorted, e1);
diff --git a/cayenne/src/test/java/org/apache/cayenne/unit/DbSchemaManager.java 
b/cayenne/src/test/java/org/apache/cayenne/unit/DbSchemaManager.java
index ef4b8cc97..314e42603 100644
--- a/cayenne/src/test/java/org/apache/cayenne/unit/DbSchemaManager.java
+++ b/cayenne/src/test/java/org/apache/cayenne/unit/DbSchemaManager.java
@@ -89,8 +89,8 @@ public class DbSchemaManager {
             filterDataMap(map);
         }
 
-        // TODO: suspect
-        domain.getEntitySorter().setEntityResolver(domain.getEntityResolver());
+        // "filterDataMap" above mutated the maps, so the sorter index is stale
+        domain.getEntitySorter().reindex();
 
         this.dataMapsInSchemaSetupOrder = sortDataMapsInSchemaSetupOrder();
     }

Reply via email to