You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2009/04/05 18:44:12 UTC

svn commit: r762115 - in /cayenne/main/trunk: docs/doc/src/main/resources/ framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/graph/ framework/cayenne-jdk1.5-unpu...

Author: aadamchik
Date: Sun Apr  5 16:44:11 2009
New Revision: 762115

URL: http://svn.apache.org/viewvc?rev=762115&view=rev
Log:
CAY-1204 Incorrect relationship syncing in nested CayenneContext's

unit tests, fix

Added:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextChildDiffLoader.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/PropertyChangeProcessingStrategy.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/NestedCayenneContextTest.java
      - copied, changed from r761544, cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtTooneDep.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtTooneMaster.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtTooneDep.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtTooneMaster.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtTooneDep.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtTooneMaster.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtTooneDep.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtTooneMaster.java
Removed:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextTest.java
Modified:
    cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt
    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/CayenneContextGraphAction.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/graph/ChildDiffLoader.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/multi-tier.map.xml

Modified: cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt?rev=762115&r1=762114&r2=762115&view=diff
==============================================================================
--- cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt (original)
+++ cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt Sun Apr  5 16:44:11 2009
@@ -49,6 +49,8 @@
 CAY-1183 commitToParent() makes object persistence state committed, produces exception when using object in parent context (ROP)
 CAY-1194 problems with relationships when using nested contexts and ROP
 CAY-1196 CayenneRuntimeException in modeler due to ClassNotFoundException when java type is invalid and db attribute is null
+CAY-1204 Incorrect relationship syncing in nested CayenneContext's
+
 ----------------------------------
 Release: 3.0M5
 Date: 15 Dec 2008

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=762115&r1=762114&r2=762115&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 Sun Apr  5 16:44:11 2009
@@ -25,7 +25,6 @@
 import java.util.List;
 
 import org.apache.cayenne.event.EventManager;
-import org.apache.cayenne.graph.ChildDiffLoader;
 import org.apache.cayenne.graph.CompoundDiff;
 import org.apache.cayenne.graph.GraphDiff;
 import org.apache.cayenne.graph.GraphManager;
