if (doc != null) translator.changeReferrers(doc, referrerChanges);
}
// if the node had any binary properties, make sure we decrement the ref count of each
for (Iterator<Property> propertyIterator = persisted.getProperties(persistedCache); propertyIterator.hasNext();) {
Property property = propertyIterator.next();
if (property.isBinary()) {
Object value = property.isMultiple() ? Arrays.asList(property.getValuesAsArray()) : property.getFirstValue();
translator.decrementBinaryReferenceCount(value, unusedBinaryKeys, null);
}
}
// Note 1: Do not actually remove the document from the documentStore yet; see below (note 2)
}
// Otherwise, the removed node was created in the session (but not ever persisted),
// so we don't have to do anything ...
} else {
// Get the primary and mixin type names; even though we're passing in the session, the two properties
// should be there and shouldn't require a looking in the cache...
Name primaryType = node.getPrimaryType(this);
Set<Name> mixinTypes = node.getMixinTypes(this);
boolean queryable = node.isQueryable(this);
CachedNode persisted = null;
Path newPath = sessionPaths.getPath(node);
NodeKey newParent = node.newParent();
EditableDocument doc = null;
ChangedAdditionalParents additionalParents = node.additionalParents();
if (node.isNew()) {
doc = Schematic.newDocument();
translator.setKey(doc, key);
translator.setParents(doc, newParent, null, additionalParents);
// Create an event ...
changes.nodeCreated(key, newParent, newPath, primaryType, mixinTypes, node.changedProperties(), queryable);
} else {
doc = documentStore.edit(keyStr, true, acquireLock);
if (doc == null) {
if (isExternal && renamedExternalNodes.contains(key)) {
// this is a renamed external node which has been processed in the parent, so we can skip it
continue;
}
// Could not find the entry in the documentStore, which means it was deleted by someone else
// just moments before we got our transaction to save ...
throw new DocumentNotFoundException(keyStr);
}
if (newParent != null) {
persisted = persistedCache.getNode(key);
// The node has moved (either within the same parent or to another parent) ...
Path oldPath = workspacePaths.getPath(persisted);
NodeKey oldParentKey = persisted.getParentKey(persistedCache);
if (!oldParentKey.equals(newParent) || (additionalParents != null && !additionalParents.isEmpty())) {
translator.setParents(doc, node.newParent(), oldParentKey, additionalParents);
}
// We only want to fire the event if the node we're working with is in the same workspace as the current
// workspace. The node will be in a different workspace when it is linked or un-linked
// (e.g. shareable node or jcr:system).
String workspaceKey = node.getKey().getWorkspaceKey();
boolean isSameWorkspace = persistedCache.getWorkspaceKey().equalsIgnoreCase(workspaceKey);
if (isSameWorkspace) {
changes.nodeMoved(key, primaryType, mixinTypes, newParent, oldParentKey, newPath, oldPath, queryable);
}
} else if (additionalParents != null) {
// The node in another workspace has been linked to this workspace ...
translator.setParents(doc, null, null, additionalParents);
}
// Deal with mixin changes here (since for new nodes they're put into the properties) ...
MixinChanges mixinChanges = node.mixinChanges(false);
if (mixinChanges != null && !mixinChanges.isEmpty()) {
Property oldProperty = translator.getProperty(doc, JcrLexicon.MIXIN_TYPES);
translator.addPropertyValues(doc, JcrLexicon.MIXIN_TYPES, true, mixinChanges.getAdded(),
unusedBinaryKeys, usedBinaryKeys);
translator.removePropertyValues(doc, JcrLexicon.MIXIN_TYPES, mixinChanges.getRemoved(), unusedBinaryKeys,
usedBinaryKeys);
// the property was changed ...
Property newProperty = translator.getProperty(doc, JcrLexicon.MIXIN_TYPES);
if (oldProperty == null) {
changes.propertyAdded(key, primaryType, mixinTypes, newPath, newProperty, queryable);
} else if (newProperty == null) {
changes.propertyRemoved(key, primaryType, mixinTypes, newPath, oldProperty, queryable);
} else {
changes.propertyChanged(key, primaryType, mixinTypes, newPath, newProperty, oldProperty, queryable);
}
}
}
LockChange lockChange = node.getLockChange();
if (lockChange != null) {
switch (lockChange) {
case LOCK_FOR_SESSION:
case LOCK_FOR_NON_SESSION:
// check is another session has already locked the document
if (translator.isLocked(doc)) {
throw new LockFailureException(key);
}
break;
case UNLOCK:
break;
}
}
// As we go through the removed and changed properties, we want to keep track of whether there are any
// effective modifications to the persisted properties.
boolean hasPropertyChanges = false;
// Save the removed properties ...
Set<Name> removedProperties = node.removedProperties();
if (!removedProperties.isEmpty()) {
assert !node.isNew();
if (persisted == null) {
persisted = persistedCache.getNode(key);
}
for (Name name : removedProperties) {
Property oldProperty = translator.removeProperty(doc, name, unusedBinaryKeys, usedBinaryKeys);
if (oldProperty != null) {
// the property was removed ...
changes.propertyRemoved(key, primaryType, mixinTypes, newPath, oldProperty, queryable);
// and we know that there are modifications to the properties ...
hasPropertyChanges = true;
}
}
}
// Save the changes to the properties
if (!node.changedProperties().isEmpty()) {
if (!node.isNew() && persisted == null) {
persisted = persistedCache.getNode(key);
}
for (Map.Entry<Name, Property> propEntry : node.changedProperties().entrySet()) {
Name name = propEntry.getKey();
Property prop = propEntry.getValue();
// Get the old property ...
Property oldProperty = persisted != null ? persisted.getProperty(name, persistedCache) : null;
translator.setProperty(doc, prop, unusedBinaryKeys, usedBinaryKeys);
if (oldProperty == null) {
// the property was created ...
changes.propertyAdded(key, primaryType, mixinTypes, newPath, prop, queryable);
// and we know that there are modifications to the properties ...
hasPropertyChanges = true;
} else if (hasPropertyChanges || !oldProperty.equals(prop)) {
// The 'hasPropertyChanges ||' in the above condition is what gives us the "slight optimization"
// mentioned in the longer comment above. This is noticeably more efficient (since the
// '!oldProperty.equals(prop)' has to be called for only some of the changes) and does result
// in correct indexing behavior, but the compromise is that some no-op property changes will
// result in a PROPERTY_CHANGE event. To remove all potential no-op PROPERTY CHANGE events,