* build set of item id's which are within the scope of
* (i.e. affected by) this save operation
*/
Set<ItemId> affectedIds = new HashSet<ItemId>(dirty.size() + removed.size());
for (Iterator<ItemState> it =
new IteratorChain(dirty.iterator(), removed.iterator());
it.hasNext();) {
affectedIds.add(it.next().getId());
}
/**
* make sure that this save operation is totally 'self-contained'
* and independent; items within the scope of this save operation
* must not have 'external' dependencies;
* (e.g. moving a node requires that the target node including both
* old and new parents are saved)
*/
for (Iterator<ItemState> it =
new IteratorChain(dirty.iterator(), removed.iterator());
it.hasNext();) {
ItemState transientState = it.next();
if (transientState.isNode()) {
NodeState nodeState = (NodeState) transientState;
Set<NodeId> dependentIDs = new HashSet<NodeId>();
if (nodeState.hasOverlayedState()) {
NodeState overlayedState =
(NodeState) nodeState.getOverlayedState();
NodeId oldParentId = overlayedState.getParentId();
NodeId newParentId = nodeState.getParentId();
if (oldParentId != null) {
if (newParentId == null) {
// node has been removed, add old parents
// to dependencies
if (overlayedState.isShareable()) {
dependentIDs.addAll(overlayedState.getSharedSet());
} else {
dependentIDs.add(oldParentId);
}
} else {
if (!oldParentId.equals(newParentId)) {
// node has been moved to a new location,
// add old and new parent to dependencies
dependentIDs.add(oldParentId);
dependentIDs.add(newParentId);
} else {
// parent id hasn't changed, check whether
// the node has been renamed (JCR-1034)
if (!affectedIds.contains(newParentId)
&& stateMgr.hasTransientItemState(newParentId)) {
try {
NodeState parent = (NodeState) stateMgr.getTransientItemState(newParentId);
// check parent's renamed child node entries
for (Iterator<ChildNodeEntry> cneIt =
parent.getRenamedChildNodeEntries().iterator();
cneIt.hasNext();) {
ChildNodeEntry cne =
cneIt.next();
if (cne.getId().equals(nodeState.getId())) {
// node has been renamed,
// add parent to dependencies
dependentIDs.add(newParentId);
}
}
} catch (ItemStateException ise) {
// should never get here
log.warn("failed to retrieve transient state: " + newParentId, ise);
}
}
}
}
}
}
// removed child node entries
for (Iterator<ChildNodeEntry> cneIt =
nodeState.getRemovedChildNodeEntries().iterator();
cneIt.hasNext();) {
ChildNodeEntry cne = cneIt.next();
dependentIDs.add(cne.getId());
}
// added child node entries
for (Iterator<ChildNodeEntry> cneIt =
nodeState.getAddedChildNodeEntries().iterator();
cneIt.hasNext();) {
ChildNodeEntry cne = cneIt.next();
dependentIDs.add(cne.getId());
}
// now walk through dependencies and check whether they
// are within the scope of this save operation
Iterator<NodeId> depIt = dependentIDs.iterator();
while (depIt.hasNext()) {
NodeId id = depIt.next();
if (!affectedIds.contains(id)) {
// JCR-1359 workaround: check whether unresolved
// dependencies originate from 'this' session;
// otherwise ignore them
if (stateMgr.hasTransientItemState(id)
|| stateMgr.hasTransientItemStateInAttic(id)) {
// need to save dependency as well
String msg = itemMgr.safeGetJCRPath(id)
+ " needs to be saved as well.";
log.debug(msg);
throw new ConstraintViolationException(msg);
}
}
}
}
}
/**
* validate access and node type constraints
* (this will also validate child removals)
*/
validateTransientItems(dirty.iterator(), removed.iterator());
// start the update operation
try {
stateMgr.edit();
} catch (IllegalStateException e) {
String msg = "Unable to start edit operation";
log.debug(msg);
throw new RepositoryException(msg, e);
}
boolean succeeded = false;
try {
// process transient items marked as 'removed'
removeTransientItems(removed.iterator());
// process transient items that have change in mixins
processShareableNodes(dirty.iterator());
// initialize version histories for new nodes (might generate new transient state)
if (initVersionHistories(dirty.iterator())) {
// re-build the list of transient states because the previous call
// generated new transient state
dirty = getTransientStates();
}
// process 'new' or 'modified' transient states
persistTransientItems(dirty.iterator());
// dispose the transient states marked 'new' or 'modified'
// at this point item state data is pushed down one level,
// node instances are disconnected from the transient
// item state and connected to the 'overlayed' item state.
// transient item states must be removed now. otherwise
// the session item state provider will return an orphaned
// item state which is not referenced by any node instance.
for (Iterator<ItemState> it = dirty.iterator(); it.hasNext();) {
ItemState transientState = it.next();
// dispose the transient state, it is no longer used
stateMgr.disposeTransientItemState(transientState);
}
// end update operation
stateMgr.update();
// update operation succeeded
succeeded = true;
} catch (StaleItemStateException e) {
throw new InvalidItemStateException(e.getMessage());
} catch (ItemStateException e) {
throw new RepositoryException(
"Unable to update item: " + this, e);
} finally {
if (!succeeded) {
// update operation failed, cancel all modifications
stateMgr.cancel();
// JCR-288: if an exception has been thrown during
// update() the transient changes have already been
// applied by persistTransientItems() and we need to
// restore transient state, i.e. undo the effect of
// persistTransientItems()
restoreTransientItems(dirty.iterator());
}
}
// now it is safe to dispose the transient states:
// dispose the transient states marked 'removed'.
// item states in attic are removed after store, because
// the observation mechanism needs to build paths of removed
// items in store().
for (Iterator<ItemState> it = removed.iterator(); it.hasNext();) {
ItemState transientState = it.next();
// dispose the transient state, it is no longer used
stateMgr.disposeTransientItemStateInAttic(transientState);
}
}
}