@@ -46,6 +45,17 @@
  */
 public class CayenneContext extends BaseContext {
 
+    /**
+     * @since 3.0
+     */
+    private static ThreadLocal<PropertyChangeProcessingStrategy> PROPERTY_CHANGE_PROCESSING_STRATEGY = new ThreadLocal<PropertyChangeProcessingStrategy>() {
+
+        @Override
+        protected PropertyChangeProcessingStrategy initialValue() {
+            return PropertyChangeProcessingStrategy.RECORD_AND_PROCESS_REVERSE_ARCS;
+        }
+    };
+
     protected EntityResolver entityResolver;
 
     CayenneContextGraphManager graphManager;
@@ -60,11 +70,6 @@
     CayenneContextMergeHandler mergeHandler;
 
     /**
-     * @since 3.0
-     */
-    boolean propertyChangeCallbacksDisabled;
-
-    /**
      * Creates a new CayenneContext with no channel and disabled graph events.
      */
     public CayenneContext() {
@@ -98,6 +103,24 @@
     }
 
     /**
+     * @since 3.0
+     */
+    // accesses a static thread local variable... still keeping the method non-static for
+    // better future portability...
+    PropertyChangeProcessingStrategy getPropertyChangeProcessingStrategy() {
+        return PROPERTY_CHANGE_PROCESSING_STRATEGY.get();
+    }
+
+    /**
+     * @since 3.0
+     */
+    // accesses a static thread local variable... still keeping the method non-static for
+    // better future portability...
+    void setPropertyChangeProcessingStrategy(PropertyChangeProcessingStrategy strategy) {
+        PROPERTY_CHANGE_PROCESSING_STRATEGY.set(strategy);
+    }
+
+    /**
      * Sets the context channel, setting up a listener for channel events.
      */
     public void setChannel(DataChannel channel) {
@@ -136,7 +159,8 @@
      * Returns true if this context posts lifecycle events. Subjects used for these events
      * are
      * <code>ObjectContext.GRAPH_COMMIT_STARTED_SUBJECT, ObjectContext.GRAPH_COMMITTED_SUBJECT,
-     * ObjectContext.GRAPH_COMMIT_ABORTED_SUBJECT, ObjectContext.GRAPH_ROLLEDBACK_SUBJECT.</code>.
+     * ObjectContext.GRAPH_COMMIT_ABORTED_SUBJECT, ObjectContext.GRAPH_ROLLEDBACK_SUBJECT.</code>
+     * .
      */
     public boolean isLifecycleEventsEnabled() {
         return graphManager.lifecycleEventsEnabled;
@@ -226,7 +250,7 @@
                 graphManager.graphCommitStarted();
 
                 GraphDiff changes = graphManager.getDiffsSinceLastFlush();
-                
+
                 try {
                     commitDiff = channel.onSync(this, changes, syncType);
                 }
@@ -242,7 +266,7 @@
                 }
 
                 graphManager.graphCommitted(commitDiff);
-                
+
                 // this event is caught by peer nested ObjectContexts to synchronize the
                 // state
                 fireDataChannelCommitted(this, changes);
@@ -277,7 +301,7 @@
             if (graphManager.hasChanges()) {
                 GraphDiff diff = graphManager.getDiffs();
                 graphManager.graphReverted();
-                
+
                 fireDataChannelRolledback(this, diff);
             }
         }
@@ -442,7 +466,7 @@
             Object oldValue,
             Object newValue) {
 
-        if (!isPropertyChangeCallbacksDisabled()) {
+        if (getPropertyChangeProcessingStrategy() != PropertyChangeProcessingStrategy.IGNORE) {
             graphAction.handlePropertyChange(object, property, oldValue, newValue);
         }
     }
@@ -482,8 +506,8 @@
             String entityName,
             ClassDescriptor descriptor) {
         /**
-         * We should create new id only if it is not set for this object.
-         * It could have been created, for instance, in child context
+         * 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) {
@@ -521,26 +545,14 @@
     }
 
     /**
-     * @since 3.0
-     */
-    boolean isPropertyChangeCallbacksDisabled() {
-        return propertyChangeCallbacksDisabled;
-    }
-
-    /**
-     * @since 3.0
-     */
-    void setPropertyChangeCallbacksDisabled(boolean propertyChangeCallbacksDisabled) {
-        this.propertyChangeCallbacksDisabled = propertyChangeCallbacksDisabled;
-    }
-
-    /**
      * Creates and returns a new child ObjectContext.
      * 
      * @since 3.0
      */
     public ObjectContext createChildContext() {
-        return new CayenneContext(this, graphManager.changeEventsEnabled, 
+        return new CayenneContext(
+                this,
+                graphManager.changeEventsEnabled,
                 graphManager.lifecycleEventsEnabled);
     }
 
@@ -553,13 +565,22 @@
         boolean childContext = this != originatingContext && changes != null;
 
         if (childContext) {
-           changes.apply(new CayenneContextChildDiffLoader(this));
-           fireDataChannelChanged(originatingContext, changes);
+
+            PropertyChangeProcessingStrategy oldStrategy = getPropertyChangeProcessingStrategy();
+            setPropertyChangeProcessingStrategy(PropertyChangeProcessingStrategy.RECORD);
+            try {
+                changes.apply(new CayenneContextChildDiffLoader(this));
+            }
+            finally {
+                setPropertyChangeProcessingStrategy(oldStrategy);
+            }
+            
+            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.
@@ -567,30 +588,4 @@
     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);
-        }
-    }
 }

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextChildDiffLoader.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextChildDiffLoader.java?rev=762115&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextChildDiffLoader.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextChildDiffLoader.java Sun Apr  5 16:44:11 2009
@@ -0,0 +1,177 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.graph.ChildDiffLoader;
+import org.apache.cayenne.reflect.ArcProperty;
+import org.apache.cayenne.reflect.AttributeProperty;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.reflect.Property;
+import org.apache.cayenne.reflect.PropertyVisitor;
+import org.apache.cayenne.reflect.ToManyProperty;
+import org.apache.cayenne.reflect.ToOneProperty;
+
+/**
+ * Used for loading child's CayenneContext changes to parent context.
+ * 
+ * @since 3.0
+ */
+class CayenneContextChildDiffLoader extends ChildDiffLoader {
+
+    public CayenneContextChildDiffLoader(CayenneContext context) {
+        super(context);
+    }
+
+    @Override
+    public void nodePropertyChanged(
+            Object nodeId,
+            String property,
+            Object oldValue,
+            Object newValue) {
+
+        super.nodePropertyChanged(nodeId, property, oldValue, newValue);
+
+        Persistent object = (Persistent) context.getGraphManager().getNode(nodeId);
+        context.propertyChanged(object, property, oldValue, newValue);
+    }
+
+    @Override
+    public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
+
+        final Persistent source = findObject(nodeId);
+        final Persistent target = findObject(targetNodeId);
+
+        // if a target was later deleted, the diff for arcCreated is still preserved and
+        // can result in NULL target here.
+        if (target == null) {
+            return;
+        }
+
+        ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(
+                ((ObjectId) nodeId).getEntityName());
+        ArcProperty property = (ArcProperty) descriptor.getProperty(arcId.toString());
+
+        // TODO: context strategy reset here still hides the difference between to-one and
+        // to-many per CAY-1204... hopefully it will go away if we do refactoring around
+        // property change strategy instead of using "changeX vs. changeXDirectly".
+        PropertyChangeProcessingStrategy oldStrategy = ((CayenneContext) context)
+                .getPropertyChangeProcessingStrategy();
+        ((CayenneContext) context)
+                .setPropertyChangeProcessingStrategy(PropertyChangeProcessingStrategy.IGNORE);
+        try {
+            property.visit(new PropertyVisitor() {
+
+                public boolean visitAttribute(AttributeProperty property) {
+                    return false;
+                }
+
+                public boolean visitToMany(ToManyProperty property) {
+                    // connect reverse arc if the relationship is marked as "runtime"
+                    ArcProperty reverseArc = property.getComplimentaryReverseArc();
+                    boolean autoConnectReverse = reverseArc != null
+                            && reverseArc.getRelationship().isRuntime();
+
+                    property.addTarget(source, target, autoConnectReverse);
+                    return false;
+                }
+
+                public boolean visitToOne(ToOneProperty property) {
+                    property.setTarget(source, target, false);
+                    return false;
+                }
+            });
+        }
+        finally {
+            ((CayenneContext) context).setPropertyChangeProcessingStrategy(oldStrategy);
+        }
+
+        context.propertyChanged(source, (String) arcId, null, target);
+    }
+
+    @Override
+    public void arcDeleted(Object nodeId, final Object targetNodeId, Object arcId) {
+        final Persistent source = findObject(nodeId);
+
+        // needed as sometime temporary objects are evoked from the context before
+        // changing their relationships
+        if (source == null) {
+            return;
+        }
+
+        ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(
+                ((ObjectId) nodeId).getEntityName());
+        Property property = descriptor.getProperty(arcId.toString());
+
+        // TODO: context strategy reset here still hides the difference between to-one and
+        // to-many per CAY-1204... hopefully it will go away if we do refactoring around
+        // property change strategy instead of using "changeX vs. changeXDirectly".
+        PropertyChangeProcessingStrategy oldStrategy = ((CayenneContext) context)
+                .getPropertyChangeProcessingStrategy();
+        ((CayenneContext) context)
+                .setPropertyChangeProcessingStrategy(PropertyChangeProcessingStrategy.IGNORE);
+        final Persistent[] target = new Persistent[1];
+        target[0] = findObject(targetNodeId);
+        
+        try {
+            property.visit(new PropertyVisitor() {
+
+                public boolean visitAttribute(AttributeProperty property) {
+                    return false;
+                }
+
+                public boolean visitToMany(ToManyProperty property) {
+                    // connect reverse arc if the relationship is marked as "runtime"
+                    ArcProperty reverseArc = property.getComplimentaryReverseArc();
+                    boolean autoConnectReverse = reverseArc != null
+                            && reverseArc.getRelationship().isRuntime();
+
+                    if (target[0] == null) {
+
+                        // this is usually the case when a NEW object was deleted and then
+                        // its relationships were manipulated; so try to locate the object
+                        // in the collection ... the performance of this is rather dubious
+                        // of course...
+                        target[0] = findObjectInCollection(targetNodeId, property
+                                .readProperty(source));
+                    }
+
+                    if (target[0] == null) {
+                        // ignore?
+                    }
+                    else {
+                        property.removeTarget(source, target[0], autoConnectReverse);
+                    }
+
+                    return false;
+                }
+
+                public boolean visitToOne(ToOneProperty property) {
+                    property.setTarget(source, null, false);
+                    return false;
+                }
+            });
+        }
+        finally {
+            ((CayenneContext) context).setPropertyChangeProcessingStrategy(oldStrategy);
+        }
+
+        context.propertyChanged(source, (String) arcId, target[0], null);
+    }
+
+}
\ No newline at end of file

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextGraphAction.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextGraphAction.java?rev=762115&r1=762114&r2=762115&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextGraphAction.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContextGraphAction.java Sun Apr  5 16:44:11 2009
@@ -42,7 +42,7 @@
         }
     };
 
