You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by an...@apache.org on 2008/12/09 17:39:57 UTC

svn commit: r724771 - in /cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src: main/java/org/apache/cayenne/ main/java/org/apache/cayenne/access/ test/java/org/apache/cayenne/ test/java/org/apache/cayenne/remote/

Author: andrey
Date: Tue Dec  9 08:39:56 2008
New Revision: 724771

URL: http://svn.apache.org/viewvc?rev=724771&view=rev
Log:
CAY-1119 Nested contexts on ROP
API changes

Added:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextLocalTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextParentEventsTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextPeerEventsTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextRollbackTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextTest.java
Modified:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextGraphManager.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextMergeHandler.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ObjectContext.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ChildDiffLoader.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/MockObjectContext.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/RemoteCayenneCase.java

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java?rev=724771&r1=724770&r2=724771&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java Tue Dec  9 08:39:56 2008
@@ -24,10 +24,15 @@
 
 import org.apache.cayenne.cache.MapQueryCache;
 import org.apache.cayenne.cache.QueryCache;
+import org.apache.cayenne.event.EventManager;
+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.query.ObjectIdQuery;
 import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.RefreshQuery;
 import org.apache.cayenne.reflect.AttributeProperty;
 import org.apache.cayenne.reflect.ClassDescriptor;
 import org.apache.cayenne.reflect.Property;
@@ -40,7 +45,7 @@
  * 
  * @since 3.0
  */
-public abstract class BaseContext implements ObjectContext {
+public abstract class BaseContext implements ObjectContext, DataChannel {
 
     /**
      * A holder of a ObjectContext bound to the current thread.
@@ -118,6 +123,7 @@
      * @deprecated since 3.0 this method is replaced by
      *             {@link #prepareForAccess(Persistent, String, boolean)}.
      */
+    @Deprecated
     public void prepareForAccess(Persistent object, String property) {
         prepareForAccess(object, property, false);
     }
@@ -260,4 +266,88 @@
     public synchronized void setQueryCache(QueryCache queryCache) {
         this.queryCache = queryCache;
     }
-}
+    
+    /**
+     * Returns EventManager associated with the ObjectStore.
+     * 
+     * @since 1.2
+     */
+    public EventManager getEventManager() {
+        return channel != null ? channel.getEventManager() : null;
+    }
+    
+    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: "
+                        + 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);
+        }
+    }
+    
+    /**
+     * "Invalidates" a Collection of persistent objects. This operation would remove each
+     * object's snapshot from cache and change object's state to HOLLOW. On the next
+     * access to this object, it will be refetched.
+     * 
+     * @see #unregisterObjects(Collection)
+     * @see RefreshQuery
+     */
+    public void invalidateObjects(Collection objects) {
+        performGenericQuery(new RefreshQuery(objects));
+    }
+}
\ No newline at end of file

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java?rev=724771&r1=724770&r2=724771&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java Tue Dec  9 08:39:56 2008
@@ -24,7 +24,9 @@
 import java.util.Iterator;
 import java.util.List;
 
+import org.apache.cayenne.access.ChildDiffLoader;
 import org.apache.cayenne.event.EventManager;
+import org.apache.cayenne.graph.CompoundDiff;
 import org.apache.cayenne.graph.GraphDiff;
 import org.apache.cayenne.graph.GraphManager;
 import org.apache.cayenne.map.EntityResolver;
@@ -223,9 +225,10 @@
 
                 graphManager.graphCommitStarted();
 
+                GraphDiff changes = graphManager.getDiffsSinceLastFlush();
+                
                 try {
-                    commitDiff = channel.onSync(this, graphManager
-                            .getDiffsSinceLastFlush(), syncType);
+                    commitDiff = channel.onSync(this, changes, syncType);
                 }
                 catch (Throwable th) {
                     graphManager.graphCommitAborted();
@@ -239,6 +242,10 @@
                 }
 
                 graphManager.graphCommitted(commitDiff);
+                
+                // this event is caught by peer nested ObjectContexts to synchronize the
+                // state
+                fireDataChannelCommitted(this, changes);
             }
         }
 
@@ -259,6 +266,7 @@
                 graphManager.graphReverted();
 
                 channel.onSync(this, diff, DataChannel.ROLLBACK_CASCADE_SYNC);
+                fireDataChannelRolledback(this, diff);
             }
         }
     }
@@ -267,7 +275,10 @@
     public void rollbackChangesLocally() {
         synchronized (graphManager) {
             if (graphManager.hasChanges()) {
+                GraphDiff diff = graphManager.getDiffs();
                 graphManager.graphReverted();
+                
+                fireDataChannelRolledback(this, diff);
             }
         }
     }
@@ -333,9 +344,7 @@
         return onQuery(this, query);
     }
 
