private void restoreNode( AbstractJcrNode sourceNode,
AbstractJcrNode targetNode,
DateTime checkinTime ) throws RepositoryException {
changedNodes.put(sourceNode, targetNode);
MutableCachedNode target = targetNode.mutable();
CachedNode source = sourceNode.node();
Set<CachedNode> versionedChildrenThatShouldNotBeRestored = new HashSet<CachedNode>();
// Try to match the existing nodes with nodes from the version to be restored
Map<NodeKey, CachedNode> presentInBoth = new HashMap<NodeKey, CachedNode>();
// Start with all target children in this set and pull them out as matches are found
List<NodeKey> inTargetOnly = asList(target.getChildReferences(cache));
// Start with no source children in this set, but add them in when no match is found
Map<CachedNode, CachedNode> inSourceOnly = new HashMap<CachedNode, CachedNode>();
// Map the source children to existing target children where possible
for (ChildReference sourceChild : source.getChildReferences(cache)) {
CachedNode child = cache.getNode(sourceChild);
Name primaryTypeName = name(child.getPrimaryType(cache));
CachedNode resolvedFrozenNode = resolveSourceNode(child, checkinTime, cache);
CachedNode match = findMatchFor(resolvedFrozenNode, cache);
if (match != null) {
if (JcrNtLexicon.VERSIONED_CHILD.equals(primaryTypeName)) {
// This is a versioned child ...
if (!removeExisting) {
throw new ItemExistsException(JcrI18n.itemAlreadyExistsWithUuid.text(match.getKey(),
session.workspace().getName(),
match.getPath(cache)));
}
// use match directly
versionedChildrenThatShouldNotBeRestored.add(match);
}
inTargetOnly.remove(match.getKey());
presentInBoth.put(child.getKey(), match);
} else {
inSourceOnly.put(child, resolvedFrozenNode);
}
}
// Remove all the extraneous children of the target node
for (NodeKey childKey : inTargetOnly) {
AbstractJcrNode child = session.node(childKey, null);
switch (child.getDefinition().getOnParentVersion()) {
case OnParentVersionAction.ABORT:
case OnParentVersionAction.VERSION:
case OnParentVersionAction.COPY:
// The next call *might* remove some children below "child" which are also present on the source, but
// higher in the hierarchy
child.doRemove();
// Otherwise we're going to reuse the existing node
break;
case OnParentVersionAction.COMPUTE:
// Technically, this should reinitialize the node per its defaults.
case OnParentVersionAction.INITIALIZE:
case OnParentVersionAction.IGNORE:
// Do nothing
}
}
LinkedList<ChildReference> reversedChildren = new LinkedList<ChildReference>();
for (ChildReference sourceChildRef : source.getChildReferences(cache)) {
reversedChildren.addFirst(sourceChildRef);
}
// Now walk through the source node children (in reversed order), inserting children as needed
// The order is reversed because SessionCache$NodeEditor supports orderBefore, but not orderAfter
NodeKey prevChildKey = null;
for (ChildReference sourceChildRef : reversedChildren) {
CachedNode sourceChild = cache.getNode(sourceChildRef);
CachedNode targetChild = presentInBoth.get(sourceChildRef.getKey());
CachedNode resolvedChild = null;
Name resolvedPrimaryTypeName = null;
AbstractJcrNode sourceChildNode = null;
AbstractJcrNode targetChildNode = null;
Property frozenPrimaryType = sourceChild.getProperty(JcrLexicon.FROZEN_PRIMARY_TYPE, cache);
Name sourceFrozenPrimaryType = frozenPrimaryType != null ? name(frozenPrimaryType.getFirstValue()) : null;
boolean isShared = ModeShapeLexicon.SHARE.equals(sourceFrozenPrimaryType);
boolean shouldRestore = !versionedChildrenThatShouldNotBeRestored.contains(targetChild);
boolean shouldRestoreMixinsAndUuid = false;
Path targetPath = target.getPath(cache);
boolean restoreTargetUnderSamePath = targetChild != null
&& targetChild.getPath(cache).getParent().isSameAs(targetPath);
if (targetChild != null) {
resolvedChild = resolveSourceNode(sourceChild, checkinTime, cache);
resolvedPrimaryTypeName = name(resolvedChild.getPrimaryType(cache));
sourceChildNode = session.node(resolvedChild, (Type)null);
targetChildNode = session.node(targetChild, (Type)null);
if (isShared && !restoreTargetUnderSamePath) {
// This is a shared node that already exists in the workspace ...
restoredSharedChild(target, sourceChild, targetChildNode);
continue;
}
}
if (!restoreTargetUnderSamePath) {
if (targetChild != null) {
if (!cache.isDestroyed(targetChild.getKey())) {
// the target child exists but is under a different path in the source than the target
// so we need to remove it from its parent in the target to avoid the case when later on, it might be
// destroyed
MutableCachedNode targetChildParent = cache.mutable(targetChild.getParentKey(cache));
targetChildParent.removeChild(cache, targetChild.getKey());
}
resolvedChild = resolveSourceNode(sourceChild, checkinTime, cache);
} else {
// Pull the resolved node
resolvedChild = inSourceOnly.get(sourceChild);
}
resolvedPrimaryTypeName = name(resolvedChild.getPrimaryType(cache));
sourceChildNode = session.node(resolvedChild, (Type)null);
shouldRestoreMixinsAndUuid = true;
Name primaryTypeName = null;
NodeKey desiredKey = null;
Name desiredName = null;
if (isShared && sourceChildNode != null) {
// This is a shared node that already exists in the workspace ...
AbstractJcrNode resolvedChildNode = session.node(resolvedChild, (Type)null);
restoredSharedChild(target, sourceChild, resolvedChildNode);
continue;
}
if (JcrNtLexicon.FROZEN_NODE.equals(resolvedPrimaryTypeName)) {
primaryTypeName = name(resolvedChild.getProperty(JcrLexicon.FROZEN_PRIMARY_TYPE, cache).getFirstValue());
Property idProp = resolvedChild.getProperty(JcrLexicon.FROZEN_UUID, cache);
String frozenUuid = string(idProp.getFirstValue());
desiredKey = target.getKey().withId(frozenUuid);
// the name should be that of the versioned child
desiredName = session.node(sourceChild, (Type)null).name();
} else {
primaryTypeName = resolvedChild.getPrimaryType(cache);
Property idProp = resolvedChild.getProperty(JcrLexicon.UUID, cache);
if (idProp == null || idProp.isEmpty()) {
desiredKey = target.getKey().withRandomId();
} else {
String uuid = string(idProp.getFirstValue());
desiredKey = target.getKey().withId(uuid);
}
assert sourceChildNode != null;
desiredName = sourceChildNode.name();
}
Property primaryType = propFactory.create(JcrLexicon.PRIMARY_TYPE, primaryTypeName);
targetChild = target.createChild(cache, desiredKey, desiredName, primaryType);
targetChildNode = session.node(targetChild, (Type)null);
assert shouldRestore;
}
if (shouldRestore) {
assert targetChild != null;
MutableCachedNode mutableTarget = targetChild instanceof MutableCachedNode ? (MutableCachedNode)targetChild : cache.mutable(targetChild.getKey());
// Have to do this first, as the properties below only exist for mix:versionable nodes
if (shouldRestoreMixinsAndUuid) {
if (JcrNtLexicon.FROZEN_NODE.equals(resolvedPrimaryTypeName)) {
// if we're dealing with a nt:versionedChild (and therefore the resolved node is a frozen node), we
// need the mixins from the frozen node