-    CayenneContextGraphAction(ObjectContext context) {
+    CayenneContextGraphAction(CayenneContext context) {
         super(context);
     }
 
@@ -57,6 +57,9 @@
             return;
         }
 
+        boolean processsReverse = ((CayenneContext) context)
+                .getPropertyChangeProcessingStrategy() == PropertyChangeProcessingStrategy.RECORD_AND_PROCESS_REVERSE_ARCS;
+
         // prevent reverse actions down the stack
         setArcChangeInProcess(true);
 
@@ -67,7 +70,10 @@
                         ((Persistent) oldValue).getObjectId(),
                         property.getName());
 
-                unsetReverse(property, object, (Persistent) oldValue);
+                if (processsReverse) {
+                    unsetReverse(property, object, (Persistent) oldValue);
+                }
+
                 markAsDirty(object);
             }
 
@@ -77,7 +83,10 @@
                         ((Persistent) newValue).getObjectId(),
                         property.getName());
 
-                setReverse(property, object, (Persistent) newValue);
+                if (processsReverse) {
+                    setReverse(property, object, (Persistent) newValue);
+                }
+                
                 markAsDirty(object);
             }
         }

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=762115&r1=762114&r2=762115&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 Sun Apr  5 16:44:11 2009
@@ -135,14 +135,14 @@
         }
 
         remapTargets();
-        
+
         stateLog.graphCommitted();
         reset();
 
         if (lifecycleEventsEnabled) {
             // include all diffs after the commit start marker.
-            //We fire event as if it was posted by parent channel, so that
-            //nested contexts could catch it
+            // We fire event as if it was posted by parent channel, so that
+            // nested contexts could catch it
             context.fireDataChannelCommitted(context.getChannel(), parentSyncDiff);
         }
     }
@@ -158,8 +158,10 @@
         EntityResolver resolver = context.getEntityResolver();
 
         // avoid processing callbacks when updating the map...
-        boolean changeCallbacks = context.isPropertyChangeCallbacksDisabled();
-        context.setPropertyChangeCallbacksDisabled(true);
+        PropertyChangeProcessingStrategy oldChangeStrategy = context
+                .getPropertyChangeProcessingStrategy();
+        context
+                .setPropertyChangeProcessingStrategy(PropertyChangeProcessingStrategy.IGNORE);
 
         try {
             while (it.hasNext()) {
@@ -186,7 +188,7 @@
             }
         }
         finally {
-            context.setPropertyChangeCallbacksDisabled(changeCallbacks);
+            context.setPropertyChangeProcessingStrategy(oldChangeStrategy);
         }
     }
 
