You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2026/09/12 20:30:59 UTC

(cayenne) 01/02: CAY-3025 ObjectContext to require/return Persistent for all object ops

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 a72e595dfe2f892837988eb5e01b5bbb87ccc6fd
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sat Sep 12 16:02:06 2026 -0400

    CAY-3025 ObjectContext to require/return Persistent for all object ops
---
 .../cayenne/DataChannelSyncCallbackAction.java     | 10 +--
 .../java/org/apache/cayenne/ObjectContext.java     | 26 ++++----
 .../org/apache/cayenne/ObjectContextStateLog.java  | 26 ++++----
 .../org/apache/cayenne/access/DataContext.java     | 74 ++++++++++------------
 .../cayenne/access/DataContextMergeHandler.java    |  4 +-
 .../access/JoinedIdParentAttachmentStrategy.java   |  6 +-
 .../org/apache/cayenne/access/ObjectStore.java     | 25 ++++----
 .../cayenne/access/ObjectStoreGraphDiff.java       |  2 +-
 .../cayenne/access/flush/DbRowOpFactory.java       |  2 +-
 .../flush/operation/GraphBasedDbRowOpSorter.java   |  2 +-
 .../cayenne/ashwood/AshwoodEntitySorter.java       |  2 +-
 .../org/apache/cayenne/graph/ChildDiffLoader.java  |  2 +-
 .../org/apache/cayenne/graph/GraphManager.java     | 13 ++--
 .../apache/cayenne/util/ShallowMergeOperation.java |  2 +-
 .../apache/cayenne/ContextStateRecorderTest.java   |  3 +-
 .../access/DataContextInvalidateObjectsIT.java     |  6 +-
 .../cayenne/dba/ConcurrentPkGeneratorIT.java       |  6 +-
 17 files changed, 106 insertions(+), 105 deletions(-)

