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/09/17 16:13:20 UTC

svn commit: r816205 - in /cayenne/sandbox/cayenne-serialization/src: main/java/org/apache/cayenne/serialization/ main/java/org/apache/cayenne/serialization/xstream/ test/java/org/apache/cayenne/serialization/ test/java/org/apache/cayenne/serialization/...

Author: aadamchik
Date: Thu Sep 17 14:13:19 2009
New Revision: 816205

URL: http://svn.apache.org/viewvc?rev=816205&view=rev
Log:
prototyping (de)serializer based on XStream

callbacks support

Added:
    cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/DeserializationCallback.java
      - copied, changed from r816198, cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/SerializerStack.java
    cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/SerializationCallback.java
      - copied, changed from r816198, cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/SerializerStack.java
    cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentDeserializeConverter.java
      - copied, changed from r816198, cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentCloneUnmarshalConverter.java
    cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentSerializeConverter.java
      - copied, changed from r816198, cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentMarshalConverter.java
Removed:
    cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentCloneUnmarshalConverter.java
    cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentMarshalConverter.java
Modified:
    cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/BaseSerializer.java
    cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/Subgraph.java
    cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/SubgraphNode.java
    cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/DeserializerStack.java
    cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/SerializerStack.java
    cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/XStreamDeserializer.java
    cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/XStreamSerializer.java
    cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/SubgraphTest.java
    cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/xstream/XStreamDeserializerTest.java
    cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/xstream/XStreamSerializerTest.java

Modified: cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/BaseSerializer.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/BaseSerializer.java?rev=816205&r1=816204&r2=816205&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/BaseSerializer.java (original)
+++ cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/BaseSerializer.java Thu Sep 17 14:13:19 2009
@@ -1,3 +1,21 @@
+/*****************************************************************
+ *   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.serialization;
 
 import java.io.OutputStream;

Copied: cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/DeserializationCallback.java (from r816198, cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/SerializerStack.java)
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/DeserializationCallback.java?p2=cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/DeserializationCallback.java&p1=cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/SerializerStack.java&r1=816198&r2=816205&rev=816205&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/SerializerStack.java (original)
+++ cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/DeserializationCallback.java Thu Sep 17 14:13:19 2009
@@ -16,34 +16,9 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
-package org.apache.cayenne.serialization.xstream;
+package org.apache.cayenne.serialization;
 
-import org.apache.cayenne.serialization.Subgraph;
-import org.apache.cayenne.serialization.SubgraphNode;
+public interface DeserializationCallback {
 
-import com.thoughtworks.xstream.core.util.FastStack;
-
-/**
- * A single-threaded stack for {@link Subgraph} traversal.
- */
-class SerializerStack {
-
-	private FastStack stack;
-
-	public SerializerStack(SubgraphNode graphDescriptorRoot) {
-		stack = new FastStack(graphDescriptorRoot.getMaxDepth());
-		stack.push(graphDescriptorRoot);
-	}
-
-	public void pushNode(SubgraphNode node) {
-		stack.push(node);
-	}
-
-	public void popNode() {
-		stack.popSilently();
-	}
-
-	public SubgraphNode peekNode() {
-		return (SubgraphNode) stack.peek();
-	}
+	void postDeserialize(SubgraphNode node, Object object);
 }

Copied: cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/SerializationCallback.java (from r816198, cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/SerializerStack.java)
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/SerializationCallback.java?p2=cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/SerializationCallback.java&p1=cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/SerializerStack.java&r1=816198&r2=816205&rev=816205&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/SerializerStack.java (original)
+++ cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/SerializationCallback.java Thu Sep 17 14:13:19 2009
@@ -16,34 +16,11 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
-package org.apache.cayenne.serialization.xstream;
+package org.apache.cayenne.serialization;
 
-import org.apache.cayenne.serialization.Subgraph;
-import org.apache.cayenne.serialization.SubgraphNode;
+import org.apache.cayenne.query.Query;
 