-    // TODO: Andrus, 2/2/2006 - make public once CayenneContext is officially declared to
-    // support DataChannel API.
-    QueryResponse onQuery(ObjectContext context, Query query) {
+    public QueryResponse onQuery(ObjectContext context, Query query) {
         return new CayenneContextQueryAction(this, context, query).execute();
     }
 
@@ -410,7 +419,8 @@
 
                 getGraphManager().registerNode(id, localObject);
 
-                if (prototype != null) {
+                if (prototype != null
+                        && ((Persistent) prototype).getPersistenceState() != PersistenceState.HOLLOW) {
                     localObject.setPersistenceState(PersistenceState.COMMITTED);
                     descriptor.shallowMerge(prototype, localObject);
                 }
@@ -471,17 +481,24 @@
             Persistent object,
             String entityName,
             ClassDescriptor descriptor) {
-        ObjectId id = new ObjectId(entityName);
+        /**
+         * We should create new id only if it is not set for this object.
+         * It could have been created, for instance, in child context
+         */
+        ObjectId id = object.getObjectId();
+        if (id == null) {
+            id = new ObjectId(entityName);
+            object.setObjectId(id);
+        }
 
         // 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
-        object.setObjectId(id);
         object.setObjectContext(this);
         object.setPersistenceState(PersistenceState.NEW);
 
         synchronized (graphManager) {
-            graphManager.registerNode(object.getObjectId(), object);
-            graphManager.nodeCreated(object.getObjectId());
+            graphManager.registerNode(id, object);
+            graphManager.nodeCreated(id);
         }
     }
 
@@ -516,4 +533,64 @@
     void setPropertyChangeCallbacksDisabled(boolean propertyChangeCallbacksDisabled) {
         this.propertyChangeCallbacksDisabled = propertyChangeCallbacksDisabled;
     }
+
+    /**
+     * Creates and returns a new child ObjectContext.
+     * 
+     * @since 3.0
+     */
+    public ObjectContext createChildObjectContext() {
+        return new CayenneContext(this, graphManager.changeEventsEnabled, 
+                graphManager.lifecycleEventsEnabled);
+    }
+
+    @Override
+    protected GraphDiff onContextFlush(
+            ObjectContext originatingContext,
+            GraphDiff changes,
+            boolean cascade) {
+
+        boolean childContext = this != originatingContext && changes != null;
+
+        if (childContext) {
+           changes.apply(new CayenneContextChildDiffLoader(this));
+           fireDataChannelChanged(originatingContext, changes);
+        }
+
+        return (cascade) ? doCommitChanges(true) : new CompoundDiff();
+    }
+    
+    /**
+     * Returns <code>true</code> if there are any modified, deleted or new objects
+     * registered with this CayenneContext, <code>false</code> otherwise.
+     */
+    public boolean hasChanges() {
+        return graphManager.hasChanges();
+    }
+    
+    /**
+     * Class for loading child's CayenneContext changes to parent context.
+     * Required to register diffs in CayenneContext's graph manager when node's simple property changes:
+     * CayenneContext's way of setting properties differs from DataContext's, and method of notifying
+     * graph manager is sealed in every 'set' method of Cayenne Client Data Object class.
+     * So here we should notify graph manager too, so that objects's state will update and they will
+     * be marked as dirty.   
+     */
+    class CayenneContextChildDiffLoader extends ChildDiffLoader {
+        public CayenneContextChildDiffLoader(ObjectContext context) {
+            super(context);
+        }
+        
+        @Override
+        public void nodePropertyChanged(
+                Object nodeId,
+                String property,
+                Object oldValue,
+                Object newValue) {
+            super.nodePropertyChanged(nodeId, property, oldValue, newValue);
+            
+            Persistent object = (Persistent) getGraphManager().getNode(nodeId);
+            propertyChanged(object, property, oldValue, newValue);
+        }
+    }
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextGraphManager.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextGraphManager.java?rev=724771&r1=724770&r2=724771&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextGraphManager.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextGraphManager.java Tue Dec  9 08:39:56 2008
@@ -135,19 +135,15 @@
         }
 
         remapTargets();
+        
+        stateLog.graphCommitted();
+        reset();
 
         if (lifecycleEventsEnabled) {
-            GraphDiff diff = changeLog.getDiffsAfterMarker(COMMIT_MARKER);
-
-            stateLog.graphCommitted();
-            reset();
-
             // include all diffs after the commit start marker.
-            send(diff, DataChannel.GRAPH_FLUSHED_SUBJECT, context);
-        }
-        else {
-            stateLog.graphCommitted();
-            reset();
+            //We fire event as if it was posted by parent channel, so that
+            //nested contexts could catch it
+            context.fireDataChannelCommitted(context.getChannel(), parentSyncDiff);
         }
     }
 
@@ -238,7 +234,7 @@
         reset();
 
         if (lifecycleEventsEnabled) {
-            send(diff, DataChannel.GRAPH_ROLLEDBACK_SUBJECT, context);
+            context.fireDataChannelRolledback(context, diff);
         }
     }
 
@@ -297,7 +293,7 @@
         changeLog.addOperation(diff);
 
         if (changeEventsEnabled) {
-            send(diff, DataChannel.GRAPH_CHANGED_SUBJECT, context);
+            context.fireDataChannelChanged(context, diff);
         }
     }
 

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextMergeHandler.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextMergeHandler.java?rev=724771&r1=724770&r2=724771&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextMergeHandler.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextMergeHandler.java Tue Dec  9 08:39:56 2008
@@ -104,10 +104,7 @@
     void repostAfterMerge(GraphEvent originalEvent) {
         // though the subject is CHANGE, "merge" events are really lifecycle.
         if (context.isLifecycleEventsEnabled()) {
-            context.internalGraphManager().send(
-                    originalEvent.getDiff(),
-                    DataChannel.GRAPH_CHANGED_SUBJECT,
-                    originalEvent.getSource());
+            context.fireDataChannelChanged(originalEvent.getSource(), originalEvent.getDiff());
         }
     }
 

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ObjectContext.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ObjectContext.java?rev=724771&r1=724770&r2=724771&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ObjectContext.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ObjectContext.java Tue Dec  9 08:39:56 2008
@@ -26,6 +26,7 @@
 import org.apache.cayenne.graph.GraphManager;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.RefreshQuery;
 
 /**
  * A Cayenne object facade to a persistent store. Instances of ObjectContext are used in
@@ -113,6 +114,7 @@
     /**
      * @deprecated since 3.0 use {@link #prepareForAccess(Persistent, String, boolean)}.
      */
+    @Deprecated
     void prepareForAccess(Persistent object, String property);
 
     /**
@@ -172,4 +174,28 @@
      * Returns an DataChannel used by this context.
      */
     DataChannel getChannel();
+    
+    /**
+     * Creates and returns a new child ObjectContext.
+     * 
+     * @since 3.0
+     */
+    ObjectContext createChildObjectContext();
+    
+    /**
+     * Returns <code>true</code> if there are any modified, deleted or new objects
+     * registered with this ObjectContext, <code>false</code> otherwise.
+     * 
+     * @since 3.0
+     */
+    boolean hasChanges();
+    
+    /**
+     * "Invalidates" a Collection of persistent objects. This operation would remove each
+     * object's snapshot from cache and change object's state to HOLLOW. On the next
+     * access to this object, it will be refetched.
+     * 
+     * @see RefreshQuery
+     */
+    void invalidateObjects(Collection objects);
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ChildDiffLoader.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ChildDiffLoader.java?rev=724771&r1=724770&r2=724771&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ChildDiffLoader.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ChildDiffLoader.java Tue Dec  9 08:39:56 2008
@@ -42,12 +42,13 @@
 import org.apache.cayenne.reflect.ToOneProperty;
 
 /**
- * A GraphChangeHandler that loads child ObjectContext diffs into a parent DataContext.
+ * A GraphChangeHandler that loads child ObjectContext diffs into a parent ObjectContext.
  * Graph node ids are expected to be ObjectIds.
+ * This class is made public since 3.0 to be used in ObjectContext synchronizing
  * 
  * @since 1.2
  */