diff --git a/cayenne/src/main/java/org/apache/cayenne/DataChannelSyncCallbackAction.java b/cayenne/src/main/java/org/apache/cayenne/DataChannelSyncCallbackAction.java
index 2120123e2..3315e2469 100644
--- a/cayenne/src/main/java/org/apache/cayenne/DataChannelSyncCallbackAction.java
+++ b/cayenne/src/main/java/org/apache/cayenne/DataChannelSyncCallbackAction.java
@@ -42,7 +42,7 @@ public abstract class DataChannelSyncCallbackAction implements GraphChangeHandle
 
     public static DataChannelSyncCallbackAction getCallbackAction(
             LifecycleCallbackRegistry callbackRegistry,
-            GraphManager graphManager,
+            GraphManager<Persistent> graphManager,
             GraphDiff changes,
             int syncType) {
 
@@ -62,10 +62,10 @@ public abstract class DataChannelSyncCallbackAction implements GraphChangeHandle
     Collection<Object> persisted;
     Collection<Object> removed;
     private Map<Object, Op> seenIds;
-    private GraphManager graphManager;
+    private GraphManager<Persistent> graphManager;
 
     DataChannelSyncCallbackAction(LifecycleCallbackRegistry callbackRegistry,
-            GraphManager graphManager, GraphDiff changes) {
+            GraphManager<Persistent> graphManager, GraphDiff changes) {
 
         this.callbackRegistry = callbackRegistry;
         this.graphManager = graphManager;
@@ -172,7 +172,7 @@ public abstract class DataChannelSyncCallbackAction implements GraphChangeHandle
     static class FlushCallbackAction extends DataChannelSyncCallbackAction {
 
         FlushCallbackAction(LifecycleCallbackRegistry callbackRegistry,
-                GraphManager graphManager, GraphDiff changes) {
+                GraphManager<Persistent> graphManager, GraphDiff changes) {
             super(callbackRegistry, graphManager, changes);
         }
 
@@ -202,7 +202,7 @@ public abstract class DataChannelSyncCallbackAction implements GraphChangeHandle
     static class RollbackCallbackAction extends DataChannelSyncCallbackAction {
 
         RollbackCallbackAction(LifecycleCallbackRegistry callbackRegistry,
-                GraphManager graphManager, GraphDiff changes) {
+                GraphManager<Persistent> graphManager, GraphDiff changes) {
             super(callbackRegistry, graphManager, changes);
         }
 
diff --git a/cayenne/src/main/java/org/apache/cayenne/ObjectContext.java b/cayenne/src/main/java/org/apache/cayenne/ObjectContext.java
index 814f93952..cb8634f13 100644
--- a/cayenne/src/main/java/org/apache/cayenne/ObjectContext.java
+++ b/cayenne/src/main/java/org/apache/cayenne/ObjectContext.java
@@ -53,24 +53,24 @@ public interface ObjectContext {
      * Returns a collection of objects that are registered with this
      * ObjectContext and have a state PersistenceState.NEW
      */
-    Collection<?> newObjects();
+    Collection<Persistent> newObjects();
 
     /**
      * Returns a collection of objects that are registered with this
      * ObjectContext and have a state PersistenceState.DELETED
      */
-    Collection<?> deletedObjects();
+    Collection<Persistent> deletedObjects();
 
     /**
      * Returns a collection of objects that are registered with this
      * ObjectContext and have a state PersistenceState.MODIFIED
      */
-    Collection<?> modifiedObjects();
+    Collection<Persistent> modifiedObjects();
 
     /**
      * Returns a collection of MODIFIED, DELETED or NEW objects.
      */
-    Collection<?> uncommittedObjects();
+    Collection<Persistent> uncommittedObjects();
 
     /**
      * Returns a local copy of 'objectFromAnotherContext' object. "Local" means
@@ -185,7 +185,7 @@ public interface ObjectContext {
      * Creates a new persistent object of a given class scheduled to be inserted
      * to the database on next commit.
      */
-    <T> T newObject(Class<T> persistentClass);
+    <T extends Persistent> T newObject(Class<T> persistentClass);
 
     /**
      * Registers a transient object with the context. The difference with
@@ -197,7 +197,7 @@ public interface ObjectContext {
      *            new object that needs to be made persistent.
      * @since 3.0
      */
-    void registerNewObject(Object object);
+    void registerNewObject(Persistent object);
 
     /**
      * Schedules deletion of a persistent object.
@@ -206,7 +206,7 @@ public interface ObjectContext {
      *             if a {@link org.apache.cayenne.map.DeleteRule#DENY} delete
      *             rule is applicable for object deletion.
      */
-    default void deleteObject(Object object) throws DeleteDenyException {
+    default void deleteObject(Persistent object) throws DeleteDenyException {
         deleteObjects(object);
     }
 
@@ -217,10 +217,10 @@ public interface ObjectContext {
      *             if a {@link org.apache.cayenne.map.DeleteRule#DENY} delete
      *             rule is applicable for object deletion.
      */
-    default void deleteObjects(Collection<?> objects) throws DeleteDenyException {
+    default void deleteObjects(Collection<? extends Persistent> objects) throws DeleteDenyException {
         // toArray() copies, which also guards against ConcurrentModificationException when delete rules modify the
         // source collection (e.g. a to-many list) mid-iteration
-        deleteObjects(objects.toArray());
+        deleteObjects(objects.toArray(new Persistent[0]));
     }
 
     /**
@@ -231,7 +231,7 @@ public interface ObjectContext {
      *             rule is applicable for object deletion.
      * @since 3.1
      */
-    <T> void deleteObjects(T... objects) throws DeleteDenyException;
+    <T extends Persistent> void deleteObjects(T... objects) throws DeleteDenyException;
 
     /**
      * A callback method that child Persistent objects are expected to call
@@ -365,7 +365,7 @@ public interface ObjectContext {
      * Returns GraphManager that manages object graph associated with this
      * context.
      */
-    GraphManager getGraphManager();
+    GraphManager<Persistent> getGraphManager();
 
     /**
      * Returns <code>true</code> if there are any modified, deleted or new
@@ -382,7 +382,7 @@ public interface ObjectContext {
      * and change object's state to HOLLOW. On the next access to this object,
      * the object will be refetched.
      */
-    void invalidateObjects(Collection<?> objects);
+    void invalidateObjects(Collection<? extends Persistent> objects);
 
     /**
      * Invalidates one or more persistent objects. Same as
@@ -392,7 +392,7 @@ public interface ObjectContext {
      * 
      * @since 3.1
      */
-    <T> void invalidateObjects(T... objects);
+    <T extends Persistent> void invalidateObjects(T... objects);
 
     /**
      * Returns a user-defined property previously set via 'setUserProperty'.
diff --git a/cayenne/src/main/java/org/apache/cayenne/ObjectContextStateLog.java b/cayenne/src/main/java/org/apache/cayenne/ObjectContextStateLog.java
index 4a501c657..83d2d1599 100644
--- a/cayenne/src/main/java/org/apache/cayenne/ObjectContextStateLog.java
+++ b/cayenne/src/main/java/org/apache/cayenne/ObjectContextStateLog.java
@@ -38,9 +38,9 @@ import java.util.Set;
 class ObjectContextStateLog implements GraphChangeHandler {
 
     Set<Object> dirtyIds;
-    GraphManager graphManager;
+    GraphManager<Persistent> graphManager;
 
-    ObjectContextStateLog(GraphManager graphManager) {
+    ObjectContextStateLog(GraphManager<Persistent> graphManager) {
         this.dirtyIds = new HashSet<>();
         this.graphManager = graphManager;
     }
@@ -59,14 +59,14 @@ class ObjectContextStateLog implements GraphChangeHandler {
         List<Object> deletedIds = new ArrayList<>();
         
         for (Object id : dirtyIds) {
-            Object node = graphManager.getNode(id);
-            if (node instanceof Persistent persistentNode) {
-                switch (persistentNode.getPersistenceState()) {
+            Persistent node = graphManager.getNode(id);
+            if (node != null) {
+                switch (node.getPersistenceState()) {
                     case PersistenceState.MODIFIED, PersistenceState.NEW ->
-                            persistentNode.setPersistenceState(PersistenceState.COMMITTED);
+                            node.setPersistenceState(PersistenceState.COMMITTED);
                     case PersistenceState.DELETED -> {
                         deletedIds.add(id);
-                        persistentNode.setPersistenceState(PersistenceState.TRANSIENT);
+                        node.setPersistenceState(PersistenceState.TRANSIENT);
                     }
                 }
             }
@@ -84,13 +84,13 @@ class ObjectContextStateLog implements GraphChangeHandler {
 
     void graphReverted() {
         for (Object id : dirtyIds) {
-            Object node = graphManager.getNode(id);
-            if (node instanceof Persistent persistentNode) {
-                switch (persistentNode.getPersistenceState()) {
+            Persistent node = graphManager.getNode(id);
+            if (node != null) {
+                switch (node.getPersistenceState()) {
                     case PersistenceState.MODIFIED, PersistenceState.DELETED ->
-                            persistentNode.setPersistenceState(PersistenceState.COMMITTED);
+                            node.setPersistenceState(PersistenceState.COMMITTED);
                     case PersistenceState.NEW ->
-                            persistentNode.setPersistenceState(PersistenceState.TRANSIENT);
+                            node.setPersistenceState(PersistenceState.TRANSIENT);
                 }
             }
         }
@@ -127,7 +127,7 @@ class ObjectContextStateLog implements GraphChangeHandler {
         int size = dirtyIds.size();
         Collection<Object> objects = new ArrayList<>(size > 50 ? size / 2 : size);
         for (Object id : dirtyIds) {
-            Persistent o = (Persistent) graphManager.getNode(id);
+            Persistent o = graphManager.getNode(id);
 
             if (o.getPersistenceState() == state) {
                 objects.add(o);
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java b/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java
index bdb48a8ed..6f4c5932c 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java
@@ -253,7 +253,7 @@ public class DataContext implements ObjectContext {
      * have a state PersistenceState.NEW
      */
     @Override
-    public Collection<?> newObjects() {
+    public Collection<Persistent> newObjects() {
         return getObjectStore().objectsInState(PersistenceState.NEW);
     }
 
@@ -262,7 +262,7 @@ public class DataContext implements ObjectContext {
      * have a state {@link PersistenceState#DELETED}
      */
     @Override
-    public Collection<?> deletedObjects() {
+    public Collection<Persistent> deletedObjects() {
         return getObjectStore().objectsInState(PersistenceState.DELETED);
     }
 
@@ -270,15 +270,15 @@ public class DataContext implements ObjectContext {
      * @since 3.1
      */
     @Override
-    public <T> void deleteObjects(T... objects) throws DeleteDenyException {
+    public <T extends Persistent> void deleteObjects(T... objects) throws DeleteDenyException {
         if (objects == null || objects.length == 0) {
             return;
         }
 
         DataContextDeleteAction action = new DataContextDeleteAction(this);
 
-        for (Object object : objects) {
-            action.performDelete((Persistent) object);
+        for (Persistent object : objects) {
+            action.performDelete(object);
         }
     }
 
@@ -287,7 +287,7 @@ public class DataContext implements ObjectContext {
      * have a state {@link PersistenceState#MODIFIED}
      */
     @Override
-    public Collection<?> modifiedObjects() {
+    public Collection<Persistent> modifiedObjects() {
         return getObjectStore().objectsInState(PersistenceState.MODIFIED);
     }
 
@@ -297,18 +297,18 @@ public class DataContext implements ObjectContext {
      * @since 1.2
      */
     @Override
-    public Collection<?> uncommittedObjects() {
+    public Collection<Persistent> uncommittedObjects() {
 
         int len = getObjectStore().registeredObjectsCount();
         if (len == 0) {
-            return Collections.EMPTY_LIST;
+            return Collections.emptyList();
         }
 
         // guess target collection size
-        Collection<Object> objects = new ArrayList<>(len > 100 ? len / 2 : len);
-        Iterator<?> it = getObjectStore().getObjectIterator();
+        Collection<Persistent> objects = new ArrayList<>(len > 100 ? len / 2 : len);
+        Iterator<Persistent> it = getObjectStore().getObjectIterator();
         while (it.hasNext()) {
-            Persistent object = (Persistent) it.next();
+            Persistent object = it.next();
             int state = object.getPersistenceState();
             if (state == PersistenceState.MODIFIED || state == PersistenceState.NEW
                     || state == PersistenceState.DELETED) {
@@ -366,8 +366,8 @@ public class DataContext implements ObjectContext {
         }
 
         for (ObjectId candidateId : inheritanceTree.polymorphicIds(id)) {
-            if (getGraphManager().getNode(candidateId) instanceof Persistent object
-                    && object.getPersistenceState() != PersistenceState.HOLLOW) {
+            Persistent object = getGraphManager().getNode(candidateId);
+            if (object != null && object.getPersistenceState() != PersistenceState.HOLLOW) {
                 return object;
             }
         }
@@ -531,7 +531,7 @@ public class DataContext implements ObjectContext {
      * @since 1.2
      */
     @Override
-    public <T> T newObject(Class<T> persistentClass) {
+    public <T extends Persistent> T newObject(Class<T> persistentClass) {
         if (persistentClass == null) {
             throw new NullPointerException("Null 'persistentClass'");
         }
@@ -541,7 +541,6 @@ public class DataContext implements ObjectContext {
             throw new IllegalArgumentException("Class is not mapped with Cayenne: " + persistentClass.getName());
         }
 
-
         return (T) newObject(entity.getName());
     }
 
@@ -584,36 +583,32 @@ public class DataContext implements ObjectContext {
      * Registers a transient object with the context, recursively registering
      * all transient persistent objects attached to this object via
      * relationships.
-     * <p>
-     * <i>Note that since 3.0 this method takes Object as an argument instead of a {@link Persistent}.</i>
      *
      * @param object new object that needs to be made persistent.
      */
     @Override
-    public void registerNewObject(Object object) {
+    public void registerNewObject(Persistent object) {
         if (object == null) {
             throw new NullPointerException("Can't register null object.");
         }
 
-        ObjEntity entity = getEntityResolver().getObjEntity((Persistent) object);
+        ObjEntity entity = getEntityResolver().getObjEntity(object);
         if (entity == null) {
             throw new IllegalArgumentException("Can't find ObjEntity for Persistent class: "
                     + object.getClass().getName() + ", class is likely not mapped.");
         }
 
-        Persistent persistent = (Persistent) object;
-
         // sanity check - maybe already registered
-        if (persistent.getObjectId() != null) {
-            if (persistent.getObjectContext() == this) {
+        if (object.getObjectId() != null) {
+            if (object.getObjectContext() == this) {
                 // already registered, just ignore
                 return;
-            } else if (persistent.getObjectContext() != null) {
+            } else if (object.getObjectContext() != null) {
                 throw new IllegalStateException("Persistent is already registered with another DataContext. "
                         + "Try using 'localObjects()' instead.");
             }
         } else {
-            persistent.setObjectId(ObjectId.of(entity.getName()));
+            object.setObjectId(ObjectId.of(entity.getName()));
         }
 
         ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(entity.getName());
@@ -629,27 +624,27 @@ public class DataContext implements ObjectContext {
         descriptor.visitProperties(new PropertyVisitor() {
 
             public boolean visitToMany(ToManyProperty property) {
-                property.injectValueHolder(persistent);
+                property.injectValueHolder(object);
 
-                Object value = property.readProperty(persistent);
+                Object value = property.readProperty(object);
                 Collection<?> collection = value instanceof Map<?, ?> map ? map.values() : (Collection<?>) value;
 
                 for (Object target : collection) {
                     if (target instanceof Persistent targetDO) {
                         registerNewObject(targetDO);
-                        getObjectStore().arcCreated(persistent.getObjectId(), targetDO.getObjectId(), new ArcId(property));
+                        getObjectStore().arcCreated(object.getObjectId(), targetDO.getObjectId(), new ArcId(property));
                     }
                 }
                 return true;
             }
 
             public boolean visitToOne(ToOneProperty property) {
-                Object target = property.readPropertyDirectly(persistent);
+                Object target = property.readPropertyDirectly(object);
 
                 if (target instanceof Persistent targetDO) {
                     // make sure it is registered
                     registerNewObject(targetDO);
-                    getObjectStore().arcCreated(persistent.getObjectId(), targetDO.getObjectId(), new ArcId(property));
+                    getObjectStore().arcCreated(object.getObjectId(), targetDO.getObjectId(), new ArcId(property));
                 }
                 return true;
             }
@@ -664,16 +659,14 @@ public class DataContext implements ObjectContext {
      * If ObjEntity qualifier is set, asks it to inject initial value to an object.
      * Also performs all Persistent initialization operations
      */
-    private void injectInitialValue(Object obj) {
+    private void injectInitialValue(Persistent object) {
         // must follow this exact order of property initialization per CAY-653,
         // i.e. have the id and the context in place BEFORE setPersistence is called
 
-        Persistent object = (Persistent) obj;
-
         object.setObjectContext(this);
         object.setPersistenceState(PersistenceState.NEW);
 
-        GraphManager graphManager = getGraphManager();
+        GraphManager<Persistent> graphManager = getGraphManager();
         synchronized (graphManager) {
             graphManager.registerNode(object.getObjectId(), object);
             graphManager.nodeCreated(object.getObjectId());
@@ -703,7 +696,7 @@ public class DataContext implements ObjectContext {
      *
      * @see #invalidateObjects(Collection)
      */
-    public void unregisterObjects(Collection<?> objects) {
+    public void unregisterObjects(Collection<? extends Persistent> objects) {
         getObjectStore().objectsUnregistered(objects);
     }
 
@@ -712,14 +705,14 @@ public class DataContext implements ObjectContext {
      */
     @SafeVarargs
     @Override
-    public final <T> void invalidateObjects(T... objects) {
+    public final <T extends Persistent> void invalidateObjects(T... objects) {
         if (objects != null && objects.length > 0) {
             invalidateObjects(Arrays.asList(objects));
         }
     }
 
     @Override
-    public void invalidateObjects(Collection<?> objects) {
+    public void invalidateObjects(Collection<? extends Persistent> objects) {
 
         // don't allow null collections as a matter of coding discipline
         if (objects == null) {
@@ -727,7 +720,6 @@ public class DataContext implements ObjectContext {
         }
 
         List<ObjectId> ids = objects.stream()
-                .map(Persistent.class::cast)
                 // NEW objects have nothing to refetch, and their temporary ids are unknown to the parent channel
                 .filter(p -> p.getPersistenceState() != PersistenceState.NEW)
                 .map(Persistent::getObjectId)
@@ -757,7 +749,7 @@ public class DataContext implements ObjectContext {
     @SuppressWarnings("unchecked")
     private List<Persistent> resolveRelationshipInSelf(ObjectId sourceId, String relationshipName, boolean resolveToMany) {
 
-        Persistent source = (Persistent) getGraphManager().getNode(sourceId);
+        Persistent source = getGraphManager().getNode(sourceId);
         if (source == null) {
             return null;
         }
@@ -1137,7 +1129,7 @@ public class DataContext implements ObjectContext {
      * @since 1.2
      */
     @Override
-    public GraphManager getGraphManager() {
+    public GraphManager<Persistent> getGraphManager() {
         return objectStore;
     }
 
@@ -1158,7 +1150,7 @@ public class DataContext implements ObjectContext {
         // messing up Persistent objects per CAY-845. Originally only parts of "else" were synchronized,
         // but we had to expand the lock scope to ensure consistent behavior.
         synchronized (getGraphManager()) {
-            Persistent cachedObject = (Persistent) getGraphManager().getNode(id);
+            Persistent cachedObject = getGraphManager().getNode(id);
 
             // return an existing object
             if (cachedObject != null) {
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DataContextMergeHandler.java b/cayenne/src/main/java/org/apache/cayenne/access/DataContextMergeHandler.java
index 5f48c0a72..d9a4f4c61 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataContextMergeHandler.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataContextMergeHandler.java
@@ -157,7 +157,7 @@ class DataContextMergeHandler implements GraphChangeHandler, DataChannelListener
             Object oldValue,
             Object newValue) {
 
-        Persistent object = (Persistent) context.getGraphManager().getNode(nodeId);
+        Persistent object = context.getGraphManager().getNode(nodeId);
         if (object != null && object.getPersistenceState() != PersistenceState.HOLLOW) {
 
             // do not override local changes....
@@ -181,7 +181,7 @@ class DataContextMergeHandler implements GraphChangeHandler, DataChannelListener
     // works the same for add and remove as long as we don't get too smart per TODO below.
     private void arcChanged(Object nodeId, Object arcId) {
 
-        final Persistent source = (Persistent) context.getGraphManager().getNode(nodeId);
+        final Persistent source = context.getGraphManager().getNode(nodeId);
         if (source != null && source.getPersistenceState() != PersistenceState.HOLLOW) {
 
             final int state = source.getPersistenceState();
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/JoinedIdParentAttachmentStrategy.java b/cayenne/src/main/java/org/apache/cayenne/access/JoinedIdParentAttachmentStrategy.java
index 7c6ef7ef4..ab0a747d8 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/JoinedIdParentAttachmentStrategy.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/JoinedIdParentAttachmentStrategy.java
@@ -38,9 +38,9 @@ class JoinedIdParentAttachmentStrategy implements ParentAttachmentStrategy {
     private final CayennePath relatedIdPrefix;
     private final Collection<ObjEntity> sourceEntities;
     private final PrefetchProcessorNode node;
-    private final GraphManager graphManager;
+    private final GraphManager<Persistent> graphManager;
 
-    JoinedIdParentAttachmentStrategy(GraphManager graphManager, PrefetchProcessorNode node) {
+    JoinedIdParentAttachmentStrategy(GraphManager<Persistent> graphManager, PrefetchProcessorNode node) {
         ClassDescriptor parentDescriptor = ((PrefetchProcessorNode) node.getParent())
                 .getResolver()
                 .getDescriptor();
@@ -70,7 +70,7 @@ class JoinedIdParentAttachmentStrategy implements ParentAttachmentStrategy {
                         , row, entity.getName(), relatedIdPrefix);
             }
 
-            parentObject = (Persistent) graphManager.getNode(id);
+            parentObject = graphManager.getNode(id);
             if (parentObject != null) {
                 break;
             }
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/ObjectStore.java b/cayenne/src/main/java/org/apache/cayenne/access/ObjectStore.java
index 329caa64b..4619e6d74 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/ObjectStore.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/ObjectStore.java
@@ -68,7 +68,7 @@ import java.util.concurrent.ConcurrentHashMap;
  *
  * @since 1.0
  */
-public class ObjectStore implements SnapshotEventListener, GraphManager {
+public class ObjectStore implements SnapshotEventListener, GraphManager<Persistent> {
 
     protected final DataContext context;
     protected final DataRowStore dataRowCache;
@@ -250,16 +250,14 @@ public class ObjectStore implements SnapshotEventListener, GraphManager {
      */
     // this method is exactly the same as "objectsInvalidated", only additionally it
     // throws out registered objects
-    public synchronized void objectsUnregistered(Collection objects) {
+    public synchronized void objectsUnregistered(Collection<? extends Persistent> objects) {
         if (objects.isEmpty()) {
             return;
         }
 
         Collection<ObjectId> ids = new ArrayList<>(objects.size());
 
-        for (Object object1 : objects) {
-            Persistent object = (Persistent) object1;
-
+        for (Persistent object : objects) {
             ObjectId id = object.getObjectId();
 
             // remove object but not snapshot
@@ -467,10 +465,11 @@ public class ObjectStore implements SnapshotEventListener, GraphManager {
             return null;
         }
 
-        GraphManager parentGraph = parent.getGraphManager();
+        GraphManager<Persistent> parentGraph = parent.getGraphManager();
         EntityInheritanceTree inheritanceTree = parent.getEntityResolver().getInheritanceTree(oid.getEntityName());
         for (ObjectId candidateId : inheritanceTree.polymorphicIds(oid)) {
-            if (parentGraph.getNode(candidateId) instanceof Persistent parentObject) {
+            Persistent parentObject = parentGraph.getNode(candidateId);
+            if (parentObject != null) {
                 return parent.currentSnapshot(parentObject);
             }
         }
@@ -784,7 +783,7 @@ public class ObjectStore implements SnapshotEventListener, GraphManager {
      * @since 1.2
      */
     @Override
-    public synchronized Object getNode(Object nodeId) {
+    public synchronized Persistent getNode(Object nodeId) {
         return objectMap.get(nodeId);
     }
 
@@ -795,7 +794,7 @@ public class ObjectStore implements SnapshotEventListener, GraphManager {
      * @since 1.2
      */
     @Override
-    public synchronized Collection<Object> registeredNodes() {
+    public synchronized Collection<Persistent> registeredNodes() {
         return new ArrayList<>(objectMap.values());
     }
 
@@ -803,16 +802,16 @@ public class ObjectStore implements SnapshotEventListener, GraphManager {
      * @since 1.2
      */
     @Override
-    public synchronized void registerNode(Object nodeId, Object nodeObject) {
-        objectMap.put(nodeId, (Persistent) nodeObject);
+    public synchronized void registerNode(Object nodeId, Persistent nodeObject) {
+        objectMap.put(nodeId, nodeObject);
     }
 
     /**
      * @since 1.2
      */
     @Override
-    public synchronized Object unregisterNode(Object nodeId) {
-        Object object = getNode(nodeId);
+    public synchronized Persistent unregisterNode(Object nodeId) {
+        Persistent object = objectMap.get(nodeId);
         if (object != null) {
             objectsUnregistered(Collections.singleton(object));
         }
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/ObjectStoreGraphDiff.java b/cayenne/src/main/java/org/apache/cayenne/access/ObjectStoreGraphDiff.java
index 90842fbb9..10abbd967 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/ObjectStoreGraphDiff.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/ObjectStoreGraphDiff.java
@@ -183,7 +183,7 @@ public class ObjectStoreGraphDiff implements GraphDiff {
 
                 ObjectId id = (ObjectId) entry.getKey();
 
-                Persistent object = (Persistent) objectStore.getNode(id);
+                Persistent object = objectStore.getNode(id);
 
                 // address manual id override.
                 ObjectId objectId = object.getObjectId();
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/flush/DbRowOpFactory.java b/cayenne/src/main/java/org/apache/cayenne/access/flush/DbRowOpFactory.java
index fd418f8f6..ba33fb885 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/flush/DbRowOpFactory.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/flush/DbRowOpFactory.java
@@ -74,7 +74,7 @@ class DbRowOpFactory {
         ObjectId id = (ObjectId) diff.getNodeId();
         this.diff = diff;
         this.descriptor = resolver.getClassDescriptor(id.getEntityName());
-        this.object = (Persistent) store.getNode(id);
+        this.object = store.getNode(id);
         this.dbRows.clear();
     }
 
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/flush/operation/GraphBasedDbRowOpSorter.java b/cayenne/src/main/java/org/apache/cayenne/access/flush/operation/GraphBasedDbRowOpSorter.java
index d6307d0ee..3332cf36d 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/flush/operation/GraphBasedDbRowOpSorter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/flush/operation/GraphBasedDbRowOpSorter.java
@@ -290,7 +290,7 @@ public class GraphBasedDbRowOpSorter implements DbRowOpSorter {
             });
 
             // check and merge flattened IDs snapshots
-            GraphManager graphManager = object.getObjectContext().getGraphManager();
+            GraphManager<Persistent> graphManager = object.getObjectContext().getGraphManager();
             if(graphManager instanceof ObjectStore store) {
                 store.getFlattenedIds(object.getObjectId()).forEach(flattenedId -> {
                     // map values of flattened ids from target to source
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 02087b966..8c88363e6 100644
--- a/cayenne/src/main/java/org/apache/cayenne/ashwood/AshwoodEntitySorter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/ashwood/AshwoodEntitySorter.java
@@ -292,7 +292,7 @@ public class AshwoodEntitySorter implements EntitySorter {
 
 		// not using 'localObject', looking up in context instead, as within the sorter
 		// we only care about objects participating in transaction, so no need to create hollow objects
-		return (id != null) ? (Persistent) context.getGraphManager().getNode(id) : null;
+		return (id != null) ? context.getGraphManager().getNode(id) : null;
 	}
 
 	@Override
diff --git a/cayenne/src/main/java/org/apache/cayenne/graph/ChildDiffLoader.java b/cayenne/src/main/java/org/apache/cayenne/graph/ChildDiffLoader.java
index 7e24264a4..f892f0d4b 100644
--- a/cayenne/src/main/java/org/apache/cayenne/graph/ChildDiffLoader.java
+++ b/cayenne/src/main/java/org/apache/cayenne/graph/ChildDiffLoader.java
@@ -243,7 +243,7 @@ public class ChildDiffLoader implements GraphChangeHandler {
 		// return it;
 		// if not - fetch.
 
-		Persistent object = (Persistent) context.getGraphManager().getNode(nodeId);
+		Persistent object = context.getGraphManager().getNode(nodeId);
 		if (object != null) {
 			return object;
 		}
diff --git a/cayenne/src/main/java/org/apache/cayenne/graph/GraphManager.java b/cayenne/src/main/java/org/apache/cayenne/graph/GraphManager.java
index c6f695fba..6f669f159 100644
--- a/cayenne/src/main/java/org/apache/cayenne/graph/GraphManager.java
+++ b/cayenne/src/main/java/org/apache/cayenne/graph/GraphManager.java
@@ -25,29 +25,30 @@ import java.util.Collection;
  * Represents a generic "managed" graph with nodes mapped by their ids. Inherited
  * GraphChangeHandler methods are intended as callbacks for graph node objects to notify
  * graph of their changes.
- * 
+ *
+ * @param <T> the type of graph nodes
  * @since 1.2
  */
-public interface GraphManager extends GraphChangeHandler {
+public interface GraphManager<T> extends GraphChangeHandler {
 
     /**
      * Returns a graph node given an id.
      */
-    Object getNode(Object nodeId);
+    T getNode(Object nodeId);
 
     /**
      * "Registers" a graph node, usually storing the node in some internal map using its
      * id as a key.
      */
-    void registerNode(Object nodeId, Object nodeObject);
+    void registerNode(Object nodeId, T nodeObject);
 
     /**
      * "Unregisters" a graph node, forgetting any information associated with nodeId.
      */
-    Object unregisterNode(Object nodeId);
+    T unregisterNode(Object nodeId);
 
     /**
      * Returns all graph nodes registered with GraphManager.
      */
-    Collection<Object> registeredNodes();
+    Collection<T> registeredNodes();
 }
diff --git a/cayenne/src/main/java/org/apache/cayenne/util/ShallowMergeOperation.java b/cayenne/src/main/java/org/apache/cayenne/util/ShallowMergeOperation.java
index 8388117b0..6780ca156 100644
--- a/cayenne/src/main/java/org/apache/cayenne/util/ShallowMergeOperation.java
+++ b/cayenne/src/main/java/org/apache/cayenne/util/ShallowMergeOperation.java
@@ -54,7 +54,7 @@ public class ShallowMergeOperation {
         ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(
                 id.getEntityName());
 
-        GraphManager graphManager = context.getGraphManager();
+        GraphManager<Persistent> graphManager = context.getGraphManager();
 
         // have to synchronize almost the entire method to prevent multiple threads from
         // messing up Persistent objects per CAY-845.
diff --git a/cayenne/src/test/java/org/apache/cayenne/ContextStateRecorderTest.java b/cayenne/src/test/java/org/apache/cayenne/ContextStateRecorderTest.java
index 8b5ac5134..86a2200c1 100644
--- a/cayenne/src/test/java/org/apache/cayenne/ContextStateRecorderTest.java
+++ b/cayenne/src/test/java/org/apache/cayenne/ContextStateRecorderTest.java
@@ -32,9 +32,10 @@ import org.junit.jupiter.api.Test;
 public class ContextStateRecorderTest {
 
 	private ObjectContextStateLog recorder;
-	private GraphManager mockGraphManager;
+	private GraphManager<Persistent> mockGraphManager;
 
 	@BeforeEach
+	@SuppressWarnings("unchecked")
 	public void before() {
 		this.mockGraphManager = mock(GraphManager.class);
 		this.recorder = new ObjectContextStateLog(mockGraphManager);
diff --git a/cayenne/src/test/java/org/apache/cayenne/access/DataContextInvalidateObjectsIT.java b/cayenne/src/test/java/org/apache/cayenne/access/DataContextInvalidateObjectsIT.java
index d5eb4e015..4c7307a20 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/DataContextInvalidateObjectsIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/DataContextInvalidateObjectsIT.java
@@ -18,11 +18,13 @@
  ****************************************************************/
 package org.apache.cayenne.access;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.cayenne.Cayenne;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.PersistenceState;
+import org.apache.cayenne.Persistent;
 import org.apache.cayenne.query.ObjectSelect;
 import org.apache.cayenne.query.QueryCacheStrategy;
 import org.apache.cayenne.query.SortOrder;
@@ -398,7 +400,9 @@ public class DataContextInvalidateObjectsIT {
                 .getSharedSnapshotCache()
                 .getCachedSnapshot(p2.getObjectId()));
 
-        context.invalidateObjects(context.getObjectStore().registeredNodes());
+        List<Persistent> registered = new ArrayList<>();
+        context.getObjectStore().getObjectIterator().forEachRemaining(registered::add);
+        context.invalidateObjects(registered);
 
         assertNull(context
                 .getChannel().getDataDomain()
diff --git a/cayenne/src/test/java/org/apache/cayenne/dba/ConcurrentPkGeneratorIT.java b/cayenne/src/test/java/org/apache/cayenne/dba/ConcurrentPkGeneratorIT.java
index a23f04a91..7a99fe2ad 100644
--- a/cayenne/src/test/java/org/apache/cayenne/dba/ConcurrentPkGeneratorIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/dba/ConcurrentPkGeneratorIT.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.dba;
 
 import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.Persistent;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.ObjEntity;
@@ -99,7 +100,10 @@ public class ConcurrentPkGeneratorIT {
                 ObjectContext context1 = runtime.newContext();
 				EntityResolver entityResolver = context1.getEntityResolver();
 				for (ObjEntity entity : dataMap.getObjEntities()) {
-                    context1.newObject(entityResolver.getObjectFactory().getJavaClass(entity.getJavaClassName()));
+                    Class<? extends Persistent> type = entityResolver.getObjectFactory()
+                            .getJavaClass(entity.getJavaClassName())
+                            .asSubclass(Persistent.class);
+                    context1.newObject(type);
                 }
                 context1.commitChanges();
             } catch (Exception e) {