-import com.thoughtworks.xstream.core.util.FastStack;
+public interface SerializationCallback {
 
-/**
- * A single-threaded stack for {@link Subgraph} traversal.
- */
-class SerializerStack {
-
-	private FastStack stack;
-
-	public SerializerStack(SubgraphNode graphDescriptorRoot) {
-		stack = new FastStack(graphDescriptorRoot.getMaxDepth());
-		stack.push(graphDescriptorRoot);
-	}
-
-	public void pushNode(SubgraphNode node) {
-		stack.push(node);
-	}
-
-	public void popNode() {
-		stack.popSilently();
-	}
-
-	public SubgraphNode peekNode() {
-		return (SubgraphNode) stack.peek();
-	}
+	Query relationshipQuery(SubgraphNode node, Object sourceObject);
 }

Modified: cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/Subgraph.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/Subgraph.java?rev=816205&r1=816204&r2=816205&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/Subgraph.java (original)
+++ cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/Subgraph.java Thu Sep 17 14:13:19 2009
@@ -69,7 +69,7 @@
 			String token = tokens.nextToken();
 
 			if (tokens.hasMoreTokens()) {
-				node = node.getOrAppendChild(token);
+				node = node.getChild(token, false);
 			} else {
 				// last token is the attribute name
 				node.excludeAttribute(token);
@@ -86,7 +86,7 @@
 
 		SubgraphNode node = rootNode;
 		while (tokens.hasMoreTokens()) {
-			node = node.getOrAppendChild(tokens.nextToken());
+			node = node.getChild(tokens.nextToken(), true);
 		}
 	}
 
@@ -101,8 +101,36 @@
 		SubgraphNode node = rootNode;
 		while (tokens.hasMoreTokens()) {
 			String token = tokens.nextToken();
-			node = node.getOrAppendChild(token);
+			node = node.getChild(token, true);
 			node.setSerializedByReference(!tokens.hasMoreTokens());
 		}
 	}
+
+	public void addDeserializationCallback(String path,
+			DeserializationCallback callback) {
+
+		StringTokenizer tokens = new StringTokenizer(path, ".");
+
+		SubgraphNode node = rootNode;
+		while (tokens.hasMoreTokens()) {
+			String token = tokens.nextToken();
+			node = node.getChild(token, false);
+		}
+
+		node.addDeserializationCallback(callback);
+	}
+
+	public void addSerializationCallback(String path,
+			SerializationCallback callback) {
+
+		StringTokenizer tokens = new StringTokenizer(path, ".");
+
+		SubgraphNode node = rootNode;
+		while (tokens.hasMoreTokens()) {
+			String token = tokens.nextToken();
+			node = node.getChild(token, false);
+		}
+
+		node.addSerializationCallback(callback);
+	}
 }

Modified: cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/SubgraphNode.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/SubgraphNode.java?rev=816205&r1=816204&r2=816205&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/SubgraphNode.java (original)
+++ cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/SubgraphNode.java Thu Sep 17 14:13:19 2009
@@ -44,11 +44,17 @@
 	private Map<String, SubgraphNode> children;
 	private boolean serializedByReference;
 	private List<AttributeProperty> attributeProperties;
+	private List<SerializationCallback> serializationCallbacks;
+	private List<DeserializationCallback> deserializationCallbacks;
 
 	/**
 	 * Creates a root subgraph node.
 	 */
 	SubgraphNode(ClassDescriptor classDescriptor) {
+
+		this.serializationCallbacks = new ArrayList<SerializationCallback>(3);
+		this.deserializationCallbacks = new ArrayList<DeserializationCallback>(
+				3);
 		this.classDescriptor = classDescriptor;
 
 		// cache properties
@@ -74,21 +80,20 @@
 		this(incomingProperty.getTargetDescriptor());
 		this.incomingProperty = incomingProperty;
 	}
