* build set of item id's who are within the scope of
* (i.e. affected by) this save operation
*/
Set affectedIds = new HashSet(dirty.size() + removed.size());
for (Iterator it =
new IteratorChain(dirty.iterator(), removed.iterator());
it.hasNext();) {
affectedIds.add(((ItemState) it.next()).getId());
}
/**
* make sure that this save operation is totally 'self-contained'
* and independant; 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 it =
new IteratorChain(dirty.iterator(), removed.iterator());
it.hasNext();) {
ItemState transientState = (ItemState) it.next();
if (transientState.isNode()) {
NodeState nodeState = (NodeState) transientState;
Set dependentIDs = new HashSet();
if (nodeState.hasOverlayedState()) {
NodeId oldParentId =
nodeState.getOverlayedState().getParentId();
NodeId newParentId = nodeState.getParentId();
if (oldParentId != null) {
if (newParentId == null) {
// node has been removed, add old parent
// to dependencies
dependentIDs.add(oldParentId);
} else {
if (!oldParentId.equals(newParentId)) {
// node has been moved, add old and new parent
// to dependencies
dependentIDs.add(oldParentId);
dependentIDs.add(newParentId);
}
}
}
}
// removed child node entries
for (Iterator cneIt =
nodeState.getRemovedChildNodeEntries().iterator();
cneIt.hasNext();) {
NodeState.ChildNodeEntry cne =
(NodeState.ChildNodeEntry) cneIt.next();
dependentIDs.add(cne.getId());
}
// added child node entries
for (Iterator cneIt =
nodeState.getAddedChildNodeEntries().iterator();
cneIt.hasNext();) {
NodeState.ChildNodeEntry cne =
(NodeState.ChildNodeEntry) cneIt.next();
dependentIDs.add(cne.getId());
}
// now walk through dependencies and check whether they
// are within the scope of this save operation
Iterator depIt = dependentIDs.iterator();
while (depIt.hasNext()) {
NodeId id = (NodeId) depIt.next();
if (!affectedIds.contains(id)) {
// need to save the parent 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());
// 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 it = dirty.iterator(); it.hasNext();) {
ItemState transientState = (ItemState) 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) {
String msg = safeGetJCRPath() + ": unable to update item.";
log.debug(msg);
throw new RepositoryException(msg, 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 it = removed.iterator(); it.hasNext();) {
ItemState transientState = (ItemState) it.next();
// dispose the transient state, it is no longer used
stateMgr.disposeTransientItemStateInAttic(transientState);
}
}
}