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/10 00:30:42 UTC
(cayenne) 01/04: CAY-3017 ObjectContext should not be a DataChannel
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 405f05be44041060efc06f6753b81b84192780ac
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Wed Sep 9 17:18:18 2026 -0400
CAY-3017 ObjectContext should not be a DataChannel
---
RELEASE-NOTES.txt | 1 +
UPGRADE.md | 5 ++
.../java/org/apache/cayenne/ObjectContext.java | 19 ++---
.../org/apache/cayenne/access/DataContext.java | 93 ++++++++--------------
.../apache/cayenne/access/DataContextChannel.java | 83 +++++++++++++++++++
.../cayenne/access/DataContextMergeHandler.java | 10 ++-
.../cayenne/access/DataContextQueryAction.java | 2 +-
.../org/apache/cayenne/access/ObjectStore.java | 8 +-
.../org/apache/cayenne/access/ToManyHolder.java | 3 +-
.../java/org/apache/cayenne/access/ToOneFault.java | 2 +-
.../flush/operation/GraphBasedDbRowOpSorter.java | 2 +-
.../cayenne/ashwood/AshwoodEntitySorter.java | 2 +-
.../apache/cayenne/commitlog/CommitLogFilter.java | 2 +-
.../configuration/ObjectContextFactory.java | 14 ++--
.../configuration/runtime/DataContextFactory.java | 77 ++++++------------
.../org/apache/cayenne/graph/ChildDiffLoader.java | 2 +-
.../org/apache/cayenne/runtime/CayenneRuntime.java | 19 ++++-
.../org/apache/cayenne/tx/TransactionFilter.java | 2 +-
.../java/org/apache/cayenne/util/EventUtil.java | 22 +++++
.../access/DataContextDataChannelEventsIT.java | 3 +-
.../access/DataContextResolveRelationshipIT.java | 6 +-
.../cayenne/access/DataContextSerializationIT.java | 6 +-
.../cayenne/access/NestedDataContextReadIT.java | 6 +-
.../DataContextFactory_DedicatedCacheIT.java | 2 +-
.../runtime/DataContextFactory_ValidationIT.java | 2 +-
.../apache/cayenne/runtime/CayenneRuntimeTest.java | 27 ++++---
26 files changed, 246 insertions(+), 174 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 7026bd377..454e0bd68 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -24,6 +24,7 @@ CAY-3013 MySQL: JDBC-level streaming for iterators
CAY-3014 Remove QueryChain
CAY-3015 Get rid of RefreshQuery
CAY-3016 Get rid of RelationshipQuery
+CAY-3017 ObjectContext should not be a DataChannel
Bug Fixes:
diff --git a/UPGRADE.md b/UPGRADE.md
index d585aaf95..3980f428d 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -136,6 +136,11 @@ Expression caseWhenExp = caseWhen(
.select(context);
```
+* Per [CAY-3017](https://issues.apache.org/jira/browse/CAY-3017) `ObjectContext` no longer extends `DataChannel`. This
+ doesn't change the overall architecture (there are still "channels" downstream from an ObjectContext). But the channel
+ methods are no longer accessible on the context itself. Application code wasn't ever not supposed to use them, as
+ everything you need to do, you can do via the ObjectContext API.
+
* The `org.apache.cayenne.query.ParameterizedQuery` interface was removed, together with the `createQuery(Map)`
methods of `SQLTemplate`, `ProcedureQuery` and `ObjectSelect` that implemented it. Applying parameters to a mapped
query is now the job of the query descriptor - override `QueryDescriptor.buildQuery(Map)` if you have a custom
diff --git a/cayenne/src/main/java/org/apache/cayenne/ObjectContext.java b/cayenne/src/main/java/org/apache/cayenne/ObjectContext.java
index 3e92a79ca..f15fffb27 100644
--- a/cayenne/src/main/java/org/apache/cayenne/ObjectContext.java
+++ b/cayenne/src/main/java/org/apache/cayenne/ObjectContext.java
@@ -31,10 +31,15 @@ import java.util.List;
/**
* A Cayenne object facade to a persistent store. Instances of ObjectContext are
* used in the application code to access Cayenne persistence features.
- *
+ *
* @since 1.2
*/
-public interface ObjectContext extends DataChannel, Serializable {
+public interface ObjectContext extends Serializable {
+
+ /**
+ * Returns a DataChannel that this context uses to access the persistent store.
+ */
+ DataChannel getChannel();
/**
* Returns EntityResolver that stores all mapping information accessible by
@@ -258,16 +263,6 @@ public interface ObjectContext extends DataChannel, Serializable {
*/
GraphManager getGraphManager();
- /**
- * Returns a DataChannel used by this context.
- *
- * @deprecated in favor of {@link DataChannel#getParent()}
- */
- @Deprecated(since = "5.0", forRemoval = true)
- default DataChannel getChannel() {
- return getParent();
- }
-
/**
* Returns <code>true</code> if there are any modified, deleted or new
* objects registered with this ObjectContext, <code>false</code> otherwise.
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 52ad3f46b..77061f4dd 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java
@@ -249,13 +249,17 @@ public class DataContext implements ObjectContext {
EventManager eventManager = channel.getEventManager();
if (eventManager != null) {
- mergeHandler = new DataContextMergeHandler(this);
- // listen to our channel events...
- // note that we must reset listener on channel switch, as there is
- // no
- // guarantee that a new channel uses the same EventManager.
- EventUtil.listenForChannelEvents(channel, mergeHandler);
+ // listen to our channel events... note that we must reset listener on channel switch, as there is no
+ // guarantee that a new channel uses the same EventManager. A parent context posts its events on its own
+ // behalf, not on behalf of the channel adapter wrapping it, so listen to the context in that case
+ if (channel instanceof DataContextChannel channelAdapter) {
+ mergeHandler = new DataContextMergeHandler(this, channelAdapter.context());
+ EventUtil.listenForChannelEvents(channelAdapter.context(), mergeHandler);
+ } else {
+ mergeHandler = new DataContextMergeHandler(this, channel);
+ EventUtil.listenForChannelEvents(channel, mergeHandler);
+ }
}
if (!usingSharedSnapshotCache && getObjectStore() != null) {
@@ -268,7 +272,7 @@ public class DataContext implements ObjectContext {
}
@Override
- public DataChannel getParent() {
+ public DataChannel getChannel() {
attachToRuntimeIfNeeded();
return channel;
}
@@ -465,13 +469,7 @@ public class DataContext implements ObjectContext {
this.queryCache = queryCache;
}
- /**
- * Returns EventManager associated with the ObjectStore.
- *
- * @since 1.2
- */
- @Override
- public EventManager getEventManager() {
+ private EventManager eventManager() {
return channel != null ? channel.getEventManager() : null;
}
@@ -479,7 +477,7 @@ public class DataContext implements ObjectContext {
* @since 1.2
*/
protected void fireDataChannelCommitted(Object postedBy, GraphDiff changes) {
- EventManager manager = getEventManager();
+ EventManager manager = eventManager();
if (manager != null) {
GraphEvent e = new GraphEvent(this, postedBy, changes);
@@ -491,7 +489,7 @@ public class DataContext implements ObjectContext {
* @since 1.2
*/
protected void fireDataChannelRolledback(Object postedBy, GraphDiff changes) {
- EventManager manager = getEventManager();
+ EventManager manager = eventManager();
if (manager != null) {
GraphEvent e = new GraphEvent(this, postedBy, changes);
@@ -603,12 +601,12 @@ public class DataContext implements ObjectContext {
// a child context has no snapshot cache, so it can't resolve rows on its own. Resolve them in the parent
// context and transfer the resulting objects here
if (getObjectStore().getDataRowCache() == null) {
- if (!(getParent() instanceof DataContext parentContext)) {
+ if (!(getChannel() instanceof DataContextChannel channelAdapter)) {
throw new CayenneRuntimeException(
"DataContext has no snapshot cache and no parent DataContext to resolve DataRows");
}
- List<?> parentObjects = parentContext.objectsFromDataRows(descriptor, dataRows);
+ List<?> parentObjects = channelAdapter.context().objectsFromDataRows(descriptor, dataRows);
ShallowMergeOperation merger = new ShallowMergeOperation(this);
List<Persistent> objects = new ArrayList<>(parentObjects.size());
for (Object parentObject : parentObjects) {
@@ -862,34 +860,22 @@ public class DataContext implements ObjectContext {
if (!ids.isEmpty()) {
getObjectStore().objectsInvalidated(ids);
- getParent().onInvalidate(this, ids);
+ getChannel().onInvalidate(this, ids);
}
}
- /**
- * @since 5.0
- */
- @Override
- public void onInvalidate(ObjectContext originatingContext, Collection<ObjectId> ids) {
+ void onInvalidate(Collection<ObjectId> ids) {
if (!ids.isEmpty()) {
getObjectStore().objectsInvalidated(ids);
- getParent().onInvalidate(this, ids);
+ getChannel().onInvalidate(this, ids);
}
}
- /**
- * An implementation of a {@link DataChannel} method that resolves a relationship from the objects already
- * registered in this context, delegating to the parent channel on a miss. Not intended for direct use, read
- * the relationship property of an object instead.
- *
- * @since 5.0
- */
- @Override
- public List<Persistent> onResolveRelationship(ObjectContext originatingContext, ObjectId sourceId, String relationshipName) {
+ List<Persistent> onResolveRelationship(ObjectContext originatingContext, ObjectId sourceId, String relationshipName) {
List<Persistent> related = resolvedRelationship(sourceId, relationshipName, originatingContext != this);
if (related == null) {
- related = getParent().onResolveRelationship(this, sourceId, relationshipName);
+ related = getChannel().onResolveRelationship(this, sourceId, relationshipName);
}
if (originatingContext == this || related.isEmpty()) {
@@ -1046,7 +1032,7 @@ public class DataContext implements ObjectContext {
*/
GraphDiff flushToParent(boolean cascade) {
- if (this.getParent() == null) {
+ if (this.getChannel() == null) {
throw new CayenneRuntimeException("Cannot commit changes - channel is not set.");
}
@@ -1067,7 +1053,7 @@ public class DataContext implements ObjectContext {
} else {
try {
- parentChanges = getParent().onSync(this, changes, syncType);
+ parentChanges = getChannel().onSync(this, changes, syncType);
// note that this is a hack resulting from a fix to CAY-766...
// To support valid object state in PostPersist callback,
@@ -1105,7 +1091,7 @@ public class DataContext implements ObjectContext {
// this event is caught by child DataContexts to update temporary ObjectIds with permanent
if (!diff.isNoop()) {
- fireDataChannelCommitted(getParent(), diff);
+ fireDataChannelCommitted(getChannel(), diff);
}
return diff;
@@ -1192,7 +1178,7 @@ public class DataContext implements ObjectContext {
return new GenericResponse();
}
- if (this.getParent() == null) {
+ if (this.getChannel() == null) {
throw new CayenneRuntimeException("Can't run query - parent DataChannel is not set.");
}
@@ -1230,28 +1216,17 @@ public class DataContext implements ObjectContext {
return result != null ? result : new ArrayList<>(1);
}
- /**
- * An implementation of a {@link DataChannel} method that is used by child
- * contexts to execute queries. Not intended for direct use.
- *
- * @since 1.2
- */
- public QueryResponse onQuery(ObjectContext context, Query query, boolean iteratedResult) {
+ QueryResponse onQuery(ObjectContext context, Query query, boolean iteratedResult) {
return new DataContextQueryAction(this, context, query, iteratedResult).execute();
}
- @Override
- public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType) {
- switch (syncType) {
- case DataChannel.ROLLBACK_CASCADE_SYNC:
- return onContextRollback();
- case DataChannel.FLUSH_NOCASCADE_SYNC:
- return onContextFlush(originatingContext, changes, false);
- case DataChannel.FLUSH_CASCADE_SYNC:
- return onContextFlush(originatingContext, changes, true);
- default:
- throw new CayenneRuntimeException("Unrecognized SyncMessage type: %d", syncType);
- }
+ GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType) {
+ return switch (syncType) {
+ case DataChannel.ROLLBACK_CASCADE_SYNC -> onContextRollback();
+ case DataChannel.FLUSH_NOCASCADE_SYNC -> onContextFlush(originatingContext, changes, false);
+ case DataChannel.FLUSH_CASCADE_SYNC -> onContextFlush(originatingContext, changes, true);
+ default -> throw new CayenneRuntimeException("Unrecognized SyncMessage type: %d", syncType);
+ };
}
GraphDiff onContextRollback() {
@@ -1450,7 +1425,7 @@ public class DataContext implements ObjectContext {
* @since 1.2
*/
protected void fireDataChannelChanged(Object postedBy, GraphDiff changes) {
- EventManager manager = getEventManager();
+ EventManager manager = eventManager();
if (manager != null) {
GraphEvent e = new GraphEvent(this, postedBy, changes);
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DataContextChannel.java b/cayenne/src/main/java/org/apache/cayenne/access/DataContextChannel.java
new file mode 100644
index 000000000..86fa9bf56
--- /dev/null
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataContextChannel.java
@@ -0,0 +1,83 @@
+/*****************************************************************
+ * 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.access;
+
+import org.apache.cayenne.DataChannel;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.QueryResponse;
+import org.apache.cayenne.event.EventManager;
+import org.apache.cayenne.graph.GraphDiff;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.Query;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A {@link DataChannel} that connects a nested ObjectContext to its parent {@link DataContext}. All channel
+ * operations are delegated to the parent context. Note that the parent context posts channel events on its own behalf,
+ * so listeners interested in those events should listen to the context, not to this channel.
+ *
+ * @since 5.0
+ */
+public record DataContextChannel(DataContext context) implements DataChannel {
+
+ public DataContextChannel {
+ Objects.requireNonNull(context, "Null parent context");
+ }
+
+ @Override
+ public EventManager getEventManager() {
+ return context.getChannel().getEventManager();
+ }
+
+ @Override
+ public EntityResolver getEntityResolver() {
+ return context.getEntityResolver();
+ }
+
+ @Override
+ public DataChannel getParent() {
+ return context.getChannel();
+ }
+
+ @Override
+ public QueryResponse onQuery(ObjectContext originatingContext, Query query, boolean iteratedResult) {
+ return context.onQuery(originatingContext, query, iteratedResult);
+ }
+
+ @Override
+ public void onInvalidate(ObjectContext originatingContext, Collection<ObjectId> objectIds) {
+ context.onInvalidate(objectIds);
+ }
+
+ @Override
+ public List<Persistent> onResolveRelationship(ObjectContext originatingContext, ObjectId sourceId, String relationshipName) {
+ return context.onResolveRelationship(originatingContext, sourceId, relationshipName);
+ }
+
+ @Override
+ public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType) {
+ return context.onSync(originatingContext, changes, syncType);
+ }
+}
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 5c37d3527..5f48c0a72 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataContextMergeHandler.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataContextMergeHandler.java
@@ -44,10 +44,12 @@ import org.apache.cayenne.reflect.ToOneProperty;
class DataContextMergeHandler implements GraphChangeHandler, DataChannelListener {
private final DataContext context;
+ private final Object eventSource;
private volatile boolean stopped;
- DataContextMergeHandler(DataContext context) {
+ DataContextMergeHandler(DataContext context, Object eventSource) {
this.context = context;
+ this.eventSource = eventSource;
}
void stop() {
@@ -65,11 +67,11 @@ class DataContextMergeHandler implements GraphChangeHandler, DataChannelListener
// this effectively filters out all events that are not coming from peers or
// grandparents...
- return e.getSource() == context.getParent()
+ return e.getSource() == eventSource
&& e.getPostedBy() != context
- && e.getPostedBy() != context.getParent();
+ && e.getPostedBy() != eventSource;
- // the first condition (e.getSource() == context.getParent()) is actually always
+ // the first condition (e.getSource() == eventSource) is actually always
// 'true' because of how the listener is registered. Still keep it here as an
// extra safegurad
}
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java b/cayenne/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
index 2467492e6..2800469d1 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
@@ -343,6 +343,6 @@ class DataContextQueryAction {
* Fetches data from the channel.
*/
protected void runQuery() {
- this.response = actingContext.getParent().onQuery(actingContext, query, iteratedResult);
+ this.response = actingContext.getChannel().onQuery(actingContext, query, iteratedResult);
}
}
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 67fec89d5..cfad0c8fa 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/ObjectStore.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/ObjectStore.java
@@ -468,9 +468,9 @@ public class ObjectStore implements Serializable, SnapshotEventListener, GraphMa
*/
public DataRow getCachedSnapshot(ObjectId oid) {
- if (context != null && context.getParent() != null) {
+ if (context != null && context.getChannel() != null) {
ObjectIdQuery query = new ObjectIdQuery(oid, true, ObjectIdQuery.CACHE_NOREFRESH);
- List<?> results = context.getParent().onQuery(context, query, false).firstList();
+ List<?> results = context.getChannel().onQuery(context, query, false).firstList();
return results.isEmpty() ? null : (DataRow) results.get(0);
}
else {
@@ -488,9 +488,9 @@ public class ObjectStore implements Serializable, SnapshotEventListener, GraphMa
*/
public synchronized DataRow getSnapshot(ObjectId oid) {
- if (context != null && context.getParent() != null) {
+ if (context != null && context.getChannel() != null) {
ObjectIdQuery query = new ObjectIdQuery(oid, true, ObjectIdQuery.CACHE);
- List<?> results = context.getParent().onQuery(context, query, false).firstList();
+ List<?> results = context.getChannel().onQuery(context, query, false).firstList();
return results.isEmpty() ? null : (DataRow) results.get(0);
}
else {
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/ToManyHolder.java b/cayenne/src/main/java/org/apache/cayenne/access/ToManyHolder.java
index 8ebc41610..29413703c 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/ToManyHolder.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/ToManyHolder.java
@@ -19,7 +19,6 @@
package org.apache.cayenne.access;
-import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.PersistenceState;
import org.apache.cayenne.Persistent;
@@ -98,7 +97,7 @@ public abstract class ToManyHolder<E> implements Serializable {
return new ArrayList<>();
}
- ObjectContext context = relationshipOwner.getObjectContext();
+ DataContext context = (DataContext) relationshipOwner.getObjectContext();
List<E> resolved = (List<E>) context.onResolveRelationship(
context,
relationshipOwner.getObjectId(),
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/ToOneFault.java b/cayenne/src/main/java/org/apache/cayenne/access/ToOneFault.java
index 486aa8b6a..4271264b0 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/ToOneFault.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/ToOneFault.java
@@ -72,7 +72,7 @@ public class ToOneFault extends Fault {
}
Object doResolveFault(Persistent sourceObject, String relationshipName) {
- ObjectContext context = sourceObject.getObjectContext();
+ DataContext context = (DataContext) sourceObject.getObjectContext();
List<Persistent> objects = context.onResolveRelationship(
context,
sourceObject.getObjectId(),
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 54ef05ca7..0a7fc0d8b 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
@@ -272,7 +272,7 @@ public class GraphBasedDbRowOpSorter implements DbRowOpSorter {
private Map<String, Object> getCachedSnapshot(Persistent object) {
ObjectIdQuery query = new ObjectIdQuery(object.getObjectId(), true, ObjectIdQuery.CACHE);
- QueryResponse response = object.getObjectContext().getParent().onQuery(null, query, false);
+ QueryResponse response = object.getObjectContext().getChannel().onQuery(null, query, false);
@SuppressWarnings("unchecked")
List<DataRow> result = (List<DataRow>) response.firstList();
if (result == null || result.isEmpty()) {
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 6e8b3d59b..a2549e9f5 100644
--- a/cayenne/src/main/java/org/apache/cayenne/ashwood/AshwoodEntitySorter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/ashwood/AshwoodEntitySorter.java
@@ -283,7 +283,7 @@ public class AshwoodEntitySorter implements EntitySorter {
}
ObjectIdQuery query = new ObjectIdQuery(object.getObjectId(), true, ObjectIdQuery.CACHE);
- QueryResponse response = context.getParent().onQuery(null, query, false);
+ QueryResponse response = context.getChannel().onQuery(null, query, false);
List<?> result = response.firstList();
if (result == null || result.isEmpty()) {
return null;
diff --git a/cayenne/src/main/java/org/apache/cayenne/commitlog/CommitLogFilter.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/CommitLogFilter.java
index 758cb4495..706ea2fe1 100644
--- a/cayenne/src/main/java/org/apache/cayenne/commitlog/CommitLogFilter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/commitlog/CommitLogFilter.java
@@ -67,7 +67,7 @@ public class CommitLogFilter implements DataChannelSyncFilter {
// passing DataDomain, not ObjectContext to speed things up
// and avoid capturing changed state when fetching snapshots
- DataChannel channel = originatingContext.getParent();
+ DataChannel channel = originatingContext.getChannel();
beforeCommit(changes, channel, beforeDiff);
GraphDiff afterDiff = filterChain.onSync(originatingContext, beforeDiff, syncType);
diff --git a/cayenne/src/main/java/org/apache/cayenne/configuration/ObjectContextFactory.java b/cayenne/src/main/java/org/apache/cayenne/configuration/ObjectContextFactory.java
index 785099e0b..edb00fbc8 100644
--- a/cayenne/src/main/java/org/apache/cayenne/configuration/ObjectContextFactory.java
+++ b/cayenne/src/main/java/org/apache/cayenne/configuration/ObjectContextFactory.java
@@ -23,19 +23,21 @@ import org.apache.cayenne.ObjectContext;
/**
* A factory for regular and nested contexts.
- *
+ *
* @since 3.1
*/
public interface ObjectContextFactory {
/**
- * Creates an ObjectContext attached to a default DataChannel.
+ * Creates an ObjectContext attached to a provided channel
*/
- ObjectContext createContext();
+ ObjectContext createContext(DataChannel parent);
/**
- * Creates an ObjectContext attached to a provided channel. This is often used for
- * nested context creation.
+ * Creates an ObjectContext nested in the provided parent context. The nested context selects and commits its
+ * objects via the parent.
+ *
+ * @since 5.0
*/
- ObjectContext createContext(DataChannel parent);
+ ObjectContext createContext(ObjectContext parent);
}
diff --git a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DataContextFactory.java b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DataContextFactory.java
index 4416f5074..97f5aef70 100644
--- a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DataContextFactory.java
+++ b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DataContextFactory.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.configuration.runtime;
import org.apache.cayenne.DataChannel;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.access.DataContextChannel;
import org.apache.cayenne.access.DataDomain;
import org.apache.cayenne.access.DataRowStore;
import org.apache.cayenne.access.DataRowStoreFactory;
@@ -30,92 +31,66 @@ import org.apache.cayenne.cache.QueryCache;
import org.apache.cayenne.configuration.ObjectContextFactory;
import org.apache.cayenne.configuration.ObjectStoreFactory;
import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.event.EventManager;
/**
* @since 3.1
*/
public class DataContextFactory implements ObjectContextFactory {
- @Inject
- protected DataDomain dataDomain;
-
- @Inject
- protected EventManager eventManager;
-
@Inject
protected DataRowStoreFactory dataRowStoreFactory;
-
+
@Inject
protected ObjectStoreFactory objectStoreFactory;
-
+
@Inject
protected QueryCache queryCache;
- @Override
- public ObjectContext createContext() {
- return createdFromDataDomain(dataDomain);
- }
-
@Override
public ObjectContext createContext(DataChannel parent) {
- // this switch may go away once we figure out clean property configuration...
- if (parent instanceof DataDomain dataDomain) {
- return createdFromDataDomain(dataDomain);
+ DataChannel c = parent;
+ while (c != null && !(c instanceof DataDomain)) {
+ c = c.getParent();
}
- else if (parent instanceof DataContext dataContext) {
- return createFromDataContext(dataContext);
- }
- else {
- return createFromGenericChannel(parent);
+
+ if (c == null) {
+ throw new IllegalArgumentException("Parent DataChannel is not attached to a DataDomain: " + parent);
}
- }
- protected ObjectContext createFromGenericChannel(DataChannel parent) {
+ DataDomain domain = (DataDomain) c;
- // for new dataRowStores use the same name for all stores
- // it makes it easier to track the event subject
- DataRowStore snapshotCache = (dataDomain.isSharedCacheEnabled())
- ? dataDomain.getSharedSnapshotCache()
- : dataRowStoreFactory.createDataRowStore(dataDomain.getName());
+ // for new dataRowStores use the same name for all stores it makes it easier to track the event subject
+ DataRowStore snapshotCache = domain.isSharedCacheEnabled()
+ ? domain.getSharedSnapshotCache()
+ : dataRowStoreFactory.createDataRowStore(domain.getName());
- DataContext context = newInstance(
- parent, objectStoreFactory.createObjectStore(snapshotCache));
- context.setValidatingObjectsOnCommit(dataDomain.isValidatingObjectsOnCommit());
+ DataContext context = newInstance(parent, objectStoreFactory.createObjectStore(snapshotCache));
+ context.setValidatingObjectsOnCommit(domain.isValidatingObjectsOnCommit());
context.setQueryCache(new NestedQueryCache(queryCache));
return context;
}
- protected ObjectContext createFromDataContext(DataContext parent) {
+ @Override
+ public ObjectContext createContext(ObjectContext parent) {
+ if (!(parent instanceof DataContext dataContext)) {
+ throw new IllegalArgumentException("Only a DataContext can be a parent of a nested context. "
+ + "Unsupported context type: " + parent);
+ }
+
// child ObjectStore should not have direct access to snapshot cache, so do not
// pass it in constructor.
ObjectStore objectStore = objectStoreFactory.createObjectStore(null);
- DataContext context = newInstance(parent, objectStore);
+ DataContext context = newInstance(new DataContextChannel(dataContext), objectStore);
- context.setValidatingObjectsOnCommit(parent.isValidatingObjectsOnCommit());
- context.setUsingSharedSnapshotCache(parent.isUsingSharedSnapshotCache());
+ context.setValidatingObjectsOnCommit(dataContext.isValidatingObjectsOnCommit());
+ context.setUsingSharedSnapshotCache(dataContext.isUsingSharedSnapshotCache());
context.setQueryCache(new NestedQueryCache(queryCache));
return context;
}
- protected ObjectContext createdFromDataDomain(DataDomain parent) {
-
- // for new dataRowStores use the same name for all stores
- // it makes it easier to track the event subject
- DataRowStore snapshotCache = (parent.isSharedCacheEnabled())
- ? parent.getSharedSnapshotCache()
- : dataRowStoreFactory.createDataRowStore(parent.getName());
-
- DataContext context = newInstance(
- parent, objectStoreFactory.createObjectStore(snapshotCache));
- context.setValidatingObjectsOnCommit(parent.isValidatingObjectsOnCommit());
- context.setQueryCache(new NestedQueryCache(queryCache));
- return context;
- }
-
protected DataContext newInstance(DataChannel parent, ObjectStore objectStore) {
return new DataContext(parent, objectStore);
}
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 bac1687bb..bcc60a69f 100644
--- a/cayenne/src/main/java/org/apache/cayenne/graph/ChildDiffLoader.java
+++ b/cayenne/src/main/java/org/apache/cayenne/graph/ChildDiffLoader.java
@@ -263,7 +263,7 @@ public class ChildDiffLoader implements GraphChangeHandler {
// skip context cache lookup, go directly to its channel
Query query = new ObjectIdQuery((ObjectId) nodeId);
- QueryResponse response = context.getParent().onQuery(context, query, false);
+ QueryResponse response = context.getChannel().onQuery(context, query, false);
List<?> objects = response.firstList();
if (objects.size() == 0) {
diff --git a/cayenne/src/main/java/org/apache/cayenne/runtime/CayenneRuntime.java b/cayenne/src/main/java/org/apache/cayenne/runtime/CayenneRuntime.java
index 97fd3b82f..13836c49c 100644
--- a/cayenne/src/main/java/org/apache/cayenne/runtime/CayenneRuntime.java
+++ b/cayenne/src/main/java/org/apache/cayenne/runtime/CayenneRuntime.java
@@ -310,18 +310,29 @@ public class CayenneRuntime {
* @since 4.0
*/
public ObjectContext newContext() {
- return injector.getInstance(ObjectContextFactory.class).createContext();
+ DataDomain domain = getDataDomain();
+ return injector.getInstance(ObjectContextFactory.class).createContext(domain);
}
/**
- * Returns a new ObjectContext which is a child of the specified
- * DataChannel. This method is used for creation of nested ObjectContexts,
- * with parent ObjectContext passed as an argument.
+ * Returns a new ObjectContext attached to the specified DataChannel.
*
* @since 4.0
+ * @deprecated use either {@link #newContext()} or {@link #newContext(ObjectContext)}
*/
+ @Deprecated(since = "5.0", forRemoval = true)
public ObjectContext newContext(DataChannel parentChannel) {
return injector.getInstance(ObjectContextFactory.class).createContext(parentChannel);
}
+ /**
+ * Returns a new ObjectContext nested in the specified parent context. The nested context selects and commits its
+ * objects via the parent.
+ *
+ * @since 5.0
+ */
+ public ObjectContext newContext(ObjectContext parentContext) {
+ return injector.getInstance(ObjectContextFactory.class).createContext(parentContext);
+ }
+
}
diff --git a/cayenne/src/main/java/org/apache/cayenne/tx/TransactionFilter.java b/cayenne/src/main/java/org/apache/cayenne/tx/TransactionFilter.java
index 8e28ca212..ea20e11c3 100644
--- a/cayenne/src/main/java/org/apache/cayenne/tx/TransactionFilter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/tx/TransactionFilter.java
@@ -41,7 +41,7 @@ public class TransactionFilter implements DataChannelSyncFilter {
@Override
public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType, DataChannelSyncFilterChain filterChain) {
DataChannelSyncCallbackAction callbackAction = DataChannelSyncCallbackAction.getCallbackAction(
- originatingContext.getParent().getEntityResolver().getCallbackRegistry(),
+ originatingContext.getEntityResolver().getCallbackRegistry(),
originatingContext.getGraphManager(),
changes,
syncType
diff --git a/cayenne/src/main/java/org/apache/cayenne/util/EventUtil.java b/cayenne/src/main/java/org/apache/cayenne/util/EventUtil.java
index 1c10d7d01..0663d9ac1 100644
--- a/cayenne/src/main/java/org/apache/cayenne/util/EventUtil.java
+++ b/cayenne/src/main/java/org/apache/cayenne/util/EventUtil.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.util;
import org.apache.cayenne.DataChannel;
import org.apache.cayenne.DataChannelListener;
+import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.event.EventManager;
import org.apache.cayenne.event.EventSubject;
import org.apache.cayenne.graph.GraphEvent;
@@ -58,6 +59,27 @@ public class EventUtil {
return true;
}
+ /**
+ * Utility method that sets up a GraphChangeListener to be notified when an ObjectContext posts an event, such as
+ * the events a parent context posts on flush, rollback, or a change merged from one of its nested contexts.
+ *
+ * @return false if the context's channel doesn't have an EventManager and therefore does not support events.
+ * @since 5.0
+ */
+ public static boolean listenForChannelEvents(
+ ObjectContext context,
+ DataChannelListener listener) {
+
+ EventManager manager = context.getChannel().getEventManager();
+
+ if (manager == null) {
+ return false;
+ }
+
+ listenForSubjects(manager, listener, context, CHANNEL_SUBJECTS);
+ return true;
+ }
+
/**
* Listen for events from all channels that use a given EventManager.
*/
diff --git a/cayenne/src/test/java/org/apache/cayenne/access/DataContextDataChannelEventsIT.java b/cayenne/src/test/java/org/apache/cayenne/access/DataContextDataChannelEventsIT.java
index 5e99411f0..88754201e 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/DataContextDataChannelEventsIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/DataContextDataChannelEventsIT.java
@@ -19,7 +19,6 @@
package org.apache.cayenne.access;
-import org.apache.cayenne.DataChannel;
import org.apache.cayenne.DataChannelListener;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.configuration.runtime.CoreModule;
@@ -162,7 +161,7 @@ public class DataContextDataChannelEventsIT {
childPeer1.commitChanges();
final MockChannelListener listener = new MockChannelListener();
- EventUtil.listenForChannelEvents((DataChannel) childPeer1, listener);
+ EventUtil.listenForChannelEvents(childPeer1, listener);
ObjectContext childPeer2 = env.runtime().newContext(context);
diff --git a/cayenne/src/test/java/org/apache/cayenne/access/DataContextResolveRelationshipIT.java b/cayenne/src/test/java/org/apache/cayenne/access/DataContextResolveRelationshipIT.java
index 94758ef11..507779b73 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/DataContextResolveRelationshipIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/DataContextResolveRelationshipIT.java
@@ -123,7 +123,7 @@ public class DataContextResolveRelationshipIT {
Artist childA = child.localObject(a);
env.runWithQueriesBlocked(() -> {
- List<Persistent> related = child.onResolveRelationship(child, childA.getObjectId(),
+ List<Persistent> related = ((DataContext) child).onResolveRelationship(child, childA.getObjectId(),
Artist.PAINTING_ARRAY.getName());
assertEquals(2, related.size());
for (Persistent p : related) {
@@ -145,7 +145,7 @@ public class DataContextResolveRelationshipIT {
Painting childP = child.localObject(p);
env.runWithQueriesBlocked(() -> {
- List<Persistent> related = child.onResolveRelationship(child, childP.getObjectId(),
+ List<Persistent> related = ((DataContext) child).onResolveRelationship(child, childP.getObjectId(),
Painting.TO_ARTIST.getName());
assertEquals(1, related.size());
assertSame(child, related.getFirst().getObjectContext());
@@ -166,7 +166,7 @@ public class DataContextResolveRelationshipIT {
// a NEW object is unknown to the database, so its relationships must be resolved from the parent context
env.runWithQueriesBlocked(() -> {
- List<Persistent> related = child.onResolveRelationship(child, childA.getObjectId(),
+ List<Persistent> related = ((DataContext) child).onResolveRelationship(child, childA.getObjectId(),
Artist.PAINTING_ARRAY.getName());
assertEquals(1, related.size());
assertSame(child, related.getFirst().getObjectContext());
diff --git a/cayenne/src/test/java/org/apache/cayenne/access/DataContextSerializationIT.java b/cayenne/src/test/java/org/apache/cayenne/access/DataContextSerializationIT.java
index 4ca0b9377..435537e27 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/DataContextSerializationIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/DataContextSerializationIT.java
@@ -84,8 +84,8 @@ public class DataContextSerializationIT {
DataContext deserializedContext = Util.cloneViaSerialization(context);
- assertNotNull(deserializedContext.getParent());
- assertSame(context.getParent(), deserializedContext.getParent());
+ assertNotNull(deserializedContext.getChannel());
+ assertSame(context.getChannel(), deserializedContext.getChannel());
}
@Test
@@ -95,7 +95,7 @@ public class DataContextSerializationIT {
ObjectContext deserializedContext = Util.cloneViaSerialization(child);
- assertNotNull(deserializedContext.getParent());
+ assertNotNull(deserializedContext.getChannel());
assertNotNull(deserializedContext.getEntityResolver());
}
diff --git a/cayenne/src/test/java/org/apache/cayenne/access/NestedDataContextReadIT.java b/cayenne/src/test/java/org/apache/cayenne/access/NestedDataContextReadIT.java
index a39de2406..1bc912413 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/NestedDataContextReadIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/NestedDataContextReadIT.java
@@ -103,7 +103,7 @@ public class NestedDataContextReadIT {
ObjectContext child1 = runtime.newContext(context);
assertNotNull(child1);
- assertSame(context, child1.getChannel());
+ assertSame(context, ((DataContextChannel) child1.getChannel()).context());
assertTrue(((DataContext) child1).isValidatingObjectsOnCommit());
context.setValidatingObjectsOnCommit(false);
@@ -111,14 +111,14 @@ public class NestedDataContextReadIT {
ObjectContext child2 = runtime.newContext(context);
assertNotNull(child2);
- assertSame(context, child2.getChannel());
+ assertSame(context, ((DataContextChannel) child2.getChannel()).context());
assertFalse(((DataContext) child2).isValidatingObjectsOnCommit());
// second level of nesting
ObjectContext child21 = runtime.newContext(child2);
assertNotNull(child21);
- assertSame(child2, child21.getChannel());
+ assertSame(child2, ((DataContextChannel) child21.getChannel()).context());
assertFalse(((DataContext) child2).isValidatingObjectsOnCommit());
}
diff --git a/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataContextFactory_DedicatedCacheIT.java b/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataContextFactory_DedicatedCacheIT.java
index 2a254f55f..0c68ef099 100644
--- a/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataContextFactory_DedicatedCacheIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataContextFactory_DedicatedCacheIT.java
@@ -41,7 +41,7 @@ public class DataContextFactory_DedicatedCacheIT {
DataContext c3 = (DataContext) env.runtime().getInjector()
.getInstance(ObjectContextFactory.class)
- .createContext();
+ .createContext(domain);
assertNotNull(c3.getObjectStore().getDataRowCache());
assertNull(domain.getSharedSnapshotCache());
diff --git a/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataContextFactory_ValidationIT.java b/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataContextFactory_ValidationIT.java
index 44d4036b3..193711af4 100644
--- a/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataContextFactory_ValidationIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataContextFactory_ValidationIT.java
@@ -36,7 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class DataContextFactory_ValidationIT {
private static DataContext createContext(CayenneTestsEnv env) {
- return (DataContext) env.runtime().getInjector().getInstance(ObjectContextFactory.class).createContext();
+ return (DataContext) env.runtime().getInjector().getInstance(ObjectContextFactory.class).createContext(env.runtime().getDataDomain());
}
@Nested
diff --git a/cayenne/src/test/java/org/apache/cayenne/runtime/CayenneRuntimeTest.java b/cayenne/src/test/java/org/apache/cayenne/runtime/CayenneRuntimeTest.java
index ee3b9aa57..0d322aff4 100644
--- a/cayenne/src/test/java/org/apache/cayenne/runtime/CayenneRuntimeTest.java
+++ b/cayenne/src/test/java/org/apache/cayenne/runtime/CayenneRuntimeTest.java
@@ -21,13 +21,13 @@ package org.apache.cayenne.runtime;
import org.apache.cayenne.DataChannel;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.access.DataDomain;
import org.apache.cayenne.configuration.ObjectContextFactory;
import org.apache.cayenne.di.Injector;
import org.apache.cayenne.di.Module;
import org.apache.cayenne.tx.BaseTransaction;
import org.apache.cayenne.tx.TransactionDescriptor;
import org.apache.cayenne.tx.TransactionFactory;
-import org.apache.cayenne.tx.TransactionalOperation;
import org.junit.jupiter.api.Test;
import java.util.Collections;
@@ -54,11 +54,9 @@ public class CayenneRuntimeTest {
try {
final Object expectedResult = new Object();
- Object result = runtime.performInTransaction(new TransactionalOperation<Object>() {
- public Object perform() {
- assertSame(tx, BaseTransaction.getThreadTransaction());
- return expectedResult;
- }
+ Object result = runtime.performInTransaction(() -> {
+ assertSame(tx, BaseTransaction.getThreadTransaction());
+ return expectedResult;
});
assertSame(expectedResult, result);
@@ -87,19 +85,25 @@ public class CayenneRuntimeTest {
@Test
public void getObjectContext_CustomModule() {
- final ObjectContext context = new DataContext();
- final ObjectContextFactory factory = new ObjectContextFactory() {
+ ObjectContext context = new DataContext();
+ ObjectContextFactory factory = new ObjectContextFactory() {
+ @Override
public ObjectContext createContext(DataChannel parent) {
return context;
}
- public ObjectContext createContext() {
+ @Override
+ public ObjectContext createContext(ObjectContext parent) {
return context;
}
};
- Module module = binder -> binder.bind(ObjectContextFactory.class).toInstance(factory);
+ // 'newContext()' resolves the DataDomain to pass to the factory, so a binding must exist
+ Module module = binder -> {
+ binder.bind(DataDomain.class).toInstance(mock(DataDomain.class));
+ binder.bind(ObjectContextFactory.class).toInstance(factory);
+ };
CayenneRuntime runtime = new CayenneRuntime(Collections.singleton(module));
assertSame(context, runtime.newContext());
@@ -116,8 +120,7 @@ public class CayenneRuntimeTest {
try {
CayenneRuntime.bindThreadInjector(injector);
assertSame(injector, CayenneRuntime.getThreadInjector());
- }
- finally {
+ } finally {
CayenneRuntime.bindThreadInjector(null);
}