for (NodeKey removed : changedChildren.getRemovals()) {
CachedNode persistent = persistedCache.getNode(removed);
if (persistent != null) {
if (appended != null && appended.hasChild(persistent.getKey())) {
// the same node has been both removed and appended => reordered at the end
ChildReference appendedChildRef = node.getChildReferences(this).getChild(persistent.getKey());
newPath = pathFactory().create(sessionPaths.getPath(node), appendedChildRef.getSegment());
Path oldPath = workspacePaths.getPath(persistent);
changes.nodeReordered(persistent.getKey(), primaryType, mixinTypes, node.getKey(), newPath,
oldPath, null, queryable);
}
}
}
}
// Now change the children ...
translator.changeChildren(doc, changedChildren, appended);
// Generate events for renames, as this is only captured in the parent node ...
Map<NodeKey, Name> newNames = changedChildren.getNewNames();
if (!newNames.isEmpty()) {
for (Map.Entry<NodeKey, Name> renameEntry : newNames.entrySet()) {
NodeKey renamedKey = renameEntry.getKey();
CachedNode oldRenamedNode = persistedCache.getNode(renamedKey);
if (oldRenamedNode == null) {
// The node was created in this session, so we can ignore this ...
continue;
}
Path renamedFromPath = workspacePaths.getPath(oldRenamedNode);
Path renamedToPath = pathFactory().create(renamedFromPath.getParent(), renameEntry.getValue());
changes.nodeRenamed(renamedKey, renamedToPath, renamedFromPath.getLastSegment(), primaryType,
mixinTypes, queryable);
if (isExternal) {
renamedExternalNodes.add(renamedKey);
}
}
}
// generate reordering events for nodes which have not been reordered to the end
Map<NodeKey, SessionNode.Insertions> insertionsByBeforeKey = changedChildren.getInsertionsByBeforeKey();
for (SessionNode.Insertions insertion : insertionsByBeforeKey.values()) {
for (ChildReference insertedRef : insertion.inserted()) {
CachedNode insertedNodePersistent = persistedCache.getNode(insertedRef);
CachedNode insertedNode = getNode(insertedRef.getKey());
Path nodeNewPath = sessionPaths.getPath(insertedNode);
if (insertedNodePersistent != null) {
Path nodeOldPath = workspacePaths.getPath(insertedNodePersistent);
Path insertedBeforePath = null;
CachedNode insertedBeforeNode = persistedCache.getNode(insertion.insertedBefore());
if (insertedBeforeNode != null) {
insertedBeforePath = workspacePaths.getPath(insertedBeforeNode);
boolean isSnsReordering = nodeOldPath.getLastSegment().getName()
.equals(insertedBeforePath.getLastSegment().getName());
if (isSnsReordering) {
nodeNewPath = insertedBeforePath;
}
}
changes.nodeReordered(insertedRef.getKey(), insertedNode.getPrimaryType(this),
insertedNode.getMixinTypes(this), node.getKey(), nodeNewPath, nodeOldPath,
insertedBeforePath, queryable);
} else {
// if the node is new and reordered at the same time (most likely due to either a version restore
// or explicit reordering of transient nodes) there is no "old path"
CachedNode insertedBeforeNode = getNode(insertion.insertedBefore().getKey());
Path insertedBeforePath = sessionPaths.getPath(insertedBeforeNode);
changes.nodeReordered(insertedRef.getKey(), insertedNode.getPrimaryType(this),
insertedNode.getMixinTypes(this), node.getKey(), nodeNewPath, null,
insertedBeforePath, queryable);
}
}
}
}
ReferrerChanges referrerChanges = node.getReferrerChanges();
boolean nodeChanged = false;
if (referrerChanges != null && !referrerChanges.isEmpty()) {
translator.changeReferrers(doc, referrerChanges);
changes.nodeChanged(key, newPath, primaryType, mixinTypes, queryable);
nodeChanged = true;
}
// write the federated segments
for (Map.Entry<String, String> federatedSegment : node.getAddedFederatedSegments().entrySet()) {
String externalNodeKey = federatedSegment.getKey();
String childName = federatedSegment.getValue();
translator.addFederatedSegment(doc, externalNodeKey, childName);
if (!nodeChanged) {
changes.nodeChanged(key, newPath, primaryType, mixinTypes, queryable);
nodeChanged = true;
}
}
Set<String> removedFederatedSegments = node.getRemovedFederatedSegments();
if (!removedFederatedSegments.isEmpty()) {
translator.removeFederatedSegments(doc, node.getRemovedFederatedSegments());
if (!nodeChanged) {
changes.nodeChanged(key, newPath, primaryType, mixinTypes, queryable);
nodeChanged = true;
}
}
// write additional node "metadata", meaning various flags which have internal meaning
if (!queryable) {
// we are only interested if the node is not queryable, as by default all nodes are queryable.
translator.setQueryable(doc, false);
}
if (node.isNew()) {
// We need to create the schematic entry for the new node ...
if (documentStore.storeDocument(keyStr, doc) != null) {
if (replacedNodes != null && replacedNodes.contains(key)) {
// Then a node is being removed and recreated with the same key ...
documentStore.localStore().put(keyStr, doc);
} else if (removedNodes != null && removedNodes.contains(key)) {
// Then a node is being removed and recreated with the same key ...
documentStore.localStore().put(keyStr, doc);
removedNodes.remove(key);
} else {
// We couldn't create the entry because one already existed ...
throw new DocumentAlreadyExistsException(keyStr);
}
}
} else {
boolean externalNodeChanged = isExternal
&& (hasPropertyChanges || node.hasNonPropertyChanges() || node.changedChildren()
.renameCount() > 0);
// writable connectors *may* change their data in-place, so the update operation needs to be called only
// after the index changes have finished.
if (externalNodeChanged) {
// in the case of external nodes, only if there are changes should the update be called
documentStore.updateDocument(keyStr, doc, node);
}
}
// The above code doesn't properly generate events for newly linked or unlinked nodes (e.g., shareable nodes
// in JCR), because NODE_ADDED or NODE_REMOVED events are generated based upon the creation or removal of the
// child nodes, whereas linking and unlinking nodes don't result in creation/removal of nodes. Instead,
// the linked/unlinked node is modified with the addition/removal of additional parents.
//
// NOTE that this happens somewhat rarely (as linked/shared nodes are used far less frequently) ...
//
if (additionalParents != null) {
// Generate NODE_ADDED events for each of the newly-added parents ...
for (NodeKey parentKey : additionalParents.getAdditions()) {
// Find the mutable parent node (if it exists) ...
SessionNode parent = this.changedNodes.get(parentKey);
if (parent != null) {
// Then the parent was changed in this session, so find the one-and-only child reference ...
ChildReference ref = parent.getChildReferences(this).getChild(key);
Path parentPath = sessionPaths.getPath(parent);
Path childPath = pathFactory().create(parentPath, ref.getSegment());
changes.nodeCreated(key, parentKey, childPath, primaryType, mixinTypes, null, queryable);
}
}
// Generate NODE_REMOVED events for each of the newly-removed parents ...
for (NodeKey parentKey : additionalParents.getRemovals()) {
// We need to read some information from the parent node before it was changed ...
CachedNode persistedParent = persistedCache.getNode(parentKey);
if (persistedParent != null) {
// Find the path to the removed child ...
ChildReference ref = persistedParent.getChildReferences(this).getChild(key);
if (ref != null) {
Path parentPath = workspacePaths.getPath(persistedParent);
Path childPath = pathFactory().create(parentPath, ref.getSegment());
changes.nodeRemoved(key, parentKey, childPath, primaryType, mixinTypes, queryable);
}
}
}
}