@@ -337,6 +339,7 @@
      * This change handler is used to perform rollback actions for Cayenne context
      */
     class RollbackChangeHandler implements GraphChangeHandler {
+
         public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
             context.mergeHandler.arcCreated(nodeId, targetNodeId, arcId);
             CayenneContextGraphManager.this.arcCreated(nodeId, targetNodeId, arcId);
@@ -356,15 +359,20 @@
         }
 
         /**
-         * Need to write property directly to this context 
+         * Need to write property directly to this context
          */
         public void nodePropertyChanged(
                 Object nodeId,
                 String property,
                 Object oldValue,
                 Object newValue) {
-            context.mergeHandler.nodePropertyChanged(nodeId, property, oldValue, newValue);
-            CayenneContextGraphManager.this.nodePropertyChanged(nodeId, property, oldValue, newValue);
+            context.mergeHandler
+                    .nodePropertyChanged(nodeId, property, oldValue, newValue);
+            CayenneContextGraphManager.this.nodePropertyChanged(
+                    nodeId,
+                    property,
+                    oldValue,
+                    newValue);
         }
 
         public void nodeRemoved(Object nodeId) {

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/PropertyChangeProcessingStrategy.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/PropertyChangeProcessingStrategy.java?rev=762115&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/PropertyChangeProcessingStrategy.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/PropertyChangeProcessingStrategy.java Sun Apr  5 16:44:11 2009
@@ -0,0 +1,48 @@
+/*****************************************************************
+ *   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;
+
+/**
+ * Defines how ObjectContext should behave in response to
+ * {@link ObjectContext#propertyChanged(Persistent, String, Object, Object)} calls.
+ * 
+ * @since 3.0
+ */
+enum PropertyChangeProcessingStrategy {
+
+    /**
+     * A strategy that does no extra processing of property changes. Usually used when
+     * already committed changes are merged from a downstream channel.
+     */
+    IGNORE,
+
+    /**
+     * A strategy that records the change in the change log and marks participating
+     * objects as dirty, but no attempt is made to process reverse relationships. Usually
+     * used when processing changes from an upstream (child) context.
+     */
+    RECORD,
+
+    /**
+     * A strategy that records the change in the change log and marks participating
+     * objects as dirty, and then initiates a complimentary change to the reverse
+     * relationships. The default operation mode used for changes initiated by the user.
+     */
+    RECORD_AND_PROCESS_REVERSE_ARCS;
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/graph/ChildDiffLoader.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/graph/ChildDiffLoader.java?rev=762115&r1=762114&r2=762115&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/graph/ChildDiffLoader.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/graph/ChildDiffLoader.java Sun Apr  5 16:44:11 2009
@@ -42,13 +42,15 @@
 
 /**
  * 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
+ * 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
  */
 public class ChildDiffLoader implements GraphChangeHandler {
 
+    // TODO: andrus 04/05/2009 - replace with PropertyChangeProcessingStrategy enum used
+    // in ROP?
     static final ThreadLocal<Boolean> childDiffProcessing = new ThreadLocal<Boolean>() {
 
         @Override
@@ -57,7 +59,7 @@
         }
     };
 
-    private ObjectContext context;
+    protected ObjectContext context;
 
     /**
      * Returns whether child diff processing is in progress.
@@ -142,8 +144,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);
@@ -259,7 +261,7 @@
         }
     }
 
-    Persistent findObject(Object nodeId) {
+    protected Persistent findObject(Object nodeId) {
         // first do a lookup in ObjectStore; if even a hollow object is found, return it;
         // if not - fetch.
 
@@ -293,7 +295,7 @@
         return (Persistent) objects.get(0);
     }
 
-    Persistent findObjectInCollection(Object nodeId, Object toManyHolder) {
+    protected Persistent findObjectInCollection(Object nodeId, Object toManyHolder) {
         Collection c = (toManyHolder instanceof Map)
                 ? ((Map) toManyHolder).values()
                 : (Collection) toManyHolder;

Copied: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/NestedCayenneContextTest.java (from r761544, 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/NestedCayenneContextTest.java?p2=cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/NestedCayenneContextTest.java&p1=cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextTest.java&r1=761544&r2=762115&rev=762115&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/NestedObjectContextTest.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/NestedCayenneContextTest.java Sun Apr  5 16:44:11 2009
@@ -16,26 +16,27 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
-package org.apache.cayenne.remote;
+package org.apache.cayenne;
 
 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.graph.GraphChangeHandler;
+import org.apache.cayenne.graph.GraphDiff;
 import org.apache.cayenne.query.ObjectIdQuery;
 import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.remote.RemoteCayenneCase;
 import org.apache.cayenne.testdo.mt.ClientMtTable1;
 import org.apache.cayenne.testdo.mt.ClientMtTable2;
+import org.apache.cayenne.testdo.mt.ClientMtTooneDep;
+import org.apache.cayenne.testdo.mt.ClientMtTooneMaster;
 
 /**
  * Tests nested object contexts
  */
-public class NestedObjectContextTest extends RemoteCayenneCase {
+public class NestedCayenneContextTest extends RemoteCayenneCase {
+
     public void testChannels() {
         ObjectContext child = context.createChildContext();
 
@@ -65,7 +66,8 @@
 
         ClientMtTable1 _new = context.newObject(ClientMtTable1.class);
 
-        ClientMtTable1 hollow = (ClientMtTable1) context.localObject(new ObjectId("MtTable1"), null);
+        ClientMtTable1 hollow = (ClientMtTable1) context.localObject(new ObjectId(
+                "MtTable1"), null);
 
         assertEquals(PersistenceState.HOLLOW, hollow.getPersistenceState());
         assertEquals(PersistenceState.COMMITTED, committed.getPersistenceState());
@@ -90,13 +92,16 @@
             assertSame(child, hollowPeer.getObjectContext());
             assertSame(context, hollow.getObjectContext());
 
-            Persistent committedPeer = child.localObject(committed.getObjectId(), committed);
+            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);
+            ClientMtTable1 modifiedPeer = (ClientMtTable1) child.localObject(modified
+                    .getObjectId(), modified);
             assertEquals(PersistenceState.COMMITTED, modifiedPeer.getPersistenceState());
             assertEquals(modified.getObjectId(), modifiedPeer.getObjectId());
             assertEquals("a", modifiedPeer.getGlobalAttribute1());
@@ -123,7 +128,8 @@
         context.commitChanges();
 
         ClientMtTable1 peerModified = (ClientMtTable1) DataObjectUtils.objectForQuery(
-                child, new ObjectIdQuery(modified.getObjectId()));
+                child,
+                new ObjectIdQuery(modified.getObjectId()));
 
         modified.setGlobalAttribute1("M1");
         peerModified.setGlobalAttribute1("M2");
@@ -135,7 +141,8 @@
 
         try {
 
-            Persistent peerModified2 = child.localObject(modified.getObjectId(), modified);
+            Persistent peerModified2 = child
+                    .localObject(modified.getObjectId(), modified);
             assertSame(peerModified, peerModified2);
             assertEquals(PersistenceState.MODIFIED, peerModified2.getPersistenceState());
             assertEquals("M2", peerModified.getGlobalAttribute1());
@@ -158,10 +165,13 @@
         blockQueries();
 
         try {
-            ClientMtTable2 child2 = (ClientMtTable2) child.localObject(_new2.getObjectId(), _new2);
+            ClientMtTable2 child2 = (ClientMtTable2) child.localObject(_new2
+                    .getObjectId(), _new2);
             assertEquals(PersistenceState.COMMITTED, child2.getPersistenceState());
             assertNotNull(child2.getTable1());
-            assertEquals(PersistenceState.COMMITTED, child2.getTable1().getPersistenceState());
+            assertEquals(PersistenceState.COMMITTED, child2
+                    .getTable1()
+                    .getPersistenceState());
         }
         finally {
             unblockQueries();
@@ -313,7 +323,8 @@
         context.commitChanges();
 
         ClientMtTable2 p = child.newObject(ClientMtTable2.class);
-        ClientMtTable1 aChild = (ClientMtTable1) DataObjectUtils.objectForPK(child, a.getObjectId());
+        ClientMtTable1 aChild = (ClientMtTable1) DataObjectUtils.objectForPK(child, a
+                .getObjectId());
         p.setGlobalAttribute("X");
         aChild.addToTable2Array(p);
 
@@ -340,7 +351,8 @@
         ObjectContext child = context.createChildContext();
         ObjectContext childPeer = context.createChildContext();
 
-        ClientMtTable2 childP1 = (ClientMtTable2) DataObjectUtils.objectForPK(child, b.getObjectId());
+        ClientMtTable2 childP1 = (ClientMtTable2) DataObjectUtils.objectForPK(child, b
+                .getObjectId());
 
         // trigger object creation in the peer nested DC
         DataObjectUtils.objectForPK(childPeer, b.getObjectId());
@@ -358,6 +370,41 @@
             assertNotNull(parentP1);
             assertEquals(PersistenceState.MODIFIED, parentP1.getPersistenceState());
             assertNull(parentP1.getTable1());
+
+            // check that arc changes got recorded in the parent context
+            GraphDiff diffs = context.internalGraphManager().getDiffs();
+            final int[] arcDiffs = new int[1];
+
+            diffs.apply(new GraphChangeHandler() {
+
+                public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
+                    arcDiffs[0]++;
+                }
+
+                public void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) {
+                    arcDiffs[0]--;
+                }
+
+                public void nodeCreated(Object nodeId) {
+
+                }
+
+                public void nodeIdChanged(Object nodeId, Object newId) {
+                }
+
+                public void nodePropertyChanged(
+                        Object nodeId,
+                        String property,
+                        Object oldValue,
+                        Object newValue) {
+                }
+
+                public void nodeRemoved(Object nodeId) {
+
+                }
+            });
+
+            assertEquals(-2, arcDiffs[0]);
         }
         finally {
             unblockQueries();
@@ -405,14 +452,18 @@
             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());
+            ClientMtTable1 parentNew = (ClientMtTable1) context
+                    .getGraphManager()
+                    .getNode(childNew.getObjectId());
+            final 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());
@@ -427,6 +478,46 @@
                     .getPersistenceState());
 
             assertNotNull(parentHollow);
