blob: 906e11575f5253b5f80b9441113d0232a145bfe5 [file] [log] [blame]
/*****************************************************************
* 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.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.reflect.AttributeProperty;
import org.apache.cayenne.reflect.ClassDescriptor;
import org.apache.cayenne.reflect.PropertyVisitor;
import org.apache.cayenne.reflect.ToManyMapProperty;
import org.apache.cayenne.reflect.ToManyProperty;
import org.apache.cayenne.reflect.ToOneProperty;
/**
* An operation that merges changes from an object graph, whose objects are registered in
* some ObjectContext, to peer objects in an ObjectConext that is a child of that context.
* The merge terminates at hollow nodes in the parent context to avoid tripping over
* unresolved relationships.
*
* @since 1.2
*/
public class DeepMergeOperation {
private final EntityResolver entityResolver;
private final ShallowMergeOperation shallowMergeOperation;
public DeepMergeOperation(ObjectContext context) {
this.entityResolver = context.getEntityResolver();
this.shallowMergeOperation = new ShallowMergeOperation(context);
}
/**
* @deprecated since 3.1 - unused as the object is now stateless
*/
@Deprecated
public void reset() {
// noop
}
/**
* @deprecated since 3.1 use {@link #merge(Persistent)}.
*/
@Deprecated
public Object merge(Object object, ClassDescriptor descriptor) {
if (!(object instanceof Persistent)) {
throw new CayenneRuntimeException("Expected Persistent, got: " + object);
}
return merge((Persistent) object);
}
public <T extends Persistent> T merge(T peerInParentContext) {
ClassDescriptor descriptor = entityResolver
.getClassDescriptor(peerInParentContext.getObjectId().getEntityName());
return merge(peerInParentContext, descriptor, new HashMap<ObjectId, Persistent>());
}
private <T extends Persistent> T merge(
final T peerInParentContext,
ClassDescriptor descriptor,
final Map<ObjectId, Persistent> seen) {
ObjectId id = peerInParentContext.getObjectId();
// sanity check
if (id == null) {
throw new CayenneRuntimeException("Server returned an object without an id: "
+ peerInParentContext);
}
Persistent seenTarget = seen.get(id);
if (seenTarget != null) {
return (T) seenTarget;
}
final T target = shallowMergeOperation.merge(peerInParentContext);
seen.put(id, target);
descriptor = descriptor.getSubclassDescriptor(peerInParentContext.getClass());
descriptor.visitProperties(new PropertyVisitor() {
public boolean visitToOne(ToOneProperty property) {
if (!property.isFault(peerInParentContext)) {
Persistent destinationSource = (Persistent) property
.readProperty(peerInParentContext);
Object destinationTarget = destinationSource != null ? merge(
destinationSource,
property.getTargetDescriptor(),
seen) : null;
Object oldTarget = property.isFault(target) ? null : property
.readProperty(target);
property.writePropertyDirectly(target, oldTarget, destinationTarget);
}
return true;
}
public boolean visitToMany(ToManyProperty property) {
if (!property.isFault(peerInParentContext)) {
Object value = property.readProperty(peerInParentContext);
Object targetValue;
if (property instanceof ToManyMapProperty) {
Map<?, ?> map = (Map) value;
Map targetMap = new HashMap();
for (Entry entry : map.entrySet()) {
Object destinationSource = entry.getValue();
Object destinationTarget = destinationSource != null ? merge(
(Persistent) destinationSource,
property.getTargetDescriptor(),
seen) : null;
targetMap.put(entry.getKey(), destinationTarget);
}
targetValue = targetMap;
}
else {
Collection collection = (Collection) value;
Collection targetCollection = new ArrayList(collection.size());
for (Object destinationSource : collection) {
Object destinationTarget = destinationSource != null ? merge(
(Persistent) destinationSource,
property.getTargetDescriptor(),
seen) : null;
targetCollection.add(destinationTarget);
}
targetValue = targetCollection;
}
property.writePropertyDirectly(target, null, targetValue);
}
return true;
}
public boolean visitAttribute(AttributeProperty property) {
return true;
}
});
return target;
}
}