-class ChildDiffLoader implements GraphChangeHandler {
+public class ChildDiffLoader implements GraphChangeHandler {
 
     static final ThreadLocal<Boolean> childDiffProcessing = new ThreadLocal<Boolean>() {
 
@@ -77,7 +78,7 @@
         childDiffProcessing.set(flag);
     }
 
-    ChildDiffLoader(ObjectContext context) {
+    public ChildDiffLoader(ObjectContext context) {
         this.context = context;
     }
 
@@ -142,8 +143,8 @@
                 ((ObjectId) nodeId).getEntityName());
 
         setExternalChange(Boolean.TRUE);
-        try {
-            descriptor.getProperty(property).writeProperty(object, null, newValue);
+        try {            
+            descriptor.getProperty(property).writeProperty(object, null, newValue);            
         }
         catch (Exception e) {
             throw new CayenneRuntimeException("Error setting property: " + property, e);

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java?rev=724771&r1=724770&r2=724771&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java Tue Dec  9 08:39:56 2008
@@ -51,7 +51,6 @@
 import org.apache.cayenne.event.EventManager;
 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;
@@ -64,7 +63,6 @@
 import org.apache.cayenne.query.ObjectIdQuery;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.QueryMetadata;
-import org.apache.cayenne.query.RefreshQuery;
 import org.apache.cayenne.reflect.AttributeProperty;
 import org.apache.cayenne.reflect.ClassDescriptor;
 import org.apache.cayenne.reflect.PropertyVisitor;
@@ -123,6 +121,7 @@
      * @see org.apache.cayenne.conf.WebApplicationContextFilter
      * @deprecated since 3.0, replaced by BaseContex#getThreadObjectContext().
      */
+    @Deprecated
     public static DataContext getThreadDataContext() throws IllegalStateException {
         return (DataContext) BaseContext.getThreadObjectContext();
     }
@@ -135,6 +134,7 @@
      * @since 1.1
      * @deprecated since 3.0, replaced by BaseContex#getThreadObjectContext().
      */
+    @Deprecated
     public static void bindThreadDataContext(DataContext context) {
         BaseContext.bindThreadObjectContext(context);
     }
@@ -255,10 +255,21 @@
     }
 
     /**
+     * Creates and returns a new child ObjectContext.
+     * 
+     * @since 3.0
+     */
+    public ObjectContext createChildObjectContext() {
+        return createChildDataContext();
+    }
+
+    /**
      * Creates and returns a new child DataContext.
      * 
      * @since 1.2
+     * @deprecated since 3.0 use {@link #createChildObjectContext()}.
      */
+    @Deprecated
     public DataContext createChildDataContext() {
         DataContextFactory factory = getParentDataDomain().getDataContextFactory();
 
@@ -598,6 +609,7 @@
      *             deprecated. Use {@link #objectsFromDataRows(ClassDescriptor, List)}
      *             instead.
      */
+    @Deprecated
     public List objectsFromDataRows(
             ObjEntity entity,
             List dataRows,
@@ -632,6 +644,7 @@
      * @since 1.1
      * @see DataRow
      */
+    @Deprecated
     public List objectsFromDataRows(
             Class<?> objectClass,
             List<? extends DataRow> dataRows,
@@ -691,6 +704,7 @@
     /**
      * @deprecated since 3.0, use {@link #newObject(String)} instead.
      */
+    @Deprecated
     public DataObject createAndRegisterNewObject(String objEntityName) {
         return (DataObject) newObject(objEntityName);
     }
@@ -766,6 +780,7 @@
      * @since 1.1
      * @deprecated since 3.0, use {@link #newObject(Class)} instead.
      */
+    @Deprecated
     public DataObject createAndRegisterNewObject(Class objectClass) {
         if (objectClass == null) {
             throw new NullPointerException("DataObject class can't be null.");
@@ -907,18 +922,6 @@
     }
 
     /**
-     * "Invalidates" a Collection of persistent objects. This operation would remove each
-     * object's snapshot from cache and change object's state to HOLLOW. On the next
-     * access to this object, it will be refetched.
-     * 
-     * @see #unregisterObjects(Collection)
-     * @see RefreshQuery
-     */
-    public void invalidateObjects(Collection objects) {
-        performGenericQuery(new RefreshQuery(objects));
-    }
-
-    /**
      * Schedules all objects in the collection for deletion on the next commit of this
      * DataContext. Object's persistence state is changed to PersistenceState.DELETED;
      * objects related to this object are processed according to delete rules, i.e.
@@ -969,6 +972,7 @@
      *             is more than one object is fetched.
      * @deprecated since 3.0 use {@link ObjectIdQuery} with appropriate refresh settings.
      */
+    @Deprecated
     public DataObject refetchObject(ObjectId oid) {
 
         if (oid == null) {
@@ -1073,45 +1077,8 @@
         flushToParent(true);
     }
 
-    /**
-     * Returns EventManager associated with the ObjectStore.
-     * 
-     * @since 1.2
-     */
-    public EventManager getEventManager() {
-        return channel != null ? channel.getEventManager() : null;
-    }
-
-    /**
-     * An implementation of a {@link DataChannel} method that is used by child contexts to
-     * synchronize state with this context. Not intended for direct use.
-     * 
-     * @since 1.2
-     */
-    public GraphDiff onSync(
-            ObjectContext originatingContext,
-            GraphDiff changes,
-            int syncType) {
-        // sync client changes
-        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: "
-                        + syncType);
-        }
-    }
-
-    GraphDiff onContextRollback(ObjectContext originatingContext) {
-        rollbackChanges();
-        return new CompoundDiff();
-    }
-
-    GraphDiff onContextFlush(
+    @Override
+    protected GraphDiff onContextFlush(
             ObjectContext originatingContext,
             GraphDiff changes,
             boolean cascade) {
@@ -1452,42 +1419,6 @@
         this.validatingObjectsOnCommit = flag;
     }
 
-    /**
-     * @since 1.2
-     */
-    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
-     */
-    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
-     */
-    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);
-        }
-    }
-
     // ---------------------------------------------
     // Serialization Support
     // ---------------------------------------------
@@ -1686,4 +1617,8 @@
             }
         }
     }
+    
+    protected void fireDataChannelChanged(Object postedBy, GraphDiff changes) {
+        super.fireDataChannelChanged(postedBy, changes);
+    }
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/MockObjectContext.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/MockObjectContext.java?rev=724771&r1=724770&r2=724771&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/MockObjectContext.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/MockObjectContext.java Tue Dec  9 08:39:56 2008
@@ -104,6 +104,7 @@
     /**
      * @deprecated since 3.0
      */
+    @Deprecated
     public void prepareForAccess(Persistent persistent, String property) {
     }
     
@@ -136,4 +137,19 @@
     public QueryResponse performGenericQuery(Query queryPlan) {
         return null;
     }
+
+    public ObjectContext createChildObjectContext() {
+        return null;
+    }
+
+    public <T> T newObject(Class<T> persistentClass) {
+        return null;
+    }
+
+    public boolean hasChanges() {
+        return false;
+    }
+
+    public void invalidateObjects(Collection objects) {
+    }
 }

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextLocalTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextLocalTest.java?rev=724771&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextLocalTest.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextLocalTest.java Tue Dec  9 08:39:56 2008
@@ -0,0 +1,49 @@
+/*****************************************************************
+ *   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
+ *
+ *    http://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.remote;
+
+import java.util.List;
+
+import org.apache.cayenne.BaseContext;
+import org.apache.cayenne.query.QueryCacheStrategy;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.testdo.mt.ClientMtTable1;
+
+public class NestedObjectContextLocalTest extends RemoteCayenneCase {
+    public void testLocalCacheStaysLocal() {
+
+        SelectQuery query = new SelectQuery(ClientMtTable1.class);
+        query.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+
+        BaseContext child1 = (BaseContext) context.createChildObjectContext();
+
+        assertNull(child1.getQueryCache().get(
+                query.getMetaData(child1.getEntityResolver())));
+
+        assertNull(context.getQueryCache().get(
+                query.getMetaData(context.getEntityResolver())));
+
+        List<?> results = child1.performQuery(query);
+        assertSame(results, child1.getQueryCache().get(
+                query.getMetaData(child1.getEntityResolver())));
+
+        assertNull(context.getQueryCache().get(
+                query.getMetaData(context.getEntityResolver())));
+    }
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextParentEventsTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextParentEventsTest.java?rev=724771&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextParentEventsTest.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextParentEventsTest.java Tue Dec  9 08:39:56 2008
@@ -0,0 +1,45 @@
+/*****************************************************************
+ *   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
+ *
+ *    http://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.remote;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.testdo.mt.ClientMtTable1;
+
+public class NestedObjectContextParentEventsTest extends RemoteCayenneCase {
+
+    public void testParentUpdatedId() throws Exception {
+        deleteTestData();
+        
+        ObjectContext child = context.createChildObjectContext();
+
+        ClientMtTable1 ac = child.newObject(ClientMtTable1.class);
+        ac.setGlobalAttribute1("X");
+        child.commitChangesToParent();
+
+        ClientMtTable1 ap = (ClientMtTable1) context.getGraphManager().getNode(ac.getObjectId());
+        assertNotNull(ap);
+
+        assertTrue(ap.getObjectId().isTemporary());
+        context.commitChanges();
+
+        assertFalse(ap.getObjectId().isTemporary());
+        assertEquals(ap.getObjectId(), ac.getObjectId());
+    }
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextPeerEventsTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextPeerEventsTest.java?rev=724771&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextPeerEventsTest.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextPeerEventsTest.java Tue Dec  9 08:39:56 2008
@@ -0,0 +1,134 @@
+/*****************************************************************
+ *   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
+ *
+ *    http://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.remote;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.testdo.mt.ClientMtTable1;
+import org.apache.cayenne.testdo.mt.ClientMtTable2;
+
+public class NestedObjectContextPeerEventsTest extends RemoteCayenneCase {
+
+    public void testPeerObjectUpdatedTempOID() throws Exception {
+        deleteTestData();
+        
+        ObjectContext peer1 = context.createChildObjectContext();
+        ClientMtTable1 a1 = peer1.newObject(ClientMtTable1.class);
+        a1.setGlobalAttribute1("Y");
+        ObjectId a1TempId = a1.getObjectId();
+
+        ObjectContext peer2 = context.createChildObjectContext();
+        ClientMtTable1 a2 = (ClientMtTable1) peer2.localObject(a1TempId, a1);
+
+        assertEquals(a1TempId, a2.getObjectId());
+
+        peer1.commitChanges();
+        assertFalse(a1.getObjectId().isTemporary());
+        assertFalse(a2.getObjectId().isTemporary());
+        assertEquals(a2.getObjectId(), a1.getObjectId());
+    }
+
+    public void testPeerObjectUpdatedSimpleProperty() throws Exception {
+        deleteTestData();
+
+        ClientMtTable1 a = context.newObject(ClientMtTable1.class);
+        a.setGlobalAttribute1("X");
+        context.commitChanges();
+
+        ObjectContext peer1 = context.createChildObjectContext();
+        ClientMtTable1 a1 = (ClientMtTable1) peer1.localObject(a.getObjectId(), a);
+
+        ObjectContext peer2 = context.createChildObjectContext();
+        ClientMtTable1 a2 = (ClientMtTable1) peer2.localObject(a.getObjectId(), a);
+
+        a1.setGlobalAttribute1("Y");
+        assertEquals("X", a2.getGlobalAttribute1());
+        peer1.commitChangesToParent();
+        assertEquals("Y", a2.getGlobalAttribute1());
+
+        assertFalse("Peer data context became dirty on event processing", peer2
+                .hasChanges());
+    }
+
+    public void testPeerObjectUpdatedToOneRelationship() throws Exception {
+        deleteTestData();
+
+        ClientMtTable1 a = context.newObject(ClientMtTable1.class);
+        ClientMtTable1 altA = context.newObject(ClientMtTable1.class);
+
+        ClientMtTable2 p = context.newObject(ClientMtTable2.class);
+        p.setTable1(a);
+        p.setGlobalAttribute("PPP");
+        a.setGlobalAttribute1("X");
+        altA.setGlobalAttribute1("Y");
+        context.commitChanges();
+
+        ObjectContext peer1 = context.createChildObjectContext();
+        ClientMtTable2 p1 = (ClientMtTable2) peer1.localObject(p.getObjectId(), p);
+        ClientMtTable1 altA1 = (ClientMtTable1) peer1.localObject(altA.getObjectId(), altA);
+
+        ObjectContext peer2 = context.createChildObjectContext();
+        ClientMtTable2 p2 = (ClientMtTable2) peer2.localObject(p.getObjectId(), p);
+        ClientMtTable1 altA2 = (ClientMtTable1) peer2.localObject(altA.getObjectId(), altA);
+        ClientMtTable1 a2 = (ClientMtTable1) peer2.localObject(a.getObjectId(), a);
+
+        p1.setTable1(altA1);
+        assertSame(a2, p2.getTable1());
+        peer1.commitChangesToParent();
+        assertEquals(altA2, p2.getTable1());
+
+        assertFalse("Peer data context became dirty on event processing", peer2
+                .hasChanges());
+    }
+
+    public void testPeerObjectUpdatedToManyRelationship() throws Exception {
+        deleteTestData();
+
+        ClientMtTable1 a = context.newObject(ClientMtTable1.class);
+        a.setGlobalAttribute1("X");
+
+        ClientMtTable2 px = context.newObject(ClientMtTable2.class);
+        px.setTable1(a);
+        px.setGlobalAttribute("PX");
+
+        ClientMtTable2 py = context.newObject(ClientMtTable2.class);
+        py.setGlobalAttribute("PY");
+
+        context.commitChanges();
+
+        ObjectContext peer1 = context.createChildObjectContext();
+        ClientMtTable2 py1 = (ClientMtTable2) peer1.localObject(py.getObjectId(), py);
+        ClientMtTable1 a1 = (ClientMtTable1) peer1.localObject(a.getObjectId(), a);
+
+        ObjectContext peer2 = context.createChildObjectContext();
+        ClientMtTable2 py2 = (ClientMtTable2) peer2.localObject(py.getObjectId(), py);
+        ClientMtTable1 a2 = (ClientMtTable1) peer2.localObject(a.getObjectId(), a);
+
+        a1.addToTable2Array(py1);
+        assertEquals(1, a2.getTable2Array().size());
+        assertFalse(a2.getTable2Array().contains(py2));
+        peer1.commitChangesToParent();
+        assertEquals(2, a2.getTable2Array().size());
+        assertTrue(a2.getTable2Array().contains(py2));
+
+        assertFalse("Peer data context became dirty on event processing", peer2
+                .hasChanges());
+    }
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextRollbackTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextRollbackTest.java?rev=724771&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextRollbackTest.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextRollbackTest.java Tue Dec  9 08:39:56 2008
@@ -0,0 +1,63 @@
+/*****************************************************************
+ *   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
+ *
+ *    http://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.remote;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.testdo.mt.ClientMtTable1;
+
+public class NestedObjectContextRollbackTest extends RemoteCayenneCase {
+
+    public void testRollbackChanges() {
+        ObjectContext child1 = context.createChildObjectContext();
+        
+        assertFalse(context.hasChanges());
+        assertFalse(child1.hasChanges());
+        
+        context.newObject(ClientMtTable1.class);
+        child1.newObject(ClientMtTable1.class);
+        
+        assertTrue(context.hasChanges());
+        assertTrue(child1.hasChanges());
+        
+        child1.rollbackChanges();
+        assertFalse(context.hasChanges());
+        assertFalse(child1.hasChanges());
+        
+        context.rollbackChanges();
+    }
+    
+    public void testRollbackChangesLocally() {
+        ObjectContext child1 = context.createChildObjectContext();
+        
+        assertFalse(context.hasChanges());
+        assertFalse(child1.hasChanges());
+        
+        context.newObject(ClientMtTable1.class);
+        child1.newObject(ClientMtTable1.class);
+        
+        assertTrue(context.hasChanges());
+        assertTrue(child1.hasChanges());
+        
+        child1.rollbackChangesLocally();
+        assertTrue(context.hasChanges());
+        assertFalse(child1.hasChanges());
+        
+        context.rollbackChanges();
+    }
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextTest.java?rev=724771&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextTest.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextTest.java Tue Dec  9 08:39:56 2008
@@ -0,0 +1,600 @@
+/*****************************************************************
+ *   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
+ *
+ *    http://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.remote;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.cayenne.DataObjectUtils;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.PersistenceState;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.query.ObjectIdQuery;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.testdo.mt.ClientMtTable1;
+import org.apache.cayenne.testdo.mt.ClientMtTable2;
+
+/**
+ * Tests nested object contexts
+ */
+public class NestedObjectContextTest extends RemoteCayenneCase {
+    public void testChannels() {
+        ObjectContext child = context.createChildObjectContext();
+
+        assertNotNull(child);
+        assertSame(context, child.getChannel());
+
+        // second level of nesting
+        ObjectContext grandchild = child.createChildObjectContext();
+
+        assertNotNull(grandchild);
+        assertSame(child, grandchild.getChannel());
+    }
+
+    public void testLocalObjectSynchronize() throws Exception {
+        deleteTestData();
+
+        ObjectContext child = context.createChildObjectContext();
+
+        ClientMtTable1 committed = context.newObject(ClientMtTable1.class);
+        ClientMtTable1 deleted = context.newObject(ClientMtTable1.class);
+        ClientMtTable1 modified = context.newObject(ClientMtTable1.class);
+
+        context.commitChanges();
+
+        context.deleteObject(deleted);
+        modified.setGlobalAttribute1("a");
+
+        ClientMtTable1 _new = context.newObject(ClientMtTable1.class);
+
+        ClientMtTable1 hollow = (ClientMtTable1) context.localObject(new ObjectId("MtTable1"), null);
+
+        assertEquals(PersistenceState.HOLLOW, hollow.getPersistenceState());
+        assertEquals(PersistenceState.COMMITTED, committed.getPersistenceState());
+        assertEquals(PersistenceState.MODIFIED, modified.getPersistenceState());
+        assertEquals(PersistenceState.DELETED, deleted.getPersistenceState());
+        assertEquals(PersistenceState.NEW, _new.getPersistenceState());
+
+        blockQueries();
+
+        try {
+            Persistent newPeer = child.localObject(_new.getObjectId(), _new);
+
+            assertEquals(_new.getObjectId(), newPeer.getObjectId());
+            assertEquals(PersistenceState.COMMITTED, newPeer.getPersistenceState());
+
+            assertSame(child, newPeer.getObjectContext());
+            assertSame(context, _new.getObjectContext());
+
+            Persistent hollowPeer = child.localObject(hollow.getObjectId(), hollow);
+            assertEquals(PersistenceState.HOLLOW, hollowPeer.getPersistenceState());
+            assertEquals(hollow.getObjectId(), hollowPeer.getObjectId());
+            assertSame(child, hollowPeer.getObjectContext());
+            assertSame(context, hollow.getObjectContext());
+
+            Persistent committedPeer = child.localObject(committed.getObjectId(), committed);
+            assertEquals(PersistenceState.COMMITTED, committedPeer.getPersistenceState());
+            assertEquals(committed.getObjectId(), committedPeer.getObjectId());
+            assertSame(child, committedPeer.getObjectContext());
+            assertSame(context, committed.getObjectContext());
+
+            ClientMtTable1 modifiedPeer = (ClientMtTable1) child.localObject(modified.getObjectId(), modified);
+            assertEquals(PersistenceState.COMMITTED, modifiedPeer.getPersistenceState());
+            assertEquals(modified.getObjectId(), modifiedPeer.getObjectId());
+            assertEquals("a", modifiedPeer.getGlobalAttribute1());
+            assertSame(child, modifiedPeer.getObjectContext());
+            assertSame(context, modified.getObjectContext());
+
+            Persistent deletedPeer = child.localObject(deleted.getObjectId(), deleted);
+            assertEquals(PersistenceState.COMMITTED, deletedPeer.getPersistenceState());
+            assertEquals(deleted.getObjectId(), deletedPeer.getObjectId());
+            assertSame(child, deletedPeer.getObjectContext());
+            assertSame(context, deleted.getObjectContext());
+        }
+        finally {
+            unblockQueries();
+        }
+    }
+
+    public void testLocalObjectsNoOverride() throws Exception {
+        deleteTestData();
+
+        ObjectContext child = context.createChildObjectContext();
+
+        ClientMtTable1 modified = context.newObject(ClientMtTable1.class);
+        context.commitChanges();
+
+        ClientMtTable1 peerModified = (ClientMtTable1) DataObjectUtils.objectForQuery(
+                child, new ObjectIdQuery(modified.getObjectId()));
+
+        modified.setGlobalAttribute1("M1");
+        peerModified.setGlobalAttribute1("M2");
+
+        assertEquals(PersistenceState.MODIFIED, modified.getPersistenceState());
+        assertEquals(PersistenceState.MODIFIED, peerModified.getPersistenceState());
+
+        blockQueries();
+
+        try {
+
+            Persistent peerModified2 = child.localObject(modified.getObjectId(), modified);
+            assertSame(peerModified, peerModified2);
+            assertEquals(PersistenceState.MODIFIED, peerModified2.getPersistenceState());
+            assertEquals("M2", peerModified.getGlobalAttribute1());
+            assertEquals("M1", modified.getGlobalAttribute1());
+        }
+        finally {
+            unblockQueries();
+        }
+    }
+
+    public void testLocalObjectRelationship() throws Exception {
+        deleteTestData();
+
+        ObjectContext child = context.createChildObjectContext();
+
+        ClientMtTable1 _new = context.newObject(ClientMtTable1.class);
+        ClientMtTable2 _new2 = context.newObject(ClientMtTable2.class);
+        _new.addToTable2Array(_new2);
+
+        blockQueries();
+
+        try {
+
+            ClientMtTable2 child2 = (ClientMtTable2) child.localObject(_new2.getObjectId(), _new2);
+            assertEquals(PersistenceState.COMMITTED, child2.getPersistenceState());
+            assertNotNull(child2.getTable1());
+            assertEquals(PersistenceState.COMMITTED, child2.getTable1().getPersistenceState());
+        }
+        finally {
+            unblockQueries();
+        }
+    }
+
+    public void testSelect() throws Exception {
+        deleteTestData();
+
+        ObjectContext child = context.createChildObjectContext();
+
+        ClientMtTable1 committed = context.newObject(ClientMtTable1.class);
+        ClientMtTable1 deleted = context.newObject(ClientMtTable1.class);
+        ClientMtTable1 modified = context.newObject(ClientMtTable1.class);
+
+        context.commitChanges();
+        int modifiedid = DataObjectUtils.intPKForObject(modified);
+
+        // test how different object states appear in the child on select
+
+        context.deleteObject(deleted);
+        modified.setGlobalAttribute1("a");
+
+        ClientMtTable1 _new = context.newObject(ClientMtTable1.class);
+
+        assertEquals(PersistenceState.COMMITTED, committed.getPersistenceState());
+        assertEquals(PersistenceState.MODIFIED, modified.getPersistenceState());
+        assertEquals(PersistenceState.DELETED, deleted.getPersistenceState());
+        assertEquals(PersistenceState.NEW, _new.getPersistenceState());
+
+        List objects = child.performQuery(new SelectQuery(ClientMtTable1.class));
+        assertEquals("All but NEW object must have been included", 3, objects.size());
+
+        Iterator it = objects.iterator();
+        while (it.hasNext()) {
+            ClientMtTable1 next = (ClientMtTable1) it.next();
+            assertEquals(PersistenceState.COMMITTED, next.getPersistenceState());
+
+            int id = DataObjectUtils.intPKForObject(next);
+            if (id == modifiedid) {
+                assertEquals("a", next.getGlobalAttribute1());
+            }
+        }
+    }
+
+    public void testPrefetchingToOne() throws Exception {
+        deleteTestData();
+
+        ClientMtTable1 mt11 = context.newObject(ClientMtTable1.class);
+        ClientMtTable1 mt12 = context.newObject(ClientMtTable1.class);
+        ClientMtTable2 mt21 = context.newObject(ClientMtTable2.class);
+        ClientMtTable2 mt22 = context.newObject(ClientMtTable2.class);
+
+        mt21.setTable1(mt11);
+        mt22.setTable1(mt11);
+
+        context.commitChanges();
+
+        ObjectContext child = context.createChildObjectContext();
+
+        SelectQuery q = new SelectQuery(ClientMtTable2.class);
+        q.addPrefetch(ClientMtTable2.TABLE1_PROPERTY);
+
+        List results = child.performQuery(q);
+
+        blockQueries();
+        try {
+            assertEquals(2, results.size());
+            Iterator it = results.iterator();
+            while (it.hasNext()) {
+                ClientMtTable2 o = (ClientMtTable2) it.next();
+                assertEquals(PersistenceState.COMMITTED, o.getPersistenceState());
+                assertSame(child, o.getObjectContext());
+
+                ClientMtTable1 o1 = o.getTable1();
+                assertNotNull(o1);
+                assertEquals(PersistenceState.COMMITTED, o1.getPersistenceState());
+                assertSame(child, o1.getObjectContext());
+                assertEquals(mt11.getObjectId(), o1.getObjectId());
+            }
+        }
+        finally {
+            unblockQueries();
+        }
+    }
+
+    public void testPrefetchingToMany() throws Exception {
+        deleteTestData();
+
+        ClientMtTable1 mt11 = context.newObject(ClientMtTable1.class);
+        mt11.setGlobalAttribute1("1");
+
+        ClientMtTable1 mt12 = context.newObject(ClientMtTable1.class);
+        mt12.setGlobalAttribute1("2");
+
+        ClientMtTable2 mt21 = context.newObject(ClientMtTable2.class);
+        ClientMtTable2 mt22 = context.newObject(ClientMtTable2.class);
+
+        mt21.setTable1(mt11);
+        mt22.setTable1(mt11);
+
+        context.commitChanges();
+
+        ObjectContext child = context.createChildObjectContext();
+
+        SelectQuery q = new SelectQuery(ClientMtTable1.class);
+        q.addOrdering("globalAttribute1", true);
+        q.addPrefetch(ClientMtTable1.TABLE2ARRAY_PROPERTY);
+
+        List results = child.performQuery(q);
+
+        blockQueries();
+        try {
+
+            ClientMtTable1 o1 = (ClientMtTable1) results.get(0);
+            assertEquals(PersistenceState.COMMITTED, o1.getPersistenceState());
+            assertSame(child, o1.getObjectContext());
+
+            List<ClientMtTable2> children1 = o1.getTable2Array();
+
+            assertEquals(2, children1.size());
+            Iterator<ClientMtTable2> it = children1.iterator();
+            while (it.hasNext()) {
+                ClientMtTable2 o = it.next();
+                assertEquals(PersistenceState.COMMITTED, o.getPersistenceState());
+                assertSame(child, o.getObjectContext());
+
+                assertEquals(o1, o.getTable1());
+            }
+
+            ClientMtTable1 o2 = (ClientMtTable1) results.get(1);
+            assertEquals(PersistenceState.COMMITTED, o2.getPersistenceState());
+            assertSame(child, o2.getObjectContext());
+
+            List children2 = o2.getTable2Array();
+
+            assertEquals(0, children2.size());
+        }
+        finally {
+            unblockQueries();
+        }
+    }
+
+    public void testDeleteNew() throws Exception {
+        deleteTestData();
+        ObjectContext child = context.createChildObjectContext();
+
+        ClientMtTable1 a = context.newObject(ClientMtTable1.class);
+        context.commitChanges();
+
+        ClientMtTable2 p = child.newObject(ClientMtTable2.class);
+        ClientMtTable1 aChild = (ClientMtTable1) DataObjectUtils.objectForPK(child, a.getObjectId());
+        p.setGlobalAttribute("X");
+        aChild.addToTable2Array(p);
+
+        child.commitChangesToParent();
+
+        child.deleteObject(p);
+        aChild.removeFromTable2Array(p);
+
+        child.commitChangesToParent();
+    }
+
+    /**
+     * A test case for CAY-698 bug.
+     */
+    public void testNullifyToOne() throws Exception {
+        deleteTestData();
+
+        ClientMtTable1 a = context.newObject(ClientMtTable1.class);
+        ClientMtTable2 b = context.newObject(ClientMtTable2.class);
+        a.addToTable2Array(b);
+
+        context.commitChanges();
+
+        ObjectContext child = context.createChildObjectContext();
+        ObjectContext childPeer = context.createChildObjectContext();
+
+        ClientMtTable2 childP1 = (ClientMtTable2) DataObjectUtils.objectForPK(child, b.getObjectId());
+
+        // trigger object creation in the peer nested DC
+        DataObjectUtils.objectForPK(childPeer, b.getObjectId());
+        childP1.setTable1(null);
+
+        blockQueries();
+
+        try {
+            child.commitChangesToParent();
+            assertEquals(PersistenceState.COMMITTED, childP1.getPersistenceState());
+
+            ClientMtTable2 parentP1 = (ClientMtTable2) context.getGraphManager().getNode(
+                    childP1.getObjectId());
+
+            assertNotNull(parentP1);
+            assertEquals(PersistenceState.MODIFIED, parentP1.getPersistenceState());
+            assertNull(parentP1.getTable1());
+        }
+        finally {
+            unblockQueries();
+        }
+    }
+
+    public void testCommitChangesToParent() throws Exception {
+        deleteTestData();
+
+        context.newObject(ClientMtTable1.class);
+        context.newObject(ClientMtTable1.class);
+        context.newObject(ClientMtTable1.class);
+        context.newObject(ClientMtTable1.class);
+        context.commitChanges();
+
+        ObjectContext child = context.createChildObjectContext();
+
+        SelectQuery query = new SelectQuery(ClientMtTable1.class);
+        List objects = child.performQuery(query);
+
+        assertEquals(4, objects.size());
+
+        ClientMtTable1 childNew = child.newObject(ClientMtTable1.class);
+        childNew.setGlobalAttribute1("NNN");
+
+        ClientMtTable1 childModified = (ClientMtTable1) objects.get(0);
+        childModified.setGlobalAttribute1("MMM");
+
+        ClientMtTable1 childCommitted = (ClientMtTable1) objects.get(1);
+
+        ClientMtTable1 childHollow = (ClientMtTable1) objects.get(3);
+        child.invalidateObjects(Collections.singleton(childHollow));
+
+        blockQueries();
+
+        try {
+            child.commitChangesToParent();
+
+            // * all modified child objects must be in committed state now
+            // * all modifications should be propagated to the parent
+            // * no actual commit should occur.
+
+            assertEquals(PersistenceState.COMMITTED, childNew.getPersistenceState());
+            assertEquals(PersistenceState.COMMITTED, childModified.getPersistenceState());
+            assertEquals(PersistenceState.COMMITTED, childCommitted.getPersistenceState());
+            assertEquals(PersistenceState.HOLLOW, childHollow.getPersistenceState());
+
+            ClientMtTable1 parentNew = (ClientMtTable1) context.getGraphManager().getNode(
+                    childNew.getObjectId());
+            ClientMtTable1 parentModified = (ClientMtTable1) context.getGraphManager().getNode(
+                    childModified.getObjectId());
+            ClientMtTable1 parentCommitted = (ClientMtTable1) context.getGraphManager().getNode(
+                    childCommitted.getObjectId());
+            ClientMtTable1 parentHollow = (ClientMtTable1) context.getGraphManager().getNode(
+                    childHollow.getObjectId());
+
+            assertNotNull(parentNew);
+            assertEquals(PersistenceState.NEW, parentNew.getPersistenceState());
+            assertEquals("NNN", parentNew.getGlobalAttribute1());
+
+            assertNotNull(parentModified);
+            assertEquals(PersistenceState.MODIFIED, parentModified.getPersistenceState());
+            assertEquals("MMM", parentModified.getGlobalAttribute1());
+
+            assertNotNull(parentCommitted);
+            assertEquals(PersistenceState.COMMITTED, parentCommitted
+                    .getPersistenceState());
+
+            assertNotNull(parentHollow);
+        }
+        finally {
+            unblockQueries();
+        }
+    }
+
+    public void testCommitChangesToParentDeleted() throws Exception {
+        deleteTestData();
+
+        context.newObject(ClientMtTable1.class);
+        context.newObject(ClientMtTable1.class);
+        context.newObject(ClientMtTable1.class);
+        context.newObject(ClientMtTable1.class);
+        context.commitChanges();
+
+        ObjectContext child = context.createChildObjectContext();
+
+        // make sure we fetch in predictable order
+        SelectQuery query = new SelectQuery(ClientMtTable1.class);
+        List objects = child.performQuery(query);
+
+        assertEquals(4, objects.size());
+
+        // delete AND modify
+        ClientMtTable1 childDeleted = (ClientMtTable1) objects.get(2);
+        child.deleteObject(childDeleted);
+        childDeleted.setGlobalAttribute1("DDD");
+
+        // don't block queries - on delete Cayenne may need to resolve delete rules via
+        // fetch
+        child.commitChangesToParent();
+
+        // * all modified child objects must be in committed state now
+        // * all modifications should be propagated to the parent
+        // * no actual commit should occur.
+
+        assertEquals(PersistenceState.TRANSIENT, childDeleted.getPersistenceState());
+
+        ClientMtTable1 parentDeleted = (ClientMtTable1) context.getGraphManager().getNode(
+                childDeleted.getObjectId());
+
+        assertNotNull(parentDeleted);
+        assertEquals(PersistenceState.DELETED, parentDeleted.getPersistenceState());
+        assertEquals("DDD", parentDeleted.getGlobalAttribute1());
+    }
+
+    public void testCommitChanges() throws Exception {
+        deleteTestData();
+
+        context.newObject(ClientMtTable1.class);
+        context.newObject(ClientMtTable1.class);
+        context.newObject(ClientMtTable1.class);
+        context.newObject(ClientMtTable1.class);
+        context.commitChanges();
+
+        ObjectContext child = context.createChildObjectContext();
+
+        // make sure we fetch in predictable order
+        SelectQuery query = new SelectQuery(ClientMtTable1.class);
+        List objects = child.performQuery(query);
+
+        assertEquals(4, objects.size());
+
+        ClientMtTable1 childNew = child.newObject(ClientMtTable1.class);
+        childNew.setGlobalAttribute1("NNN");
+
+        ClientMtTable1 childModified = (ClientMtTable1) objects.get(0);
+        childModified.setGlobalAttribute1("MMM");
+
+        ClientMtTable1 childCommitted = (ClientMtTable1) objects.get(1);
+
+        // delete AND modify
+        ClientMtTable1 childDeleted = (ClientMtTable1) objects.get(2);
+        child.deleteObject(childDeleted);
+        childDeleted.setGlobalAttribute1("DDD");
+
+        ClientMtTable1 childHollow = (ClientMtTable1) objects.get(3);
+        child.invalidateObjects(Collections.singleton(childHollow));
+
+        child.commitChanges();
+
+        assertEquals(PersistenceState.COMMITTED, childNew.getPersistenceState());
+        assertEquals(PersistenceState.COMMITTED, childModified.getPersistenceState());
+        assertEquals(PersistenceState.COMMITTED, childCommitted.getPersistenceState());
+        assertEquals(PersistenceState.TRANSIENT, childDeleted.getPersistenceState());
+        assertEquals(PersistenceState.HOLLOW, childHollow.getPersistenceState());
+
+        ClientMtTable1 parentNew = (ClientMtTable1) context.getGraphManager().getNode(
+                childNew.getObjectId());
+        ClientMtTable1 parentModified = (ClientMtTable1) context.getGraphManager().getNode(
+                childModified.getObjectId());
+        ClientMtTable1 parentCommitted = (ClientMtTable1) context.getGraphManager().getNode(
+                childCommitted.getObjectId());
+        ClientMtTable1 parentDeleted = (ClientMtTable1) context.getGraphManager().getNode(
+                childDeleted.getObjectId());
+        ClientMtTable1 parentHollow = (ClientMtTable1) context.getGraphManager().getNode(
+                childHollow.getObjectId());
+
+        assertNotNull(parentNew);
+        assertEquals(PersistenceState.COMMITTED, parentNew.getPersistenceState());
+        assertEquals("NNN", parentNew.getGlobalAttribute1());
+
+        assertNotNull(parentModified);
+        assertEquals(PersistenceState.COMMITTED, parentModified.getPersistenceState());
+        assertEquals("MMM", parentModified.getGlobalAttribute1());
+
+        assertNull("Deleted object should not be registered.", parentDeleted);
+
+        assertNotNull(parentCommitted);
+        assertEquals(PersistenceState.COMMITTED, parentCommitted.getPersistenceState());
+
+        assertNotNull(parentHollow);
+    }
+
+    public void testAddRemove() throws Exception {
+        deleteTestData();
+        ObjectContext child = context.createChildObjectContext();
+
+        ClientMtTable1 a = child.newObject(ClientMtTable1.class);
+        a.setGlobalAttribute1("X");
+        child.commitChanges();
+
+        ClientMtTable2 p1 = child.newObject(ClientMtTable2.class);
+        p1.setGlobalAttribute("P1");
+        a.addToTable2Array(p1);
+
+        ClientMtTable2 p2 = child.newObject(ClientMtTable2.class);
+        p2.setGlobalAttribute("P2");
+        a.addToTable2Array(p2);
+
+        a.removeFromTable2Array(p2);
+
+        // this causes an error on commit
+        child.deleteObject(p2);
+
+        child.commitChangesToParent();
+
+    }
+
+    public void testChangeRel() throws Exception {
+        deleteTestData();
+        ObjectContext child = context.createChildObjectContext();
+
+        ClientMtTable1 a = child.newObject(ClientMtTable1.class);
+        ClientMtTable2 b = child.newObject(ClientMtTable2.class);
+        child.commitChanges();
+
+        assertEquals(PersistenceState.COMMITTED, a.getPersistenceState());
+
+        a.addToTable2Array(b);
+        assertEquals(PersistenceState.MODIFIED, a.getPersistenceState());
+
+        child.commitChangesToParent();
+        ClientMtTable1 parentA = (ClientMtTable1) context.getGraphManager().getNode(a.getObjectId());
+        assertEquals(PersistenceState.COMMITTED, a.getPersistenceState());
+        assertEquals(PersistenceState.MODIFIED, parentA.getPersistenceState());
+        assertEquals(1, parentA.getTable2Array().size());
+
+        context.commitChanges();
+        assertEquals(PersistenceState.COMMITTED, parentA.getPersistenceState());
+
+        a.removeFromTable2Array(b);
+        assertEquals(PersistenceState.MODIFIED, a.getPersistenceState());
+
+        child.commitChangesToParent();
+        assertEquals(PersistenceState.COMMITTED, a.getPersistenceState());
+        assertEquals(PersistenceState.MODIFIED, parentA.getPersistenceState());
+        assertEquals(0, parentA.getTable2Array().size());
+    }
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/RemoteCayenneCase.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/RemoteCayenneCase.java?rev=724771&r1=724770&r2=724771&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/RemoteCayenneCase.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/RemoteCayenneCase.java Tue Dec  9 08:39:56 2008
@@ -33,10 +33,12 @@
 public abstract class RemoteCayenneCase extends CayenneCase {
     protected CayenneContext context;
     
+    protected DataContext parentDataContext;
+    
     @Override
     public void setUp() throws Exception {
-        DataContext dataContext = createDataContext();
-        ClientServerChannel clientServerChannel = new ClientServerChannel(dataContext);
+        parentDataContext = createDataContext();
+        ClientServerChannel clientServerChannel = new ClientServerChannel(parentDataContext);
         UnitLocalConnection connection = new UnitLocalConnection(
                 clientServerChannel,
                 LocalConnection.HESSIAN_SERIALIZATION);