+
+            // check that arc changes got recorded in the parent context
+            GraphDiff diffs = context.internalGraphManager().getDiffs();
+
+            final int[] modifiedProperties = new int[1];
+
+            diffs.apply(new GraphChangeHandler() {
+
+                public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
+
+                }
+
+                public void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) {
+
+                }
+
+                public void nodeCreated(Object nodeId) {
+
+                }
+
+                public void nodeIdChanged(Object nodeId, Object newId) {
+                }
+
+                public void nodePropertyChanged(
+                        Object nodeId,
+                        String property,
+                        Object oldValue,
+                        Object newValue) {
+
+                    if (nodeId.equals(parentModified.getObjectId())) {
+                        modifiedProperties[0]++;
+                    }
+                }
+
+                public void nodeRemoved(Object nodeId) {
+
+                }
+            });
+
+            assertEquals(1, modifiedProperties[0]);
         }
         finally {
             unblockQueries();
@@ -465,8 +556,9 @@
 
         assertEquals(PersistenceState.TRANSIENT, childDeleted.getPersistenceState());
 
-        ClientMtTable1 parentDeleted = (ClientMtTable1) context.getGraphManager().getNode(
-                childDeleted.getObjectId());
+        ClientMtTable1 parentDeleted = (ClientMtTable1) context
+                .getGraphManager()
+                .getNode(childDeleted.getObjectId());
 
         assertNotNull(parentDeleted);
         assertEquals(PersistenceState.DELETED, parentDeleted.getPersistenceState());
@@ -516,12 +608,15 @@
 
         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 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());
 