-	
+
 	public int getMaxDepth() {
-		if(children != null) {
+		if (children != null) {
 			int depth = 0;
-			for(SubgraphNode child : children.values()) {
+			for (SubgraphNode child : children.values()) {
 				int childDepth = child.getMaxDepth();
-				
-				if(depth < childDepth) {
+
+				if (depth < childDepth) {
 					depth = childDepth;
 				}
 			}
-			
+
 			return depth + 1;
-		}
-		else {
+		} else {
 			return 1;
 		}
 	}
@@ -104,38 +109,51 @@
 	void setSerializedByReference(boolean reference) {
 		this.serializedByReference = reference;
 	}
-	
+
 	void excludeAttribute(String attributeName) {
 		Iterator<AttributeProperty> it = attributeProperties.iterator();
-		while(it.hasNext()) {
+		while (it.hasNext()) {
 			AttributeProperty property = it.next();
-			if(property.getName().equals(attributeName)) {
+			if (property.getName().equals(attributeName)) {
 				it.remove();
-				break;
+				return;
 			}
 		}
+
+		throw new IllegalArgumentException(
+				"Attribute is either unmapped or was exlcuded before: "
+						+ attributeName);
+	}
+
+	public SubgraphNode getChild(String name) {
+		return children != null ? children.get(name) : null;
 	}
 
-	SubgraphNode getOrAppendChild(String path) {
+	SubgraphNode getChild(String name, boolean create) {
 
 		SubgraphNode child = null;
 		if (children != null) {
-			child = children.get(path);
+			child = children.get(name);
 		} else {
 			children = new LinkedHashMap<String, SubgraphNode>();
 		}
 
 		if (child == null) {
 
-			Property relationship = classDescriptor.getProperty(path);
+			if (!create) {
+				throw new IllegalArgumentException("Invalid child path: "
+						+ name);
+			}
+
+			Property relationship = classDescriptor.getProperty(name);
 
 			if (relationship == null) {
-				throw new IllegalArgumentException("Path '" + path
+				throw new IllegalArgumentException("Path '" + name
 						+ "' does not denote a mapped class property");
 			}
 
 			if (!(relationship instanceof ArcProperty)) {
-				throw new IllegalArgumentException("Path '" + path
+				throw new IllegalArgumentException("Path '" + name
 						+ "' does not denote a mapped relationship property. "
 						+ "Is this an attribute?");
 			}
@@ -143,12 +161,20 @@
 			ArcProperty arc = (ArcProperty) relationship;
 
 			child = new SubgraphNode(arc);
-			children.put(path, child);
+			children.put(name, child);
 		}
 
 		return child;
 	}
 
+	void addSerializationCallback(SerializationCallback callback) {
+		serializationCallbacks.add(callback);
+	}
+
+	void addDeserializationCallback(DeserializationCallback callback) {
+		deserializationCallbacks.add(callback);
+	}
+
 	public ClassDescriptor getClassDescriptor() {
 		return classDescriptor;
 	}
@@ -169,4 +195,12 @@
 		return children != null ? children.values() : Collections
 				.<SubgraphNode> emptyList();
 	}
+
+	public List<SerializationCallback> getSerializationCallbacks() {
+		return serializationCallbacks;
+	}
+
+	public List<DeserializationCallback> getDeserializationCallbacks() {
+		return deserializationCallbacks;
+	}
 }
\ No newline at end of file

Modified: cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/DeserializerStack.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/DeserializerStack.java?rev=816205&r1=816204&r2=816205&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/DeserializerStack.java (original)
+++ cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/DeserializerStack.java Thu Sep 17 14:13:19 2009
@@ -18,49 +18,76 @@
  ****************************************************************/
 package org.apache.cayenne.serialization.xstream;
 
+import org.apache.cayenne.PersistenceState;
+import org.apache.cayenne.Persistent;
 import org.apache.cayenne.reflect.ArcProperty;
 import org.apache.cayenne.reflect.ToManyProperty;
+import org.apache.cayenne.serialization.DeserializationCallback;
+import org.apache.cayenne.serialization.SubgraphNode;
 
 import com.thoughtworks.xstream.core.util.FastStack;
 
 class DeserializerStack {
 
-	private FastStack stack;
+	private FastStack subgraphStack;
+	private FastStack objectStack;
 	private int counter;
 
-	DeserializerStack() {
-		stack = new FastStack(10);
+	DeserializerStack(SubgraphNode root) {
+		int maxDepth = root.getMaxDepth();
+		objectStack = new FastStack(maxDepth);
+		subgraphStack = new FastStack(maxDepth);
+		subgraphStack.push(root);
 	}
 
-	int incrementCounter() {
-		return ++counter;
-	}
+	void pushObject(Object object) {
 
-	void push(Object object) {
+		SubgraphNode node = (SubgraphNode) subgraphStack.peek();
 
 		// connect to parent
-		Object[] peek = (Object[]) stack.peek();
-		if (peek != null) {
+		ArcProperty incoming = node.getIncomingProperty();
+		if (incoming != null) {
+			Object peek = objectStack.peek();
 
-			if (peek[1] instanceof ToManyProperty) {
-				((ToManyProperty) peek[1]).addTarget(peek[0], object, true);
+			if (incoming instanceof ToManyProperty) {
+				((ToManyProperty) incoming).addTarget(peek, object, true);
 			} else {
-				((ArcProperty) peek[1]).writeProperty(peek[0], null, object);
+				incoming.writeProperty(peek, null, object);
 			}
 		}
 
-		Object[] entry = new Object[2];
-		entry[0] = object;
-		stack.push(entry);
+		// apply callbacks
+		for (DeserializationCallback callback : node
+				.getDeserializationCallbacks()) {
+			callback.postDeserialize(node, object);
+		}
+
+		objectStack.push(object);
 	}
 
-	void pop() {
-		stack.popSilently();
+	int popObject() {
+		Persistent object = (Persistent) objectStack.pop();
+		if (object != null
+				&& object.getPersistenceState() == PersistenceState.NEW) {
+			counter++;
+		}
+		
+		return counter;
 	}
 
-	void startRelationship(ArcProperty property) {
-		Object[] peek = (Object[]) stack.peek();
-		peek[1] = property;
+	boolean pushPath(String name) {
+
+		SubgraphNode child = ((SubgraphNode) subgraphStack.peek())
+				.getChild(name);
+		if (child == null) {
+			return false;
+		}
+
+		subgraphStack.push(child);
+		return true;
 	}
 
+	void popPath() {
+		subgraphStack.popSilently();
+	}
 }

Copied: cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentDeserializeConverter.java (from r816198, cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentCloneUnmarshalConverter.java)
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentDeserializeConverter.java?p2=cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentDeserializeConverter.java&p1=cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentCloneUnmarshalConverter.java&r1=816198&r2=816205&rev=816205&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentCloneUnmarshalConverter.java (original)
+++ cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentDeserializeConverter.java Thu Sep 17 14:13:19 2009
@@ -27,6 +27,7 @@
 import org.apache.cayenne.reflect.ClassDescriptor;
 import org.apache.cayenne.reflect.Property;
 import org.apache.cayenne.reflect.ToManyProperty;
+import org.apache.cayenne.serialization.SubgraphNode;
 
 import com.thoughtworks.xstream.converters.Converter;
 import com.thoughtworks.xstream.converters.MarshallingContext;
@@ -34,22 +35,20 @@
 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
 
-/**
- * "Clone" in {@link PersistentCloneUnmarshalConverter} means that ids of the
- * Persistent objects will be ignored, and new objects will be created.
- */
-class PersistentCloneUnmarshalConverter implements Converter {
+class PersistentDeserializeConverter implements Converter {
 
-	private static final String TRAVERSAL_CONTEXT_KEY = PersistentCloneUnmarshalConverter.class
+	private static final String STACK_KEY = PersistentDeserializeConverter.class
 			.getName()
-			+ "_TRAVERSAL_CONTEXT";
+			+ "_STACK";
 
 	private ObjectContext objectContext;
 	private int commitCountThreshold;
+	private SubgraphNode rootNode;
 
-	public PersistentCloneUnmarshalConverter(ObjectContext objectContext,
-			int commitCountThreshold) {
+	public PersistentDeserializeConverter(SubgraphNode rootNode,
+			ObjectContext objectContext, int commitCountThreshold) {
 
+		this.rootNode = rootNode;
 		this.objectContext = objectContext;
 		this.commitCountThreshold = commitCountThreshold;
 	}
@@ -83,9 +82,9 @@
 		// TODO: handle deleted objects that no longer exist...
 
 		if (object != null) {
-			DeserializerStack stack = getDeserialierCounter(context);
-			stack.push(object);
-			stack.pop();
+			DeserializerStack stack = getStack(context);
+			stack.pushObject(object);
+			stack.popObject();
 		}
 
 		return object;
@@ -98,12 +97,12 @@
 		ClassDescriptor descriptor = objectContext.getEntityResolver()
 				.getClassDescriptor(entityName);
 
-		DeserializerStack stack = getDeserialierCounter(context);
+		DeserializerStack stack = getStack(context);
 
 		Object object = descriptor.createObject();
 		objectContext.registerNewObject(object);
 
-		stack.push(object);
+		stack.pushObject(object);
 
 		while (reader.hasMoreChildren()) {
 			reader.moveDown();
@@ -113,27 +112,31 @@
 				deserializeAttribute(reader, context, object,
 						(AttributeProperty) property);
 			} else {
-				ArcProperty arc = (ArcProperty) property;
 
-				stack.startRelationship(arc);
+				if (stack.pushPath(property.getName())) {
 
-				if (arc.getRelationship().isToMany()) {
-					deserializeToManyRelationship(reader, context, object,
-							(ToManyProperty) arc);
-				} else {
-					deserializeToOneRelationship(reader, context, object, arc);
+					ArcProperty arc = (ArcProperty) property;
+					if (arc.getRelationship().isToMany()) {
+						deserializeToManyRelationship(reader, context, object,
+								(ToManyProperty) arc);
+					} else {
+						deserializeToOneRelationship(reader, context, object,
+								arc);
+					}
+
+					stack.popPath();
 				}
 			}
 
 			reader.moveUp();
 		}
 
-		if (commitCountThreshold > 0
-				&& stack.incrementCounter() % commitCountThreshold == 0) {
+		int count = stack.popObject();
+
+		if (commitCountThreshold > 0 && count % commitCountThreshold == 0) {
 			objectContext.commitChanges();
 		}
 
-		stack.pop();
 		return object;
 	}
 
@@ -152,9 +155,12 @@
 
 		Class<?> javaType = property.getTargetDescriptor().getObjectClass();
 
-		reader.moveDown();
-		context.convertAnother(parentObject, javaType);
-		reader.moveUp();
+		// check for children to handle optional to-one
+		if (reader.hasMoreChildren()) {
+			reader.moveDown();
+			context.convertAnother(parentObject, javaType);
+			reader.moveUp();
+		}
 	}
 
 	private void deserializeToManyRelationship(HierarchicalStreamReader reader,
@@ -170,15 +176,14 @@
 		}
 	}
 
-	private DeserializerStack getDeserialierCounter(UnmarshallingContext context) {
-		DeserializerStack deserializerCounter = (DeserializerStack) context
-				.get(TRAVERSAL_CONTEXT_KEY);
+	private DeserializerStack getStack(UnmarshallingContext context) {
+		DeserializerStack stack = (DeserializerStack) context.get(STACK_KEY);
 
-		if (deserializerCounter == null) {
-			deserializerCounter = new DeserializerStack();
-			context.put(TRAVERSAL_CONTEXT_KEY, deserializerCounter);
+		if (stack == null) {
+			stack = new DeserializerStack(rootNode);
+			context.put(STACK_KEY, stack);
 		}
 
-		return deserializerCounter;
+		return stack;
 	}
 }

Copied: cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentSerializeConverter.java (from r816198, cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentMarshalConverter.java)
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentSerializeConverter.java?p2=cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentSerializeConverter.java&p1=cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentMarshalConverter.java&r1=816198&r2=816205&rev=816205&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentMarshalConverter.java (original)
+++ cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/PersistentSerializeConverter.java Thu Sep 17 14:13:19 2009
@@ -21,14 +21,18 @@
 import org.apache.cayenne.CayenneException;
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.DataObject;
+import org.apache.cayenne.DataObjectUtils;
 import org.apache.cayenne.DataRow;
+import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.Persistent;
 import org.apache.cayenne.access.DataContext;
 import org.apache.cayenne.access.ResultIterator;
+import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.RelationshipQuery;
 import org.apache.cayenne.reflect.ArcProperty;
 import org.apache.cayenne.reflect.AttributeProperty;
 import org.apache.cayenne.reflect.Property;
+import org.apache.cayenne.serialization.SerializationCallback;
 import org.apache.cayenne.serialization.Subgraph;
 import org.apache.cayenne.serialization.SubgraphNode;
 
@@ -38,16 +42,16 @@
 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
 
-class PersistentMarshalConverter<T> implements Converter {
+class PersistentSerializeConverter implements Converter {
 
-	private static final String TRAVERSAL_CONTEXT_KEY = PersistentMarshalConverter.class
+	private static final String STACK_KEY = PersistentSerializeConverter.class
 			.getName()
-			+ "_TRAVERSAL_CONTEXT";
+			+ "_STACK";
 
-	private Subgraph<T> subgraph;
+	private Subgraph<?> subgraph;
 	private int statementFetchSize;
 
-	public PersistentMarshalConverter(Subgraph<T> subgraph,
+	public PersistentSerializeConverter(Subgraph<?> subgraph,
 			int statementFetchSize) {
 		this.subgraph = subgraph;
 		this.statementFetchSize = statementFetchSize;
@@ -56,7 +60,7 @@
 	public void marshal(Object object, HierarchicalStreamWriter writer,
 			MarshallingContext context) {
 
-		SerializerStack serializerContext = getSerializerStack(context);
+		SerializerStack serializerContext = getStack(context);
 
 		SubgraphNode node = serializerContext.peekNode();
 
@@ -76,13 +80,24 @@
 
 			serializerContext.pushNode(child);
 
+			Query query = null;
+			for (SerializationCallback callback : child
+					.getSerializationCallbacks()) {
+				query = callback.relationshipQuery(child, object);
+				if (query != null) {
+					break;
+				}
+			}
+
 			ArcProperty incoming = child.getIncomingProperty();
 			boolean byReference = child.isSerializedByReference();
 
 			if (incoming.getRelationship().isToMany()) {
-				marshalToMany(object, incoming, writer, context, byReference);
+				marshalToMany(object, incoming, writer, context, byReference,
+						query);
 			} else {
-				marshalToOne(object, incoming, writer, context, byReference);
+				marshalToOne(object, incoming, writer, context, byReference,
+						query);
 			}
 
 			serializerContext.popNode();
@@ -107,31 +122,47 @@
 
 	private void marshalToOne(Object object, ArcProperty arc,
 			HierarchicalStreamWriter writer, MarshallingContext context,
-			boolean byReference) {
+			boolean byReference, Query query) {
 
-		writer.startNode(arc.getName());
+		Persistent value = null;
 
-		Persistent value = (Persistent) arc.readProperty(object);
+		if (query != null) {
+			ObjectContext objectContext = ((Persistent) object)
+					.getObjectContext();
+			value = (Persistent) DataObjectUtils.objectForQuery(objectContext,
+					query);
+		} else {
+			value = (Persistent) arc.readProperty(object);
+		}
+
+		// note that we don't even write an empty tag for NULL to-one. This may
+		// be a problem only if we allow non-reference nodes to be attached to
+		// reference-serialized nodes
 		if (value != null) {
+			writer.startNode(arc.getName());
 			context.convertAnother(byReference ? value.getObjectId() : value);
+			writer.endNode();
 		}
 
-		writer.endNode();
 	}
 
 	private void marshalToMany(Object object, ArcProperty arc,
 			HierarchicalStreamWriter writer, MarshallingContext context,
-			boolean byReference) {
+			boolean byReference, Query query) {
 
 		writer.startNode(arc.getName());
 
 		Persistent persistent = (Persistent) object;
-		RelationshipQuery query = new RelationshipQuery(persistent
-				.getObjectId(), arc.getName());
 
-		// "fetchSize" is absolutely critical to avoid storing the entire
-		// ResultSet in memory.
-		query.setStatementFetchSize(statementFetchSize);
+		if (query == null) {
+			RelationshipQuery relationshipQuery = new RelationshipQuery(
+					persistent.getObjectId(), arc.getName());
+
+			// "fetchSize" is absolutely critical to avoid storing the entire
+			// ResultSet in memory.
+			relationshipQuery.setStatementFetchSize(statementFetchSize);
+			query = relationshipQuery;
+		}
 
 		DataContext dataContext = (DataContext) persistent.getObjectContext();
 
@@ -162,16 +193,15 @@
 		writer.endNode();
 	}
 
-	private SerializerStack getSerializerStack(MarshallingContext context) {
-		SerializerStack travsersalContext = (SerializerStack) context
-				.get(TRAVERSAL_CONTEXT_KEY);
+	private SerializerStack getStack(MarshallingContext context) {
+		SerializerStack stack = (SerializerStack) context.get(STACK_KEY);
 
-		if (travsersalContext == null) {
-			travsersalContext = new SerializerStack(subgraph.getRootNode());
-			context.put(TRAVERSAL_CONTEXT_KEY, travsersalContext);
+		if (stack == null) {
+			stack = new SerializerStack(subgraph.getRootNode());
+			context.put(STACK_KEY, stack);
 		}
 
-		return travsersalContext;
+		return stack;
 	}
 
 	public Object unmarshal(HierarchicalStreamReader reader,

Modified: cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/SerializerStack.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/SerializerStack.java?rev=816205&r1=816204&r2=816205&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/SerializerStack.java (original)
+++ cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/SerializerStack.java Thu Sep 17 14:13:19 2009
@@ -30,9 +30,9 @@
 
 	private FastStack stack;
 
-	public SerializerStack(SubgraphNode graphDescriptorRoot) {
-		stack = new FastStack(graphDescriptorRoot.getMaxDepth());
-		stack.push(graphDescriptorRoot);
+	public SerializerStack(SubgraphNode root) {
+		stack = new FastStack(root.getMaxDepth());
+		stack.push(root);
 	}
 
 	public void pushNode(SubgraphNode node) {

Modified: cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/XStreamDeserializer.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/XStreamDeserializer.java?rev=816205&r1=816204&r2=816205&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/XStreamDeserializer.java (original)
+++ cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/XStreamDeserializer.java Thu Sep 17 14:13:19 2009
@@ -42,8 +42,8 @@
 		int commitCountThreshold = isCommitting() ? getCommitCountThreshold()
 				: 0;
 
-		xstream.registerConverter(new PersistentCloneUnmarshalConverter(
-				context, commitCountThreshold));
+		xstream.registerConverter(new PersistentDeserializeConverter(
+				subgraph.getRootNode(), context, commitCountThreshold));
 		xstream.registerConverter(new ObjectIdConverter(context
 				.getEntityResolver()));
 

Modified: cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/XStreamSerializer.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/XStreamSerializer.java?rev=816205&r1=816204&r2=816205&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/XStreamSerializer.java (original)
+++ cayenne/sandbox/cayenne-serialization/src/main/java/org/apache/cayenne/serialization/xstream/XStreamSerializer.java Thu Sep 17 14:13:19 2009
@@ -40,7 +40,7 @@
 		XStream xstream = createXStream(subgraph.getRootNode()
 				.getClassDescriptor());
 
-		xstream.registerConverter(new PersistentMarshalConverter<T>(subgraph,
+		xstream.registerConverter(new PersistentSerializeConverter(subgraph,
 				statementFetchSize));
 		xstream.registerConverter(new ObjectIdConverter(null));
 

Modified: cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/SubgraphTest.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/SubgraphTest.java?rev=816205&r1=816204&r2=816205&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/SubgraphTest.java (original)
+++ cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/SubgraphTest.java Thu Sep 17 14:13:19 2009
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.serialization;
 
+import org.apache.cayenne.query.Query;
 import org.apache.cayenne.serialization.persistent.Table1;
 import org.apache.cayenne.serialization.persistent.Table2;
 import org.apache.cayenne.serialization.unit.SerializationCase;
@@ -32,6 +33,53 @@
 		assertEquals(0, subgraph.getRootNode().getChildren().size());
 	}
 
+	public void testAddDerserializationCallback() {
+		Subgraph<Table2> subgraph = new Subgraph<Table2>(Table2.class,
+				newContext().getEntityResolver());
+		subgraph.addSerializeByReferencePath(Table2.TABLE1_PROPERTY);
+
+		SubgraphNode node = subgraph.getRootNode().getChild(
+				Table2.TABLE1_PROPERTY, false);
+
+		assertEquals(0, node.getSerializationCallbacks().size());
+		assertEquals(0, node.getDeserializationCallbacks().size());
+
+		subgraph.addDeserializationCallback(Table2.TABLE1_PROPERTY,
+				new DeserializationCallback() {
+					public void postDeserialize(SubgraphNode node, Object object) {
+						// noop
+					}
+				});
+
+		assertEquals(0, node.getSerializationCallbacks().size());
+		assertEquals(1, node.getDeserializationCallbacks().size());
+	}
+
+	public void testAddSerializationCallback() {
+		Subgraph<Table2> subgraph = new Subgraph<Table2>(Table2.class,
+				newContext().getEntityResolver());
+		subgraph.addSerializeByReferencePath(Table2.TABLE1_PROPERTY);
+
+		SubgraphNode node = subgraph.getRootNode().getChild(
+				Table2.TABLE1_PROPERTY, false);
+
+		assertEquals(0, node.getSerializationCallbacks().size());
+		assertEquals(0, node.getDeserializationCallbacks().size());
+
+		subgraph.addSerializationCallback(Table2.TABLE1_PROPERTY,
+				new SerializationCallback() {
+
+					public Query relationshipQuery(SubgraphNode node,
+							Object sourceObject) {
+						// TODO Auto-generated method stub
+						return null;
+					}
+				});
+
+		assertEquals(1, node.getSerializationCallbacks().size());
+		assertEquals(0, node.getDeserializationCallbacks().size());
+	}
+
 	public void testAddSerializeByReferencePathToOne() {
 		Subgraph<Table2> subgraph = new Subgraph<Table2>(Table2.class,
 				newContext().getEntityResolver());

Modified: cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/xstream/XStreamDeserializerTest.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/xstream/XStreamDeserializerTest.java?rev=816205&r1=816204&r2=816205&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/xstream/XStreamDeserializerTest.java (original)
+++ cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/xstream/XStreamDeserializerTest.java Thu Sep 17 14:13:19 2009
@@ -27,7 +27,9 @@
 import org.apache.cayenne.DataObjectUtils;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.PersistenceState;
+import org.apache.cayenne.serialization.DeserializationCallback;
 import org.apache.cayenne.serialization.Subgraph;
+import org.apache.cayenne.serialization.SubgraphNode;
 import org.apache.cayenne.serialization.persistent.Table1;
 import org.apache.cayenne.serialization.persistent.Table2;
 import org.apache.cayenne.serialization.unit.SerializationCase;
@@ -182,4 +184,88 @@
 		assertTrue(names.contains("t22"));
 	}
 
+	public void testCallbackByReference() throws IOException {
+
+		ObjectContext context = newContext();
+		Table1 t11 = context.newObject(Table1.class);
+		t11.setName("t11");
+
+		Table2 t21 = context.newObject(Table2.class);
+		t21.setName("t21");
+		t21.setTable1(t11);
+
+		context.commitChanges();
+
+		final boolean[] callbackInvoked = new boolean[1];
+
+		Subgraph<Table2> subgraph = new Subgraph<Table2>(Table2.class, context
+				.getEntityResolver());
+		subgraph.addSerializeByReferencePath(Table2.TABLE1_PROPERTY);
+		subgraph.addDeserializationCallback(Table2.TABLE1_PROPERTY,
+				new DeserializationCallback() {
+					public void postDeserialize(SubgraphNode node, Object object) {
+						assertNotNull(node);
+						assertEquals(Table2.TABLE1_PROPERTY, node
+								.getIncomingProperty().getName());
+						assertNotNull(object);
+						assertTrue(object instanceof Table1);
+
+						callbackInvoked[0] = true;
+					}
+				});
+
+		XStreamDeserializer deserializer = new XStreamDeserializer();
+
+		int id = DataObjectUtils.intPKForObject(t11);
+		String xml = "<Table2><name>t21</name><table1><Table1 ref=\"true\"><PK>"
+				+ id + "</PK></Table1></table1></Table2>";
+
+		InputStream in = new ByteArrayInputStream(xml.getBytes());
+		try {
+			deserializer.deserialize(context, subgraph, in);
+		} finally {
+			in.close();
+		}
+
+		assertTrue(callbackInvoked[0]);
+	}
+
+	public void testCallbackByValue() throws IOException {
+
+		ObjectContext context = newContext();
+
+		final boolean[] callbackInvoked = new boolean[1];
+
+		Subgraph<Table1> subgraph = new Subgraph<Table1>(Table1.class, context
+				.getEntityResolver());
+		subgraph.addSerializeByValuePath(Table1.TABLE2S_PROPERTY);
+		subgraph.addDeserializationCallback(Table1.TABLE2S_PROPERTY,
+				new DeserializationCallback() {
+					public void postDeserialize(SubgraphNode node, Object object) {
+						assertNotNull(node);
+						assertEquals(Table1.TABLE2S_PROPERTY, node
+								.getIncomingProperty().getName());
+						assertNotNull(object);
+						assertTrue(object instanceof Table2);
+
+						callbackInvoked[0] = true;
+					}
+				});
+
+		XStreamDeserializer deserializer = new XStreamDeserializer();
+
+		String xml = "<Table1><name>t11</name><table2s>"
+				+ "<Table2><name>t21</name></Table2>"
+				+ "<Table2><name>t22</name></Table2></table2s></Table1>";
+
+		InputStream in = new ByteArrayInputStream(xml.getBytes());
+		try {
+			deserializer.deserialize(context, subgraph, in);
+		} finally {
+			in.close();
+		}
+
+		assertTrue(callbackInvoked[0]);
+	}
+
 }

Modified: cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/xstream/XStreamSerializerTest.java
URL: http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/xstream/XStreamSerializerTest.java?rev=816205&r1=816204&r2=816205&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/xstream/XStreamSerializerTest.java (original)
+++ cayenne/sandbox/cayenne-serialization/src/test/java/org/apache/cayenne/serialization/xstream/XStreamSerializerTest.java Thu Sep 17 14:13:19 2009
@@ -18,13 +18,18 @@
  ****************************************************************/
 package org.apache.cayenne.serialization.xstream;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 
 import org.apache.cayenne.DataObjectUtils;
 import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.query.Query;
+import org.apache.cayenne.serialization.SerializationCallback;
 import org.apache.cayenne.serialization.Subgraph;
+import org.apache.cayenne.serialization.SubgraphNode;
 import org.apache.cayenne.serialization.persistent.Table1;
 import org.apache.cayenne.serialization.persistent.Table2;
 import org.apache.cayenne.serialization.unit.SerializationCase;
@@ -175,9 +180,9 @@
 
 		assertEquals("<Table1><name>t11</name></Table1>", Util.stringFromFile(
 				f1).trim());
-		
+
 		subgraph.excludeAttribute(Table1.NAME_PROPERTY);
-		
+
 		File f2 = tempFile(".xml");
 
 		FileOutputStream out2 = new FileOutputStream(f2);
@@ -190,8 +195,48 @@
 		assertTrue(f2.isFile());
 		assertTrue(f2.length() > 0);
 
-		assertEquals("<Table1/>", Util.stringFromFile(
-				f2).trim());
-		
+		assertEquals("<Table1/>", Util.stringFromFile(f2).trim());
+
 	}
+
+	public void testCallbackByValueToMany() throws IOException {
+
+		ObjectContext context = newContext();
+		Table1 t11 = context.newObject(Table1.class);
+		t11.setName("t11");
+
+		Table2 t21 = context.newObject(Table2.class);
+		t21.setName("t21");
+		t21.setTable1(t11);
+
+		context.commitChanges();
+
+		final boolean[] callbackInvoked = new boolean[1];
+
+		Subgraph<Table1> subgraph = new Subgraph<Table1>(Table1.class, context
+				.getEntityResolver());
+		subgraph.addSerializeByValuePath(Table1.TABLE2S_PROPERTY);
+		subgraph.addSerializationCallback(Table1.TABLE2S_PROPERTY,
+				new SerializationCallback() {
+					public Query relationshipQuery(SubgraphNode node,
+							Object sourceObject) {
+
+						callbackInvoked[0] = true;
+						return null;
+					}
+				});
+
+		XStreamSerializer serializer = new XStreamSerializer();
+		serializer.setCreatingCompactXML(true);
+
+		OutputStream out = new ByteArrayOutputStream();
+		try {
+			serializer.serialize(t11, subgraph, out);
+		} finally {
+			out.close();
+		}
+
+		assertTrue(callbackInvoked[0]);
+	}
+
 }