You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2023/12/05 10:57:44 UTC
(cayenne) 02/02: CAY-2829 Merge `BaseContext` with `DataContext` and deprecate it
This is an automated email from the ASF dual-hosted git repository.
ntimofeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit d8941d1a89a87ad8417831be2c021d3da758ad8f
Author: stariy95 <st...@gmail.com>
AuthorDate: Tue Dec 5 14:58:54 2023 +0400
CAY-2829 Merge `BaseContext` with `DataContext` and deprecate it
---
RELEASE-NOTES.txt | 1 +
.../main/java/org/apache/cayenne/BaseContext.java | 681 +--------------------
.../org/apache/cayenne/access/DataContext.java | 629 ++++++++++++++++++-
.../{ => access}/ObjectContextDeleteAction.java | 7 +-
.../java/org/apache/cayenne/MockBaseContext.java | 124 ----
.../DataContextTest.java} | 191 +++---
.../access/NestedDataContextLocalCacheIT.java | 5 +-
7 files changed, 720 insertions(+), 918 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index c4c13a70c..ce84893f8 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -50,6 +50,7 @@ CAY-2824 Rename CayenneServerModuleProvider to CayenneRuntimeModuleProvider
CAY-2825 Rename package `o.a.c.configuration.server` to `o.a.c.configuration.runtime`
CAY-2826 Rename `ServerModule` to `CoreModule`
CAY-2828 Rename values in `org.apache.cayenne.configuration.Constants`
+CAY-2829 Merge `BaseContext` with `DataContext` and deprecate it
Bug Fixes:
diff --git a/cayenne/src/main/java/org/apache/cayenne/BaseContext.java b/cayenne/src/main/java/org/apache/cayenne/BaseContext.java
index fd8a5ea4f..d308b4fef 100644
--- a/cayenne/src/main/java/org/apache/cayenne/BaseContext.java
+++ b/cayenne/src/main/java/org/apache/cayenne/BaseContext.java
@@ -18,689 +18,16 @@
****************************************************************/
package org.apache.cayenne;
-import org.apache.cayenne.cache.NestedQueryCache;
-import org.apache.cayenne.cache.QueryCache;
-import org.apache.cayenne.runtime.CayenneRuntime;
-import org.apache.cayenne.di.Injector;
-import org.apache.cayenne.event.EventManager;
-import org.apache.cayenne.exp.ValueInjector;
-import org.apache.cayenne.graph.CompoundDiff;
-import org.apache.cayenne.graph.GraphDiff;
-import org.apache.cayenne.graph.GraphEvent;
-import org.apache.cayenne.graph.GraphManager;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.LifecycleEvent;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.query.ObjectIdQuery;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.RefreshQuery;
-import org.apache.cayenne.query.Select;
-import org.apache.cayenne.reflect.AttributeProperty;
-import org.apache.cayenne.reflect.ClassDescriptor;
-import org.apache.cayenne.reflect.PropertyDescriptor;
-import org.apache.cayenne.reflect.PropertyVisitor;
-import org.apache.cayenne.reflect.ToManyProperty;
-import org.apache.cayenne.reflect.ToOneProperty;
-import org.apache.cayenne.util.ObjectContextGraphAction;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import org.apache.cayenne.access.DataContext;
/**
* A common base superclass for Cayenne ObjectContext implementors.
*
* @since 3.0
+ * @deprecated since 5.0, use {@link DataContext} directly
*/
-public abstract class BaseContext implements ObjectContext {
-
- /**
- * A holder of a ObjectContext bound to the current thread.
- *
- * @since 3.0
- */
- protected static final ThreadLocal<ObjectContext> threadObjectContext = new ThreadLocal<ObjectContext>();
-
- /**
- * Returns the ObjectContext bound to the current thread.
- *
- * @since 3.0
- * @return the ObjectContext associated with caller thread.
- * @throws IllegalStateException
- * if there is no ObjectContext bound to the current thread.
- */
- public static ObjectContext getThreadObjectContext() throws IllegalStateException {
- ObjectContext context = threadObjectContext.get();
- if (context == null) {
- throw new IllegalStateException("Current thread has no bound ObjectContext.");
- }
-
- return context;
- }
-
- /**
- * Binds a ObjectContext to the current thread. ObjectContext can later be
- * retrieved by users in the same thread by calling
- * {@link BaseContext#getThreadObjectContext}. Using null parameter will
- * unbind currently bound ObjectContext.
- *
- * @since 3.0
- */
- public static void bindThreadObjectContext(ObjectContext context) {
- threadObjectContext.set(context);
- }
-
- // transient variables that should be reinitialized on deserialization from
- // the
- // registry
- protected transient DataChannel channel;
- protected transient QueryCache queryCache;
- protected transient EntityResolver entityResolver;
-
- protected boolean validatingObjectsOnCommit = true;
-
- /**
- * Graph action that handles property changes
- *
- * @since 3.1
- */
- protected ObjectContextGraphAction graphAction;
-
- /**
- * Stores user defined properties associated with this DataContext.
- *
- * @since 3.0
- */
- protected volatile Map<String, Object> userProperties;
-
+@Deprecated(since = "5.0", forRemoval = true)
+public abstract class BaseContext extends DataContext {
protected BaseContext() {
- graphAction = new ObjectContextGraphAction(this);
- }
-
- /**
- * Checks whether this context is attached to Cayenne runtime stack and if
- * not, attempts to attach itself to the runtime using Injector returned
- * from the call to {@link CayenneRuntime#getThreadInjector()}. If thread
- * Injector is not available and the context is not attached, throws
- * CayenneRuntimeException.
- * <p>
- * This method is called internally by the context before access to
- * transient variables to allow the context to attach to the stack lazily
- * following deserialization.
- *
- * @return true if the context successfully attached to the thread runtime,
- * false - if it was already attached.
- * @since 3.1
- */
- protected boolean attachToRuntimeIfNeeded() {
- if (channel != null) {
- return false;
- }
-
- Injector injector = CayenneRuntime.getThreadInjector();
- if (injector == null) {
- throw new CayenneRuntimeException("Can't attach to Cayenne runtime. "
- + "Null injector returned from CayenneRuntime.getThreadInjector()");
- }
-
- attachToRuntime(injector);
- return true;
- }
-
- /**
- * Attaches this context to the CayenneRuntime whose Injector is passed as
- * an argument to this method.
- *
- * @since 3.1
- */
- protected void attachToRuntime(Injector injector) {
-
- // TODO: nested contexts handling??
- attachToChannel(injector.getInstance(DataChannel.class));
- setQueryCache(new NestedQueryCache(injector.getInstance(QueryCache.class)));
- }
-
- /**
- * Attaches to a provided DataChannel.
- *
- * @since 3.1
- */
- protected void attachToChannel(DataChannel channel) {
- if (channel == null) {
- throw new NullPointerException("Null channel");
- }
-
- setChannel(channel);
- setEntityResolver(channel.getEntityResolver());
- }
-
- @Override
- public abstract void commitChanges();
-
- @Override
- public abstract void commitChangesToParent();
-
- @Override
- public void deleteObject(Object object) throws DeleteDenyException {
- deleteObjects(object);
- }
-
- @Override
- public abstract Collection<?> deletedObjects();
-
- @Override
- public DataChannel getChannel() {
- attachToRuntimeIfNeeded();
- return channel;
- }
-
- /**
- * Sets a new DataChannel for this context.
- *
- * @since 3.1
- */
- public void setChannel(DataChannel channel) {
- this.channel = channel;
- }
-
- @Override
- public EntityResolver getEntityResolver() {
- attachToRuntimeIfNeeded();
- return entityResolver;
- }
-
- /**
- * @since 3.1
- */
- public void setEntityResolver(EntityResolver entityResolver) {
- this.entityResolver = entityResolver;
- }
-
- /**
- * Returns whether this ObjectContext performs object validation before
- * commit is executed.
- *
- * @since 1.1
- */
- public boolean isValidatingObjectsOnCommit() {
- return validatingObjectsOnCommit;
- }
-
- /**
- * Sets the property defining whether this ObjectContext should perform
- * object validation before commit is executed.
- *
- * @since 1.1
- */
- public void setValidatingObjectsOnCommit(boolean flag) {
- this.validatingObjectsOnCommit = flag;
- }
-
- /**
- * @since 3.1
- */
- @Override
- public <T extends Persistent> T localObject(T objectFromAnotherContext) {
-
- if (objectFromAnotherContext == null) {
- throw new NullPointerException("Null object argument");
- }
-
- ObjectId id = objectFromAnotherContext.getObjectId();
-
- // first look for the ID in the local GraphManager
- synchronized (getGraphManager()) {
- @SuppressWarnings("unchecked")
- T localObject = (T) getGraphManager().getNode(id);
- if (localObject != null) {
- return localObject;
- }
-
- // create a hollow object, optimistically assuming that the ID we got from
- // 'objectFromAnotherContext' is a valid ID either in the parent context or in the DB.
- // This essentially defers possible FaultFailureExceptions.
-
- ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(id.getEntityName());
- @SuppressWarnings("unchecked")
- T persistent = (T) descriptor.createObject();
-
- persistent.setObjectContext(this);
- persistent.setObjectId(id);
- persistent.setPersistenceState(PersistenceState.HOLLOW);
-
- getGraphManager().registerNode(id, persistent);
-
- return persistent;
- }
- }
-
- @Override
- public abstract GraphManager getGraphManager();
-
- @Override
- public abstract Collection<?> modifiedObjects();
-
- @Override
- public abstract <T> T newObject(Class<T> persistentClass);
-
- @Override
- public abstract void registerNewObject(Object object);
-
- @Override
- public abstract Collection<?> newObjects();
-
- @Override
- public abstract QueryResponse performGenericQuery(Query query);
-
- @Override
- public abstract List performQuery(Query query);
-
- /**
- * @since 4.0
- */
- @SuppressWarnings("unchecked")
- @Override
- public <T> List<T> select(Select<T> query) {
- return performQuery(query);
- }
-
- /**
- * @since 4.0
- */
- @Override
- public <T> T selectOne(Select<T> query) {
- List<T> objects = select(query);
-
- if (objects.size() == 0) {
- return null;
- } else if (objects.size() > 1) {
- throw new CayenneRuntimeException("Expected zero or one object, instead query matched: %d", objects.size());
- }
-
- return objects.get(0);
- }
-
- /**
- * @since 4.0
- */
- @Override
- public <T> T selectFirst(Select<T> query) {
- List<T> objects = select(query);
-
- return (objects == null || objects.isEmpty()) ? null : objects.get(0);
- }
-
- /**
- * @since 4.0
- */
- @Override
- public <T> void iterate(Select<T> query, ResultIteratorCallback<T> callback) {
-
- try (ResultIterator<T> it = iterator(query);) {
- for (T t : it) {
- callback.next(t);
- }
- }
- }
-
- @Override
- public abstract <T> ResultIterator<T> iterator(Select<T> query);
-
- @Override
- public <T> ResultBatchIterator<T> batchIterator(Select<T> query, int size) {
- return new ResultBatchIterator<T>(iterator(query), size);
- }
-
- @Override
- public void prepareForAccess(Persistent object, String property, boolean lazyFaulting) {
- if (object.getPersistenceState() == PersistenceState.HOLLOW) {
-
- ObjectId oid = object.getObjectId();
- List<?> objects = performQuery(new ObjectIdQuery(oid, false, ObjectIdQuery.CACHE));
-
- if (objects.size() == 0) {
- throw new FaultFailureException(
- "Error resolving fault, no matching row exists in the database for ObjectId: " + oid);
- } else if (objects.size() > 1) {
- throw new FaultFailureException(
- "Error resolving fault, more than one row exists in the database for ObjectId: " + oid);
- }
-
- // 5/28/2013 - Commented out this block to allow for modifying
- // objects in the postLoad callback
- // sanity check...
- // if (object.getPersistenceState() != PersistenceState.COMMITTED) {
- //
- // String state =
- // PersistenceState.persistenceStateName(object.getPersistenceState());
- //
- // // TODO: andrus 4/13/2006, modified and deleted states are
- // // possible due to
- // // a race condition, should we handle them here?
- // throw new
- // FaultFailureException("Error resolving fault for ObjectId: " +
- // oid + " and state (" + state
- // +
- // "). Possible cause - matching row is missing from the database.");
- // }
- }
-
- // resolve relationship fault
- if (lazyFaulting && property != null) {
- ClassDescriptor classDescriptor = getEntityResolver().getClassDescriptor(
- object.getObjectId().getEntityName());
- PropertyDescriptor propertyDescriptor = classDescriptor.getProperty(property);
-
- // If we don't have a property descriptor, there's not much we can
- // do.
- // Let the caller know that the specified property could not be
- // found and list
- // all of the properties that could be so the caller knows what can
- // be used.
- if (propertyDescriptor == null) {
- final StringBuilder errorMessage = new StringBuilder();
-
- errorMessage.append(String.format("Property '%s' is not declared for entity '%s'.", property, object
- .getObjectId().getEntityName()));
-
- errorMessage.append(" Declared properties are: ");
-
- // Grab each of the declared properties.
- final List<String> properties = new ArrayList<>();
- classDescriptor.visitProperties(new PropertyVisitor() {
- @Override
- public boolean visitAttribute(final AttributeProperty property) {
- properties.add(property.getName());
-
- return true;
- }
-
- @Override
- public boolean visitToOne(final ToOneProperty property) {
- properties.add(property.getName());
-
- return true;
- }
-
- @Override
- public boolean visitToMany(final ToManyProperty property) {
- properties.add(property.getName());
-
- return true;
- }
- });
-
- // Now add the declared property names to the error message.
- boolean first = true;
- for (String declaredProperty : properties) {
- if (first) {
- errorMessage.append(String.format("'%s'", declaredProperty));
-
- first = false;
- } else {
- errorMessage.append(String.format(", '%s'", declaredProperty));
- }
- }
-
- errorMessage.append(".");
-
- throw new CayenneRuntimeException(errorMessage.toString());
- }
-
- // this should trigger fault resolving
- propertyDescriptor.readProperty(object);
- }
- }
-
- @Override
- public void propertyChanged(Persistent object, String property, Object oldValue, Object newValue) {
- graphAction.handlePropertyChange(object, property, oldValue, newValue);
- }
-
- @Override
- public abstract void rollbackChanges();
-
- @Override
- public abstract void rollbackChangesLocally();
-
- @Override
- public abstract Collection<?> uncommittedObjects();
-
- public QueryCache getQueryCache() {
- attachToRuntimeIfNeeded();
- return queryCache;
- }
-
- /**
- * Sets a QueryCache to be used for storing cached query results.
- */
- public void setQueryCache(QueryCache queryCache) {
- this.queryCache = queryCache;
- }
-
- /**
- * Returns EventManager associated with the ObjectStore.
- *
- * @since 1.2
- */
- @Override
- public EventManager getEventManager() {
- return channel != null ? channel.getEventManager() : null;
- }
-
- @Override
- public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType) {
- switch (syncType) {
- case DataChannel.ROLLBACK_CASCADE_SYNC:
- return onContextRollback(originatingContext);
- 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 onContextRollback(ObjectContext originatingContext) {
- rollbackChanges();
- return new CompoundDiff();
- }
-
- protected abstract GraphDiff onContextFlush(ObjectContext originatingContext, GraphDiff changes, boolean cascade);
-
- /**
- * @since 1.2
- */
- protected void fireDataChannelCommitted(Object postedBy, GraphDiff changes) {
- EventManager manager = getEventManager();
-
- if (manager != null) {
- GraphEvent e = new GraphEvent(this, postedBy, changes);
- manager.postEvent(e, DataChannel.GRAPH_FLUSHED_SUBJECT);
- }
- }
-
- /**
- * @since 1.2
- */
- protected void fireDataChannelRolledback(Object postedBy, GraphDiff changes) {
- EventManager manager = getEventManager();
-
- if (manager != null) {
- GraphEvent e = new GraphEvent(this, postedBy, changes);
- manager.postEvent(e, DataChannel.GRAPH_ROLLEDBACK_SUBJECT);
- }
- }
-
- /**
- * @since 1.2
- */
- protected void fireDataChannelChanged(Object postedBy, GraphDiff changes) {
- EventManager manager = getEventManager();
-
- if (manager != null) {
- GraphEvent e = new GraphEvent(this, postedBy, changes);
- manager.postEvent(e, DataChannel.GRAPH_CHANGED_SUBJECT);
- }
- }
-
- @Override
- public void invalidateObjects(Collection<?> objects) {
-
- // don't allow null collections as a matter of coding discipline
- if (objects == null) {
- throw new NullPointerException("Null collection of objects to invalidate");
- }
-
- if (!objects.isEmpty()) {
- performGenericQuery(new RefreshQuery(objects));
- }
- }
-
- /**
- * @since 3.1
- */
- @Override
- public <T> void invalidateObjects(T... objects) {
- if (objects != null && objects.length > 0) {
- performGenericQuery(new RefreshQuery(Arrays.asList(objects)));
- }
- }
-
- /**
- * Returns a map of user-defined properties associated with this
- * DataContext.
- *
- * @since 3.0
- */
- protected Map<String, Object> getUserProperties() {
-
- // as not all users will take advantage of properties, creating the
- // map on demand to keep the context lean...
- if (userProperties == null) {
- synchronized (this) {
- if (userProperties == null) {
- userProperties = new ConcurrentHashMap<>();
- }
- }
- }
-
- return userProperties;
- }
-
- /**
- * Returns a user-defined property previously set via 'setUserProperty'.
- * Note that it is a caller responsibility to synchronize access to
- * properties.
- *
- * @since 3.0
- */
- @Override
- public Object getUserProperty(String key) {
- return getUserProperties().get(key);
- }
-
- /**
- * Sets a user-defined property. Note that it is a caller responsibility to
- * synchronize access to properties.
- *
- * @since 3.0
- */
- @Override
- public void setUserProperty(String key, Object value) {
- getUserProperties().put(key, value);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 5.0
- */
- @Override
- public void removeUserProperty(String key) {
- getUserProperties().remove(key);
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 5.0
- */
- @Override
- public void clearUserProperties() {
- getUserProperties().clear();
- }
-
- /**
- * If ObjEntity qualifier is set, asks it to inject initial value to an
- * object. Also performs all Persistent initialization operations
- */
- protected void injectInitialValue(Object obj) {
- // 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();
- synchronized (graphManager) {
- graphManager.registerNode(object.getObjectId(), object);
- graphManager.nodeCreated(object.getObjectId());
- }
-
- ObjEntity entity;
- try {
- entity = getEntityResolver().getObjEntity(object.getClass());
- } catch (CayenneRuntimeException ex) {
- // ObjEntity cannot be fetched, ignored
- entity = null;
- }
-
- if (entity != null) {
- if (entity.getDeclaredQualifier() instanceof ValueInjector) {
- ((ValueInjector) entity.getDeclaredQualifier()).injectValue(object);
- }
- }
-
- // invoke callbacks
- getEntityResolver().getCallbackRegistry().performCallbacks(LifecycleEvent.POST_ADD, object);
- }
-
- /**
- * @since 3.1
- */
- @Override
- public <T> void deleteObjects(T... objects) throws DeleteDenyException {
- if (objects == null || objects.length == 0) {
- return;
- }
-
- ObjectContextDeleteAction action = new ObjectContextDeleteAction(this);
-
- for (Object object : objects) {
- action.performDelete((Persistent) object);
- }
- }
-
- @Override
- public void deleteObjects(Collection<?> objects) throws DeleteDenyException {
- if (objects.isEmpty()) {
- return;
- }
-
- ObjectContextDeleteAction action = new ObjectContextDeleteAction(this);
-
- // Make a copy to iterate over to avoid ConcurrentModificationException.
- List<Object> copy = new ArrayList<>(objects);
- for (Object object : copy) {
- action.performDelete((Persistent) object);
- }
}
}
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 4206edfd5..6cf895aa2 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java
@@ -23,48 +23,59 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
-import org.apache.cayenne.BaseContext;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.DataChannel;
import org.apache.cayenne.DataObject;
import org.apache.cayenne.DataRow;
+import org.apache.cayenne.DeleteDenyException;
import org.apache.cayenne.Fault;
+import org.apache.cayenne.FaultFailureException;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.PersistenceState;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.QueryResponse;
+import org.apache.cayenne.ResultBatchIterator;
import org.apache.cayenne.ResultIterator;
-import org.apache.cayenne.access.util.IteratedSelectObserver;
+import org.apache.cayenne.ResultIteratorCallback;
+import org.apache.cayenne.cache.NestedQueryCache;
+import org.apache.cayenne.cache.QueryCache;
import org.apache.cayenne.di.Injector;
import org.apache.cayenne.event.EventManager;
+import org.apache.cayenne.exp.ValueInjector;
import org.apache.cayenne.graph.ArcId;
import org.apache.cayenne.graph.ChildDiffLoader;
import org.apache.cayenne.graph.CompoundDiff;
import org.apache.cayenne.graph.GraphDiff;
+import org.apache.cayenne.graph.GraphEvent;
import org.apache.cayenne.graph.GraphManager;
import org.apache.cayenne.map.DbJoin;
import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.LifecycleEvent;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
import org.apache.cayenne.query.*;
import org.apache.cayenne.reflect.AttributeProperty;
import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.reflect.PropertyDescriptor;
import org.apache.cayenne.reflect.PropertyVisitor;
import org.apache.cayenne.reflect.ToManyProperty;
import org.apache.cayenne.reflect.ToOneProperty;
-import org.apache.cayenne.tx.BaseTransaction;
-import org.apache.cayenne.tx.Transaction;
+import org.apache.cayenne.runtime.CayenneRuntime;
import org.apache.cayenne.tx.TransactionFactory;
import org.apache.cayenne.util.EventUtil;
import org.apache.cayenne.util.GenericResponse;
+import org.apache.cayenne.util.ObjectContextGraphAction;
import org.apache.cayenne.util.Util;
/**
@@ -73,12 +84,68 @@ import org.apache.cayenne.util.Util;
* changes to persistent objects that are registered with the context, are not
* visible to the users of other contexts.
*/
-public class DataContext extends BaseContext {
+public class DataContext implements ObjectContext {
+
+ /**
+ * A holder of a ObjectContext bound to the current thread.
+ *
+ * @since 3.0
+ */
+ protected static final ThreadLocal<ObjectContext> threadObjectContext = new ThreadLocal<ObjectContext>();
+
+ /**
+ * Returns the ObjectContext bound to the current thread.
+ *
+ * @since 3.0
+ * @return the ObjectContext associated with caller thread.
+ * @throws IllegalStateException
+ * if there is no ObjectContext bound to the current thread.
+ */
+ public static ObjectContext getThreadObjectContext() throws IllegalStateException {
+ ObjectContext context = threadObjectContext.get();
+ if (context == null) {
+ throw new IllegalStateException("Current thread has no bound ObjectContext.");
+ }
+
+ return context;
+ }
+
+ /**
+ * Binds a ObjectContext to the current thread. ObjectContext can later be
+ * retrieved by users in the same thread by calling
+ * {@link #getThreadObjectContext}. Using null parameter will
+ * unbind currently bound ObjectContext.
+ *
+ * @since 3.0
+ */
+ public static void bindThreadObjectContext(ObjectContext context) {
+ threadObjectContext.set(context);
+ }
+
private DataContextDelegate delegate;
- protected boolean usingSharedSnaphsotCache;
+ protected boolean usingSharedSnapshotCache;
protected ObjectStore objectStore;
+ /**
+ * Graph action that handles property changes
+ *
+ * @since 3.1
+ */
+ protected ObjectContextGraphAction graphAction;
+
+ /**
+ * Stores user defined properties associated with this DataContext.
+ *
+ * @since 3.0
+ */
+ protected volatile Map<String, Object> userProperties;
+
+ // transient variables that should be reinitialized on deserialization from the registry
+ protected transient DataChannel channel;
+ protected transient QueryCache queryCache;
+ protected transient EntityResolver entityResolver;
+
/**
* @deprecated since 4.0 used in a method that itself should be deprecated,
* so this is a temp code
@@ -88,6 +155,8 @@ public class DataContext extends BaseContext {
protected transient DataContextMergeHandler mergeHandler;
+ protected boolean validatingObjectsOnCommit = true;
+
/**
* Creates a new DataContext that is not attached to the Cayenne stack.
*/
@@ -102,6 +171,8 @@ public class DataContext extends BaseContext {
*/
public DataContext(DataChannel channel, ObjectStore objectStore) {
+ graphAction = new ObjectContextGraphAction(this);
+
// inject self as parent context
if (objectStore != null) {
this.objectStore = objectStore;
@@ -114,24 +185,66 @@ public class DataContext extends BaseContext {
if (objectStore != null) {
DataDomain domain = getParentDataDomain();
- this.usingSharedSnaphsotCache = domain != null
+ this.usingSharedSnapshotCache = domain != null
&& objectStore.getDataRowCache() == domain.getSharedSnapshotCache();
}
}
- @Override
+ /**
+ * Checks whether this context is attached to Cayenne runtime stack and if
+ * not, attempts to attach itself to the runtime using Injector returned
+ * from the call to {@link CayenneRuntime#getThreadInjector()}. If thread
+ * Injector is not available and the context is not attached, throws
+ * CayenneRuntimeException.
+ * <p>
+ * This method is called internally by the context before access to
+ * transient variables to allow the context to attach to the stack lazily
+ * following deserialization.
+ *
+ * @return true if the context successfully attached to the thread runtime,
+ * false - if it was already attached.
+ * @since 3.1
+ */
+ protected boolean attachToRuntimeIfNeeded() {
+ if (channel != null) {
+ return false;
+ }
+
+ Injector injector = CayenneRuntime.getThreadInjector();
+ if (injector == null) {
+ throw new CayenneRuntimeException("Can't attach to Cayenne runtime. "
+ + "Null injector returned from CayenneRuntime.getThreadInjector()");
+ }
+
+ attachToRuntime(injector);
+ return true;
+ }
+
+ /**
+ * Attaches this context to the CayenneRuntime whose Injector is passed as
+ * an argument to this method.
+ *
+ * @since 3.1
+ */
protected void attachToRuntime(Injector injector) {
- super.attachToRuntime(injector);
+ attachToChannel(injector.getInstance(DataChannel.class));
+ setQueryCache(new NestedQueryCache(injector.getInstance(QueryCache.class)));
this.transactionFactory = injector.getInstance(TransactionFactory.class);
}
/**
+ * Attaches to a provided DataChannel.
+ *
* @since 3.1
*/
- @Override
protected void attachToChannel(DataChannel channel) {
- super.attachToChannel(channel);
+ if (channel == null) {
+ throw new NullPointerException("Null channel");
+ }
+
+ setChannel(channel);
+ setEntityResolver(channel.getEntityResolver());
if (mergeHandler != null) {
mergeHandler.setActive(false);
@@ -150,7 +263,7 @@ public class DataContext extends BaseContext {
EventUtil.listenForChannelEvents(channel, mergeHandler);
}
- if (!usingSharedSnaphsotCache && getObjectStore() != null) {
+ if (!usingSharedSnapshotCache && getObjectStore() != null) {
DataRowStore cache = getObjectStore().getDataRowCache();
if (cache != null) {
@@ -159,6 +272,35 @@ public class DataContext extends BaseContext {
}
}
+ @Override
+ public DataChannel getChannel() {
+ attachToRuntimeIfNeeded();
+ return channel;
+ }
+
+ /**
+ * Sets a new DataChannel for this context.
+ *
+ * @since 3.1
+ */
+ public void setChannel(DataChannel channel) {
+ this.channel = channel;
+ }
+
+ @Override
+ public EntityResolver getEntityResolver() {
+ attachToRuntimeIfNeeded();
+ return entityResolver;
+ }
+
+ /**
+ * @since 3.1
+ */
+ public void setEntityResolver(EntityResolver entityResolver) {
+ this.entityResolver = entityResolver;
+ }
+
+
/**
* Returns a DataDomain used by this DataContext. DataDomain is looked up in
* the DataChannel hierarchy. If a channel is not a DataDomain or a
@@ -251,6 +393,42 @@ public class DataContext extends BaseContext {
return getObjectStore().objectsInState(PersistenceState.DELETED);
}
+ @Override
+ public void deleteObject(Object object) throws DeleteDenyException {
+ deleteObjects(object);
+ }
+
+ /**
+ * @since 3.1
+ */
+ @Override
+ public <T> void deleteObjects(T... objects) throws DeleteDenyException {
+ if (objects == null || objects.length == 0) {
+ return;
+ }
+
+ ObjectContextDeleteAction action = new ObjectContextDeleteAction(this);
+
+ for (Object object : objects) {
+ action.performDelete((Persistent) object);
+ }
+ }
+
+ @Override
+ public void deleteObjects(Collection<?> objects) throws DeleteDenyException {
+ if (objects.isEmpty()) {
+ return;
+ }
+
+ ObjectContextDeleteAction action = new ObjectContextDeleteAction(this);
+
+ // Make a copy to iterate over to avoid ConcurrentModificationException.
+ List<Object> copy = new ArrayList<>(objects);
+ for (Object object : copy) {
+ action.performDelete((Persistent) object);
+ }
+ }
+
/**
* Returns a list of objects that are registered with this DataContext and
* have a state PersistenceState.MODIFIED
@@ -290,6 +468,90 @@ public class DataContext extends BaseContext {
return objects;
}
+ public QueryCache getQueryCache() {
+ attachToRuntimeIfNeeded();
+ return queryCache;
+ }
+
+ /**
+ * Sets a QueryCache to be used for storing cached query results.
+ */
+ public void setQueryCache(QueryCache queryCache) {
+ this.queryCache = queryCache;
+ }
+
+ /**
+ * Returns EventManager associated with the ObjectStore.
+ *
+ * @since 1.2
+ */
+ @Override
+ public EventManager getEventManager() {
+ return channel != null ? channel.getEventManager() : null;
+ }
+
+ /**
+ * @since 1.2
+ */
+ protected void fireDataChannelCommitted(Object postedBy, GraphDiff changes) {
+ EventManager manager = getEventManager();
+
+ if (manager != null) {
+ GraphEvent e = new GraphEvent(this, postedBy, changes);
+ manager.postEvent(e, DataChannel.GRAPH_FLUSHED_SUBJECT);
+ }
+ }
+
+ /**
+ * @since 1.2
+ */
+ protected void fireDataChannelRolledback(Object postedBy, GraphDiff changes) {
+ EventManager manager = getEventManager();
+
+ if (manager != null) {
+ GraphEvent e = new GraphEvent(this, postedBy, changes);
+ manager.postEvent(e, DataChannel.GRAPH_ROLLEDBACK_SUBJECT);
+ }
+ }
+
+ /**
+ * @since 3.1
+ */
+ @Override
+ public <T extends Persistent> T localObject(T objectFromAnotherContext) {
+
+ if (objectFromAnotherContext == null) {
+ throw new NullPointerException("Null object argument");
+ }
+
+ ObjectId id = objectFromAnotherContext.getObjectId();
+
+ // first look for the ID in the local GraphManager
+ synchronized (getGraphManager()) {
+ @SuppressWarnings("unchecked")
+ T localObject = (T) getGraphManager().getNode(id);
+ if (localObject != null) {
+ return localObject;
+ }
+
+ // create a hollow object, optimistically assuming that the ID we got from
+ // 'objectFromAnotherContext' is a valid ID either in the parent context or in the DB.
+ // This essentially defers possible FaultFailureExceptions.
+
+ ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(id.getEntityName());
+ @SuppressWarnings("unchecked")
+ T persistent = (T) descriptor.createObject();
+
+ persistent.setObjectContext(this);
+ persistent.setObjectId(id);
+ persistent.setPersistenceState(PersistenceState.HOLLOW);
+
+ getGraphManager().registerNode(id, persistent);
+
+ return persistent;
+ }
+ }
+
/**
* Returns a DataRow reflecting current, possibly uncommitted, object state.
* <p>
@@ -399,6 +661,107 @@ public class DataContext extends BaseContext {
return snapshot;
}
+ @Override
+ public void prepareForAccess(Persistent object, String property, boolean lazyFaulting) {
+ if (object.getPersistenceState() == PersistenceState.HOLLOW) {
+
+ ObjectId oid = object.getObjectId();
+ List<?> objects = performQuery(new ObjectIdQuery(oid, false, ObjectIdQuery.CACHE));
+
+ if (objects.size() == 0) {
+ throw new FaultFailureException(
+ "Error resolving fault, no matching row exists in the database for ObjectId: " + oid);
+ } else if (objects.size() > 1) {
+ throw new FaultFailureException(
+ "Error resolving fault, more than one row exists in the database for ObjectId: " + oid);
+ }
+
+ // 5/28/2013 - Commented out this block to allow for modifying
+ // objects in the postLoad callback
+ // sanity check...
+ // if (object.getPersistenceState() != PersistenceState.COMMITTED) {
+ //
+ // String state =
+ // PersistenceState.persistenceStateName(object.getPersistenceState());
+ //
+ // // TODO: andrus 4/13/2006, modified and deleted states are
+ // // possible due to
+ // // a race condition, should we handle them here?
+ // throw new
+ // FaultFailureException("Error resolving fault for ObjectId: " +
+ // oid + " and state (" + state
+ // +
+ // "). Possible cause - matching row is missing from the database.");
+ // }
+ }
+
+ // resolve relationship fault
+ if (lazyFaulting && property != null) {
+ ClassDescriptor classDescriptor = getEntityResolver().getClassDescriptor(
+ object.getObjectId().getEntityName());
+ PropertyDescriptor propertyDescriptor = classDescriptor.getProperty(property);
+
+ // If we don't have a property descriptor, there's not much we can
+ // do.
+ // Let the caller know that the specified property could not be
+ // found and list
+ // all of the properties that could be so the caller knows what can
+ // be used.
+ if (propertyDescriptor == null) {
+ final StringBuilder errorMessage = new StringBuilder();
+
+ errorMessage.append(String.format("Property '%s' is not declared for entity '%s'.", property, object
+ .getObjectId().getEntityName()));
+
+ errorMessage.append(" Declared properties are: ");
+
+ // Grab each of the declared properties.
+ final List<String> properties = new ArrayList<>();
+ classDescriptor.visitProperties(new PropertyVisitor() {
+ @Override
+ public boolean visitAttribute(final AttributeProperty property) {
+ properties.add(property.getName());
+
+ return true;
+ }
+
+ @Override
+ public boolean visitToOne(final ToOneProperty property) {
+ properties.add(property.getName());
+
+ return true;
+ }
+
+ @Override
+ public boolean visitToMany(final ToManyProperty property) {
+ properties.add(property.getName());
+
+ return true;
+ }
+ });
+
+ // Now add the declared property names to the error message.
+ boolean first = true;
+ for (String declaredProperty : properties) {
+ if (first) {
+ errorMessage.append(String.format("'%s'", declaredProperty));
+
+ first = false;
+ } else {
+ errorMessage.append(String.format(", '%s'", declaredProperty));
+ }
+ }
+
+ errorMessage.append(".");
+
+ throw new CayenneRuntimeException(errorMessage.toString());
+ }
+
+ // this should trigger fault resolving
+ propertyDescriptor.readProperty(object);
+ }
+ }
+
/**
* Converts a list of DataRows to a List of DataObject registered with this
* DataContext.
@@ -604,6 +967,44 @@ public class DataContext extends BaseContext {
});
}
+ /**
+ * If ObjEntity qualifier is set, asks it to inject initial value to an
+ * object. Also performs all Persistent initialization operations
+ */
+ protected void injectInitialValue(Object obj) {
+ // 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();
+ synchronized (graphManager) {
+ graphManager.registerNode(object.getObjectId(), object);
+ graphManager.nodeCreated(object.getObjectId());
+ }
+
+ ObjEntity entity;
+ try {
+ entity = getEntityResolver().getObjEntity(object.getClass());
+ } catch (CayenneRuntimeException ex) {
+ // ObjEntity cannot be fetched, ignored
+ entity = null;
+ }
+
+ if (entity != null) {
+ if (entity.getDeclaredQualifier() instanceof ValueInjector) {
+ ((ValueInjector) entity.getDeclaredQualifier()).injectValue(object);
+ }
+ }
+
+ // invoke callbacks
+ getEntityResolver().getCallbackRegistry().performCallbacks(LifecycleEvent.POST_ADD, object);
+ }
+
/**
* Unregisters a Collection of DataObjects from the DataContext and the
* underlying ObjectStore. This operation also unsets DataContext for
@@ -615,6 +1016,34 @@ public class DataContext extends BaseContext {
getObjectStore().objectsUnregistered(dataObjects);
}
+ @Override
+ public void invalidateObjects(Collection<?> objects) {
+
+ // don't allow null collections as a matter of coding discipline
+ if (objects == null) {
+ throw new NullPointerException("Null collection of objects to invalidate");
+ }
+
+ if (!objects.isEmpty()) {
+ performGenericQuery(new RefreshQuery(objects));
+ }
+ }
+
+ /**
+ * @since 3.1
+ */
+ @Override
+ public <T> void invalidateObjects(T... objects) {
+ if (objects != null && objects.length > 0) {
+ performGenericQuery(new RefreshQuery(Arrays.asList(objects)));
+ }
+ }
+
+ @Override
+ public void propertyChanged(Persistent object, String property, Object oldValue, Object newValue) {
+ graphAction.handlePropertyChange(object, property, oldValue, newValue);
+ }
+
/**
* If the parent channel is a DataContext, reverts local changes to make
* this context look like the parent, if the parent channel is a DataDomain,
@@ -685,7 +1114,6 @@ public class DataContext extends BaseContext {
flushToParent(true);
}
- @Override
protected GraphDiff onContextFlush(ObjectContext originatingContext, GraphDiff changes, boolean cascade) {
boolean childContext = this != originatingContext && changes != null;
@@ -792,6 +1220,54 @@ public class DataContext extends BaseContext {
}
+ /**
+ * @since 4.0
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> List<T> select(Select<T> query) {
+ return performQuery(query);
+ }
+
+ /**
+ * @since 4.0
+ */
+ @Override
+ public <T> T selectOne(Select<T> query) {
+ List<T> objects = select(query);
+
+ if (objects.size() == 0) {
+ return null;
+ } else if (objects.size() > 1) {
+ throw new CayenneRuntimeException("Expected zero or one object, instead query matched: %d", objects.size());
+ }
+
+ return objects.get(0);
+ }
+
+ /**
+ * @since 4.0
+ */
+ @Override
+ public <T> T selectFirst(Select<T> query) {
+ List<T> objects = select(query);
+
+ return (objects == null || objects.isEmpty()) ? null : objects.get(0);
+ }
+
+ /**
+ * @since 4.0
+ */
+ @Override
+ public <T> void iterate(Select<T> query, ResultIteratorCallback<T> callback) {
+
+ try (ResultIterator<T> it = iterator(query);) {
+ for (T t : it) {
+ callback.next(t);
+ }
+ }
+ }
+
/**
* Performs a single database select query returning result as a {@link ResultIterator}.
* <p>
@@ -894,6 +1370,25 @@ public class DataContext extends BaseContext {
return new DataContextQueryAction(this, context, query).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 onContextRollback() {
+ rollbackChanges();
+ return new CompoundDiff();
+ }
+
/**
* Performs a single database query that does not select rows. Returns an
* array of update counts.
@@ -941,7 +1436,7 @@ public class DataContext extends BaseContext {
* @since 1.1
*/
public List<?> performQuery(String queryName, boolean expireCachedLists) {
- return performQuery(queryName, Collections.EMPTY_MAP, expireCachedLists);
+ return performQuery(queryName, Collections.emptyMap(), expireCachedLists);
}
/**
@@ -974,14 +1469,14 @@ public class DataContext extends BaseContext {
* @since 1.1
*/
public boolean isUsingSharedSnapshotCache() {
- return usingSharedSnaphsotCache;
+ return usingSharedSnapshotCache;
}
/**
* @since 3.1
*/
public void setUsingSharedSnapshotCache(boolean flag) {
- this.usingSharedSnaphsotCache = flag;
+ this.usingSharedSnapshotCache = flag;
}
// ---------------------------------------------
@@ -1103,11 +1598,16 @@ public class DataContext extends BaseContext {
}
- // this completely meaningless override is needed to expose the method as
- // package-private ... is there a better way?
- @Override
+ /**
+ * @since 1.2
+ */
protected void fireDataChannelChanged(Object postedBy, GraphDiff changes) {
- super.fireDataChannelChanged(postedBy, changes);
+ EventManager manager = getEventManager();
+
+ if (manager != null) {
+ GraphEvent e = new GraphEvent(this, postedBy, changes);
+ manager.postEvent(e, DataChannel.GRAPH_CHANGED_SUBJECT);
+ }
}
TransactionFactory getTransactionFactory() {
@@ -1125,4 +1625,93 @@ public class DataContext extends BaseContext {
this.transactionFactory = transactionFactory;
}
+ @Override
+ public <T> ResultBatchIterator<T> batchIterator(Select<T> query, int size) {
+ return new ResultBatchIterator<T>(iterator(query), size);
+ }
+
+ /**
+ * Returns whether this ObjectContext performs object validation before
+ * commit is executed.
+ *
+ * @since 1.1
+ */
+ public boolean isValidatingObjectsOnCommit() {
+ return validatingObjectsOnCommit;
+ }
+
+ /**
+ * Sets the property defining whether this ObjectContext should perform
+ * object validation before commit is executed.
+ *
+ * @since 1.1
+ */
+ public void setValidatingObjectsOnCommit(boolean flag) {
+ this.validatingObjectsOnCommit = flag;
+ }
+
+ /**
+ * Returns a map of user-defined properties associated with this
+ * DataContext.
+ *
+ * @since 3.0
+ */
+ protected Map<String, Object> getUserProperties() {
+
+ // as not all users will take advantage of properties, creating the
+ // map on demand to keep the context lean...
+ if (userProperties == null) {
+ synchronized (this) {
+ if (userProperties == null) {
+ userProperties = new ConcurrentHashMap<>();
+ }
+ }
+ }
+
+ return userProperties;
+ }
+
+ /**
+ * Returns a user-defined property previously set via 'setUserProperty'.
+ * Note that it is a caller responsibility to synchronize access to
+ * properties.
+ *
+ * @since 3.0
+ */
+ @Override
+ public Object getUserProperty(String key) {
+ return getUserProperties().get(key);
+ }
+
+ /**
+ * Sets a user-defined property. Note that it is a caller responsibility to
+ * synchronize access to properties.
+ *
+ * @since 3.0
+ */
+ @Override
+ public void setUserProperty(String key, Object value) {
+ getUserProperties().put(key, value);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since 5.0
+ */
+ @Override
+ public void removeUserProperty(String key) {
+ getUserProperties().remove(key);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since 5.0
+ */
+ @Override
+ public void clearUserProperties() {
+ getUserProperties().clear();
+ }
+
}
diff --git a/cayenne/src/main/java/org/apache/cayenne/ObjectContextDeleteAction.java b/cayenne/src/main/java/org/apache/cayenne/access/ObjectContextDeleteAction.java
similarity index 97%
rename from cayenne/src/main/java/org/apache/cayenne/ObjectContextDeleteAction.java
rename to cayenne/src/main/java/org/apache/cayenne/access/ObjectContextDeleteAction.java
index e017c9660..430586c58 100644
--- a/cayenne/src/main/java/org/apache/cayenne/ObjectContextDeleteAction.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/ObjectContextDeleteAction.java
@@ -17,13 +17,18 @@
* under the License.
****************************************************************/
-package org.apache.cayenne;
+package org.apache.cayenne.access;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.DeleteDenyException;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.PersistenceState;
+import org.apache.cayenne.Persistent;
import org.apache.cayenne.graph.ArcId;
import org.apache.cayenne.map.DeleteRule;
import org.apache.cayenne.map.LifecycleEvent;
diff --git a/cayenne/src/test/java/org/apache/cayenne/MockBaseContext.java b/cayenne/src/test/java/org/apache/cayenne/MockBaseContext.java
deleted file mode 100644
index 30ef33769..000000000
--- a/cayenne/src/test/java/org/apache/cayenne/MockBaseContext.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*****************************************************************
- * 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;
-
-import org.apache.cayenne.graph.GraphDiff;
-import org.apache.cayenne.graph.GraphManager;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.Select;
-
-import java.util.Collection;
-import java.util.List;
-
-public class MockBaseContext extends BaseContext {
-
- @Override
- public void commitChanges() {
- }
-
- @Override
- public void commitChangesToParent() {
- }
-
- @Override
- public Collection<?> deletedObjects() {
- return null;
- }
-
- @Override
- public EntityResolver getEntityResolver() {
- return null;
- }
-
- @Override
- public GraphManager getGraphManager() {
- return null;
- }
-
- @Override
- public Collection<?> modifiedObjects() {
- return null;
- }
-
- @Override
- public <T> T newObject(Class<T> persistentClass) {
- return null;
- }
-
- @Override
- public Collection<?> newObjects() {
- return null;
- }
-
- @Override
- protected GraphDiff onContextFlush(ObjectContext originatingContext, GraphDiff changes, boolean cascade) {
- return null;
- }
-
- @Override
- public QueryResponse performGenericQuery(Query query) {
- return null;
- }
-
- @Override
- public List performQuery(Query query) {
- return null;
- }
-
- @Override
- public void registerNewObject(Object object) {
- }
-
- @Override
- public void rollbackChanges() {
- }
-
- @Override
- public void rollbackChangesLocally() {
- }
-
- @Override
- public Collection<?> uncommittedObjects() {
- return null;
- }
-
- public ObjectContext createChildContext() {
- return null;
- }
-
- public boolean hasChanges() {
- return false;
- }
-
- public QueryResponse onQuery(ObjectContext originatingContext, Query query) {
- return null;
- }
-
- @Override
- public <T> ResultIterator<T> iterator(Select<T> query) {
- return null;
- }
-
- @Override
- public <T> ResultBatchIterator<T> batchIterator(Select<T> query, int size) {
- return null;
- }
-
-}
diff --git a/cayenne/src/test/java/org/apache/cayenne/BaseContextTest.java b/cayenne/src/test/java/org/apache/cayenne/access/DataContextTest.java
similarity index 83%
rename from cayenne/src/test/java/org/apache/cayenne/BaseContextTest.java
rename to cayenne/src/test/java/org/apache/cayenne/access/DataContextTest.java
index d53e356e7..1df21d649 100644
--- a/cayenne/src/test/java/org/apache/cayenne/BaseContextTest.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/DataContextTest.java
@@ -1,93 +1,98 @@
-/*****************************************************************
- * 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;
-
-import org.apache.cayenne.cache.QueryCache;
-import org.apache.cayenne.runtime.CayenneRuntime;
-import org.apache.cayenne.di.DIBootstrap;
-import org.apache.cayenne.di.Injector;
-import org.apache.cayenne.di.Module;
-import org.junit.Test;
-
-import java.util.Map;
-
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.mock;
-
-public class BaseContextTest {
-
- @Test
- public void testUserPropertiesLazyInit() {
- BaseContext context = new MockBaseContext();
- assertNull(context.userProperties);
-
- Map<String, Object> properties = context.getUserProperties();
- assertNotNull(properties);
- assertSame(properties, context.getUserProperties());
- }
-
- @Test
- public void testAttachToRuntimeIfNeeded() {
-
- final DataChannel channel = mock(DataChannel.class);
- final QueryCache cache = mock(QueryCache.class);
-
- Module testModule = binder -> {
- binder.bind(DataChannel.class).toInstance(channel);
- binder.bind(QueryCache.class).toInstance(cache);
- };
-
- Injector injector = DIBootstrap.createInjector(testModule);
-
- BaseContext context = new MockBaseContext();
- assertNull(context.channel);
- assertNull(context.queryCache);
-
- Injector oldInjector = CayenneRuntime.getThreadInjector();
- try {
-
- CayenneRuntime.bindThreadInjector(injector);
-
- assertTrue(context.attachToRuntimeIfNeeded());
- assertSame(channel, context.channel);
-
- assertFalse(context.attachToRuntimeIfNeeded());
- assertFalse(context.attachToRuntimeIfNeeded());
- }
- finally {
- CayenneRuntime.bindThreadInjector(oldInjector);
- }
- }
-
- @Test
- public void testAttachToRuntimeIfNeeded_NoStack() {
-
- BaseContext context = new MockBaseContext();
- assertNull(context.channel);
- assertNull(context.queryCache);
-
- try {
- context.attachToRuntimeIfNeeded();
- fail("No thread stack, must have thrown");
- }
- catch (CayenneRuntimeException e) {
- // expected
- }
- }
-}
+/*****************************************************************
+ * 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.CayenneRuntimeException;
+import org.apache.cayenne.DataChannel;
+import org.apache.cayenne.cache.QueryCache;
+import org.apache.cayenne.di.DIBootstrap;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.runtime.CayenneRuntime;
+import org.apache.cayenne.tx.TransactionFactory;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+
+public class DataContextTest {
+ @Test
+ public void testUserPropertiesLazyInit() {
+ DataContext context = new DataContext();
+ assertNull(context.userProperties);
+
+ Map<String, Object> properties = context.getUserProperties();
+ assertNotNull(properties);
+ assertSame(properties, context.getUserProperties());
+ }
+
+ @Test
+ public void testAttachToRuntimeIfNeeded() {
+
+ final DataChannel channel = mock(DataChannel.class);
+ final QueryCache cache = mock(QueryCache.class);
+ final TransactionFactory factory = mock(TransactionFactory.class);
+
+ Module testModule = binder -> {
+ binder.bind(DataChannel.class).toInstance(channel);
+ binder.bind(QueryCache.class).toInstance(cache);
+ binder.bind(TransactionFactory.class).toInstance(factory);
+ };
+
+ Injector injector = DIBootstrap.createInjector(testModule);
+
+ DataContext context = new DataContext();
+ assertNull(context.channel);
+ assertNull(context.queryCache);
+
+ Injector oldInjector = CayenneRuntime.getThreadInjector();
+ try {
+
+ CayenneRuntime.bindThreadInjector(injector);
+
+ assertTrue(context.attachToRuntimeIfNeeded());
+ assertSame(channel, context.getChannel());
+
+ assertFalse(context.attachToRuntimeIfNeeded());
+ assertFalse(context.attachToRuntimeIfNeeded());
+ }
+ finally {
+ CayenneRuntime.bindThreadInjector(oldInjector);
+ }
+ }
+
+ @Test
+ public void testAttachToRuntimeIfNeeded_NoStack() {
+
+ DataContext context = new DataContext();
+ assertNull(context.channel);
+ assertNull(context.queryCache);
+
+ try {
+ context.attachToRuntimeIfNeeded();
+ fail("No thread stack, must have thrown");
+ }
+ catch (CayenneRuntimeException e) {
+ // expected
+ }
+ }
+}
\ No newline at end of file
diff --git a/cayenne/src/test/java/org/apache/cayenne/access/NestedDataContextLocalCacheIT.java b/cayenne/src/test/java/org/apache/cayenne/access/NestedDataContextLocalCacheIT.java
index 67801b692..c15d8e82d 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/NestedDataContextLocalCacheIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/NestedDataContextLocalCacheIT.java
@@ -20,7 +20,6 @@ package org.apache.cayenne.access;
import java.util.List;
-import org.apache.cayenne.BaseContext;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.query.ObjectSelect;
@@ -52,14 +51,14 @@ public class NestedDataContextLocalCacheIT extends RuntimeCase {
ObjectContext child1 = runtime.newContext(context);
- assertNull(((BaseContext) child1).getQueryCache().get(
+ assertNull(((DataContext) child1).getQueryCache().get(
query.getMetaData(child1.getEntityResolver())));
assertNull(context.getQueryCache().get(
query.getMetaData(context.getEntityResolver())));
List<?> results = child1.performQuery(query);
- assertSame(results, ((BaseContext) child1).getQueryCache().get(
+ assertSame(results, ((DataContext) child1).getQueryCache().get(
query.getMetaData(child1.getEntityResolver())));
assertNull(context.getQueryCache().get(