@@ -580,7 +675,8 @@
         assertEquals(PersistenceState.MODIFIED, a.getPersistenceState());
 
         child.commitChangesToParent();
-        ClientMtTable1 parentA = (ClientMtTable1) context.getGraphManager().getNode(a.getObjectId());
+        ClientMtTable1 parentA = (ClientMtTable1) context.getGraphManager().getNode(
+                a.getObjectId());
         assertEquals(PersistenceState.COMMITTED, a.getPersistenceState());
         assertEquals(PersistenceState.MODIFIED, parentA.getPersistenceState());
         assertEquals(1, parentA.getTable2Array().size());
@@ -604,7 +700,9 @@
         context.commitChanges();
 
         ObjectContext child = context.createChildContext();
-        ClientMtTable1 childMt = (ClientMtTable1) DataObjectUtils.objectForPK(child, parentMt.getObjectId());
+        ClientMtTable1 childMt = (ClientMtTable1) DataObjectUtils.objectForPK(
+                child,
+                parentMt.getObjectId());
         childMt.setGlobalAttribute1("1183");
         ClientMtTable2 childMt2 = child.newObject(ClientMtTable2.class);
         childMt2.setGlobalAttribute("1183");
@@ -612,32 +710,160 @@
 
         child.commitChangesToParent();
 
-        //fetching other relationship... this fails per CAY-1183
+        // fetching other relationship... this fails per CAY-1183
         childMt2.getTable3();
     }
