| /***************************************************************** |
| * 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; |
| } |
| } |