-    
+
     public void testCAY1194() throws Exception {
         deleteTestData();
-        
-        ClientMtTable1 parentMt = context.newObject(ClientMtTable1.class);        
+
+        ClientMtTable1 parentMt = context.newObject(ClientMtTable1.class);
         ObjectContext child = context.createChildContext();
-        
+
         ClientMtTable2 childMt2 = child.newObject(ClientMtTable2.class);
         childMt2.setGlobalAttribute("222");
-        
-        ClientMtTable1 localParentMt = (ClientMtTable1) child.localObject(parentMt.getObjectId(), null);
+
+        ClientMtTable1 localParentMt = (ClientMtTable1) child.localObject(parentMt
+                .getObjectId(), null);
         assertEquals(0, parentMt.getTable2Array().size());
         assertEquals(0, localParentMt.getTable2Array().size());
-        
+
         childMt2.setTable1(localParentMt);
-        
+
         assertEquals(0, parentMt.getTable2Array().size());
         assertEquals(1, localParentMt.getTable2Array().size());
-        
-        assertEquals(((Persistent) localParentMt.getTable2Array().get(0)).getObjectContext(), child);
-        
+
+        assertEquals(((Persistent) localParentMt.getTable2Array().get(0))
+                .getObjectContext(), child);
+
         child.commitChangesToParent();
         assertEquals(1, parentMt.getTable2Array().size());
-        assertEquals(((Persistent) parentMt.getTable2Array().get(0)).getObjectContext(), context);
+        assertEquals(
+                ((Persistent) parentMt.getTable2Array().get(0)).getObjectContext(),
+                context);
+    }
+
+    public void testCommitChangesToParentOneToMany() throws Exception {
+        deleteTestData();
+
+        ObjectContext child = context.createChildContext();
+
+        ClientMtTable1 master = child.newObject(ClientMtTable1.class);
+        ClientMtTable2 dep = child.newObject(ClientMtTable2.class);
+        master.addToTable2Array(dep);
+
+        child.commitChangesToParent();
+
+        ClientMtTable1 masterParent = (ClientMtTable1) context.getGraphManager().getNode(
+                master.getObjectId());
+        ClientMtTable2 depParent = (ClientMtTable2) context.getGraphManager().getNode(
+                dep.getObjectId());
+
+        assertNotNull(masterParent);
+        assertNotNull(depParent);
+
+        assertSame(masterParent, depParent.getTable1());
+        assertTrue(masterParent.getTable2Array().contains(depParent));
+
+        // check that arc changes got recorded in the parent context
+        GraphDiff diffs = context.internalGraphManager().getDiffs();
+
+        final int[] arcDiffs = new int[1];
+        final int[] newNodes = new int[1];
+
+        diffs.apply(new GraphChangeHandler() {
+
+            public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
+                arcDiffs[0]++;
+            }
+
+            public void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) {
+                arcDiffs[0]--;
+            }
+
+            public void nodeCreated(Object nodeId) {
+                newNodes[0]++;
+            }
+
+            public void nodeIdChanged(Object nodeId, Object newId) {
+            }
+
+            public void nodePropertyChanged(
+                    Object nodeId,
+                    String property,
+                    Object oldValue,
+                    Object newValue) {
+            }
+
+            public void nodeRemoved(Object nodeId) {
+                newNodes[0]--;
+            }
+        });
+
+        assertEquals(2, newNodes[0]);
+        assertEquals(2, arcDiffs[0]);
+    }
+
+    public void testCommitChangesToParentOneToOne() throws Exception {
+        deleteTestData();
+
+        ObjectContext child = context.createChildContext();
+
+        ClientMtTooneMaster master = child.newObject(ClientMtTooneMaster.class);
+        ClientMtTooneDep dep = child.newObject(ClientMtTooneDep.class);
+        master.setToDependent(dep);
+
+        child.commitChangesToParent();
+
+        ClientMtTooneMaster masterParent = (ClientMtTooneMaster) context
+                .getGraphManager()
+                .getNode(master.getObjectId());
+        ClientMtTooneDep depParent = (ClientMtTooneDep) context
+                .getGraphManager()
+                .getNode(dep.getObjectId());
+
+        assertNotNull(masterParent);
+        assertNotNull(depParent);
+
+        assertSame(masterParent, depParent.getToMaster());
+        assertSame(depParent, masterParent.getToDependent());
+
+        // check that arc changes got recorded in the parent context
+        GraphDiff diffs = context.internalGraphManager().getDiffs();
+
+        final int[] arcDiffs = new int[1];
+        final int[] newNodes = new int[1];
+
+        diffs.apply(new GraphChangeHandler() {
+
+            public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
+                arcDiffs[0]++;
+            }
+
+            public void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) {
+                arcDiffs[0]--;
+            }
+
+            public void nodeCreated(Object nodeId) {
+                newNodes[0]++;
+            }
+
+            public void nodeIdChanged(Object nodeId, Object newId) {
+            }
+
+            public void nodePropertyChanged(
+                    Object nodeId,
+                    String property,
+                    Object oldValue,
+                    Object newValue) {
+            }
+
+            public void nodeRemoved(Object nodeId) {
+                newNodes[0]--;
+            }
+        });
+
+        assertEquals(2, newNodes[0]);
+        assertEquals(2, arcDiffs[0]);
     }
 }

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtTooneDep.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtTooneDep.java?rev=762115&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtTooneDep.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtTooneDep.java Sun Apr  5 16:44:11 2009
@@ -0,0 +1,10 @@
+package org.apache.cayenne.testdo.mt;
+
+import org.apache.cayenne.testdo.mt.auto._ClientMtTooneDep;
+
+/**
+ * A persistent class mapped as "MtTooneDep" Cayenne entity.
+ */
+public class ClientMtTooneDep extends _ClientMtTooneDep {
+
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtTooneMaster.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtTooneMaster.java?rev=762115&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtTooneMaster.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtTooneMaster.java Sun Apr  5 16:44:11 2009
@@ -0,0 +1,10 @@
+package org.apache.cayenne.testdo.mt;
+
+import org.apache.cayenne.testdo.mt.auto._ClientMtTooneMaster;
+
+/**
+ * A persistent class mapped as "MtTooneMaster" Cayenne entity.
+ */
+public class ClientMtTooneMaster extends _ClientMtTooneMaster {
+
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtTooneDep.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtTooneDep.java?rev=762115&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtTooneDep.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtTooneDep.java Sun Apr  5 16:44:11 2009
@@ -0,0 +1,7 @@
+package org.apache.cayenne.testdo.mt;
+
+import org.apache.cayenne.testdo.mt.auto._MtTooneDep;
+
+public class MtTooneDep extends _MtTooneDep {
+
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtTooneMaster.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtTooneMaster.java?rev=762115&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtTooneMaster.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtTooneMaster.java Sun Apr  5 16:44:11 2009
@@ -0,0 +1,7 @@
+package org.apache.cayenne.testdo.mt;
+
+import org.apache.cayenne.testdo.mt.auto._MtTooneMaster;
+
+public class MtTooneMaster extends _MtTooneMaster {
+
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtTooneDep.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtTooneDep.java?rev=762115&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtTooneDep.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtTooneDep.java Sun Apr  5 16:44:11 2009
@@ -0,0 +1,33 @@
+package org.apache.cayenne.testdo.mt.auto;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.ValueHolder;
+import org.apache.cayenne.testdo.mt.ClientMtTooneMaster;
+
+/**
+ * A generated persistent class mapped as "MtTooneDep" Cayenne entity. It is a good idea to
+ * avoid changing this class manually, since it will be overwritten next time code is
+ * regenerated. If you need to make any customizations, put them in a subclass.
+ */
+public abstract class _ClientMtTooneDep extends PersistentObject {
+
+    public static final String TO_MASTER_PROPERTY = "toMaster";
+
+    protected ValueHolder toMaster;
+
+    public ClientMtTooneMaster getToMaster() {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "toMaster", true);
+        }
+
+        return (ClientMtTooneMaster) toMaster.getValue();
+    }
+    public void setToMaster(ClientMtTooneMaster toMaster) {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "toMaster", true);
+        }
+
+        this.toMaster.setValue(toMaster);
+    }
+
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtTooneMaster.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtTooneMaster.java?rev=762115&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtTooneMaster.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtTooneMaster.java Sun Apr  5 16:44:11 2009
@@ -0,0 +1,33 @@
+package org.apache.cayenne.testdo.mt.auto;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.ValueHolder;
+import org.apache.cayenne.testdo.mt.ClientMtTooneDep;
+
+/**
+ * A generated persistent class mapped as "MtTooneMaster" Cayenne entity. It is a good idea to
+ * avoid changing this class manually, since it will be overwritten next time code is
+ * regenerated. If you need to make any customizations, put them in a subclass.
+ */
+public abstract class _ClientMtTooneMaster extends PersistentObject {
+
+    public static final String TO_DEPENDENT_PROPERTY = "toDependent";
+
+    protected ValueHolder toDependent;
+
+    public ClientMtTooneDep getToDependent() {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "toDependent", true);
+        }
+
+        return (ClientMtTooneDep) toDependent.getValue();
+    }
+    public void setToDependent(ClientMtTooneDep toDependent) {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "toDependent", true);
+        }
+
+        this.toDependent.setValue(toDependent);
+    }
+
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtTooneDep.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtTooneDep.java?rev=762115&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtTooneDep.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtTooneDep.java Sun Apr  5 16:44:11 2009
@@ -0,0 +1,27 @@
+package org.apache.cayenne.testdo.mt.auto;
+
+import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.testdo.mt.MtTooneMaster;
+
+/**
+ * Class _MtTooneDep was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _MtTooneDep extends CayenneDataObject {
+
+    public static final String TO_MASTER_PROPERTY = "toMaster";
+
+    public static final String ID_PK_COLUMN = "ID";
+
+    public void setToMaster(MtTooneMaster toMaster) {
+        setToOneTarget("toMaster", toMaster, true);
+    }
+
+    public MtTooneMaster getToMaster() {
+        return (MtTooneMaster)readProperty("toMaster");
+    }
+
+
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtTooneMaster.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtTooneMaster.java?rev=762115&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtTooneMaster.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtTooneMaster.java Sun Apr  5 16:44:11 2009
@@ -0,0 +1,27 @@
+package org.apache.cayenne.testdo.mt.auto;
+
+import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.testdo.mt.MtTooneDep;
+
+/**
+ * Class _MtTooneMaster was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _MtTooneMaster extends CayenneDataObject {
+
+    public static final String TO_DEPENDENT_PROPERTY = "toDependent";
+
+    public static final String ID_PK_COLUMN = "ID";
+
+    public void setToDependent(MtTooneDep toDependent) {
+        setToOneTarget("toDependent", toDependent, true);
+    }
+
+    public MtTooneDep getToDependent() {
+        return (MtTooneDep)readProperty("toDependent");
+    }
+
+
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/multi-tier.map.xml
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/multi-tier.map.xml?rev=762115&r1=762114&r2=762115&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/multi-tier.map.xml (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/multi-tier.map.xml Sun Apr  5 16:44:11 2009
@@ -70,6 +70,12 @@
 		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
 		<db-attribute name="NUMERIC_COLUMN" type="INTEGER"/>
 	</db-entity>
+	<db-entity name="MT_TOONE_DEP">
+		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+	</db-entity>
+	<db-entity name="MT_TOONE_MASTER">
+		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+	</db-entity>
 	<obj-entity name="MtDeleteCascade" className="org.apache.cayenne.testdo.mt.MtDeleteCascade" clientClassName="org.apache.cayenne.testdo.mt.ClientMtDeleteCascade" dbEntityName="MT_DELETE_CASCADE">
 		<obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
 	</obj-entity>
@@ -116,6 +122,10 @@
 		<obj-attribute name="blablacheck" type="boolean" db-attribute-path="BOOLEAN_COLUMN"/>
 		<obj-attribute name="number" type="int" db-attribute-path="NUMERIC_COLUMN"/>
 	</obj-entity>
+	<obj-entity name="MtTooneDep" className="org.apache.cayenne.testdo.mt.MtTooneDep" clientClassName="org.apache.cayenne.testdo.mt.ClientMtTooneDep" dbEntityName="MT_TOONE_DEP">
+	</obj-entity>
+	<obj-entity name="MtTooneMaster" className="org.apache.cayenne.testdo.mt.MtTooneMaster" clientClassName="org.apache.cayenne.testdo.mt.ClientMtTooneMaster" dbEntityName="MT_TOONE_MASTER">
+	</obj-entity>
 	<db-relationship name="cascade" source="MT_DELETE_CASCADE" target="MT_DELETE_RULE" toMany="false">
 		<db-attribute-pair source="DELETE_RULE_ID" target="DELETE_RULE_ID"/>
 	</db-relationship>
@@ -170,6 +180,12 @@
 	<db-relationship name="joins" source="MT_TABLE5" target="MT_JOIN45" toMany="true">
 		<db-attribute-pair source="ID" target="TABLE5_ID"/>
 	</db-relationship>
+	<db-relationship name="toMaster" source="MT_TOONE_DEP" target="MT_TOONE_MASTER" toMany="false">
+		<db-attribute-pair source="ID" target="ID"/>
+	</db-relationship>
+	<db-relationship name="toDependent" source="MT_TOONE_MASTER" target="MT_TOONE_DEP" toDependentPK="true" toMany="false">
+		<db-attribute-pair source="ID" target="ID"/>
+	</db-relationship>
 	<obj-relationship name="cascade" source="MtDeleteCascade" target="MtDeleteRule" deleteRule="Cascade" db-relationship-path="cascade"/>
 	<obj-relationship name="deny" source="MtDeleteDeny" target="MtDeleteRule" deleteRule="Deny" db-relationship-path="deny"/>
 	<obj-relationship name="nullify" source="MtDeleteNullify" target="MtDeleteRule" deleteRule="Nullify" db-relationship-path="nullify"/>
@@ -186,6 +202,8 @@
 	<obj-relationship name="table2Array" source="MtTable3" target="MtTable2" deleteRule="Deny" db-relationship-path="table2Array"/>
 	<obj-relationship name="table5s" source="MtTable4" target="MtTable5" db-relationship-path="joins.toTable5"/>
 	<obj-relationship name="table4s" source="MtTable5" target="MtTable4" db-relationship-path="joins.toTable4"/>
+	<obj-relationship name="toMaster" source="MtTooneDep" target="MtTooneMaster" deleteRule="Nullify" db-relationship-path="toMaster"/>
+	<obj-relationship name="toDependent" source="MtTooneMaster" target="MtTooneDep" deleteRule="Cascade" db-relationship-path="toDependent"/>
 	<query name="AllMtTable1" factory="org.apache.cayenne.map.SelectQueryBuilder" root="obj-entity" root-name="MtTable1">
 	</query>
 	<query name="MtQueryWithLocalCache" factory="org.apache.cayenne.map.SelectQueryBuilder" root="obj-entity" root-name="MtTable1">