Author: jukka Date: Wed Jun 23 10:14:26 2010 New Revision: 957147 URL: http://svn.apache.org/viewvc?rev=957147&view=rev Log: JCR-890: concurrent read-only access to a session Extract session operations to separate classes. Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java (with props) jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionRefreshOperation.java (with props) jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionSaveOperation.java (with props) Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java?rev=957147&r1=957146&r2=957147&view=diff ============================================================================== --- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java (original) +++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java Wed Jun 23 10:14:26 2010 @@ -17,13 +17,7 @@ package org.apache.jackrabbit.core; import java.util.ArrayList; -import java.util.Collection; -import java.util.ConcurrentModificationException; -import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; -import java.util.Map; -import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; @@ -32,45 +26,21 @@ import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.PathNotFoundException; -import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; -import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; -import javax.jcr.nodetype.ItemDefinition; -import javax.jcr.nodetype.NodeDefinition; -import javax.jcr.nodetype.NodeType; import javax.jcr.version.VersionException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; -import org.apache.jackrabbit.core.id.PropertyId; -import org.apache.jackrabbit.core.nodetype.EffectiveNodeType; -import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException; -import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; -import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; -import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry; -import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.session.SessionOperation; -import org.apache.jackrabbit.core.state.ChildNodeEntry; import org.apache.jackrabbit.core.state.ItemState; -import org.apache.jackrabbit.core.state.ItemStateException; -import org.apache.jackrabbit.core.state.NodeState; -import org.apache.jackrabbit.core.state.PropertyState; import org.apache.jackrabbit.core.state.SessionItemStateManager; -import org.apache.jackrabbit.core.state.StaleItemStateException; -import org.apache.jackrabbit.core.value.InternalValue; -import org.apache.jackrabbit.core.version.VersionHistoryInfo; -import org.apache.jackrabbit.core.version.InternalVersionManager; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; -import org.apache.jackrabbit.spi.QPropertyDefinition; -import org.apache.jackrabbit.spi.QItemDefinition; -import org.apache.jackrabbit.spi.commons.name.NameConstants; -import org.apache.jackrabbit.util.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -79,6 +49,9 @@ import org.slf4j.LoggerFactory; */ public abstract class ItemImpl implements Item { + /** + * Logger instance + */ private static Logger log = LoggerFactory.getLogger(ItemImpl.class); protected static final int STATUS_NORMAL = 0; @@ -221,626 +194,6 @@ public abstract class ItemImpl implement } /** - * Builds a list of transient (i.e. new or modified) item states that are - * within the scope of this.{@link #save()}. The collection - * returned is ordered depth-first, i.e. the item itself (if transient) - * comes last. - * - * @return list of transient item states - * @throws InvalidItemStateException - * @throws RepositoryException - */ - private Collection getTransientStates() - throws InvalidItemStateException, RepositoryException { - // list of transient states that should be persisted - ArrayList dirty = new ArrayList(); - ItemState transientState; - - if (isNode()) { - // build list of 'new' or 'modified' descendants - Iterator iter = stateMgr.getDescendantTransientItemStates((NodeId) id); - while (iter.hasNext()) { - transientState = iter.next(); - // fail-fast test: check status of transient state - switch (transientState.getStatus()) { - case ItemState.STATUS_NEW: - case ItemState.STATUS_EXISTING_MODIFIED: - // add modified state to the list - dirty.add(transientState); - break; - - case ItemState.STATUS_STALE_MODIFIED: - throw new InvalidItemStateException( - "Item cannot be saved because it has been " - + "modified externally: " + this); - - case ItemState.STATUS_STALE_DESTROYED: - throw new InvalidItemStateException( - "Item cannot be saved because it has been " - + "deleted externally: " + this); - - case ItemState.STATUS_UNDEFINED: - throw new InvalidItemStateException( - "Item cannot be saved; it seems to have been " - + "removed externally: " + this); - - default: - log.warn("Unexpected item state status: " - + transientState.getStatus() + " of " + this); - // ignore - break; - } - } - } - // fail-fast test: check status of this item's state - if (isTransient()) { - final ItemState state = getItemState(); - switch (state.getStatus()) { - case ItemState.STATUS_EXISTING_MODIFIED: - // add this item's state to the list - dirty.add(state); - break; - - case ItemState.STATUS_NEW: - throw new RepositoryException( - "Cannot save a new item: " + this); - - case ItemState.STATUS_STALE_MODIFIED: - throw new InvalidItemStateException( - "Item cannot be saved because it has been" - + " modified externally: " + this); - - case ItemState.STATUS_STALE_DESTROYED: - throw new InvalidItemStateException( - "Item cannot be saved because it has been" - + " deleted externally:" + this); - - case ItemState.STATUS_UNDEFINED: - throw new InvalidItemStateException( - "Item cannot be saved; it seems to have been" - + " removed externally: " + this); - - default: - log.warn("Unexpected item state status:" - + state.getStatus() + " of " + this); - // ignore - break; - } - } - - return dirty; - } - - /** - * Builds a list of transient descendant item states in the attic - * (i.e. those marked as 'removed') that are within the scope of - * this.{@link #save()}. - * - * @return list of transient item states - * @throws InvalidItemStateException - * @throws RepositoryException - */ - private Collection getRemovedStates() - throws InvalidItemStateException, RepositoryException { - ArrayList removed = new ArrayList(); - ItemState transientState; - - if (isNode()) { - Iterator iter = stateMgr.getDescendantTransientItemStatesInAttic((NodeId) id); - while (iter.hasNext()) { - transientState = iter.next(); - // check if stale - if (transientState.getStatus() == ItemState.STATUS_STALE_MODIFIED) { - String msg = transientState.getId() - + ": the item cannot be removed because it has been modified externally."; - log.debug(msg); - throw new InvalidItemStateException(msg); - } - if (transientState.getStatus() == ItemState.STATUS_STALE_DESTROYED) { - String msg = transientState.getId() - + ": the item cannot be removed because it has already been deleted externally."; - log.debug(msg); - throw new InvalidItemStateException(msg); - } - removed.add(transientState); - } - } - return removed; - } - - /** - * the following validations/checks are performed on transient items: - * - * for every transient item: - * - if it is 'modified' or 'new' check the corresponding write permission. - * - if it is 'removed' check the REMOVE permission - * - * for every transient node: - * - if it is 'new' check that its node type satisfies the - * 'required node type' constraint specified in its definition - * - check if 'mandatory' child items exist - * - * for every transient property: - * - check if the property value satisfies the value constraints - * specified in the property's definition - * - * note that the protected flag is checked in Node.addNode/Node.remove - * (for adding/removing child entries of a node), in - * Node.addMixin/removeMixin/setPrimaryType (for type changes on nodes) - * and in Property.setValue (for properties to be modified). - */ - private void validateTransientItems(Iterable dirty, Iterable removed) - throws AccessDeniedException, ConstraintViolationException, - RepositoryException { - AccessManager accessMgr = session.getAccessManager(); - NodeTypeManagerImpl ntMgr = session.getNodeTypeManager(); - // walk through list of dirty transient items and validate each - for (ItemState itemState : dirty) { - ItemDefinition def; - if (itemState.isNode()) { - def = itemMgr.getDefinition((NodeState) itemState); - } else { - def = itemMgr.getDefinition((PropertyState) itemState); - } - /* check permissions for non-protected items. protected items are - only added through API methods which need to assert that - permissions are not violated. - */ - if (!def.isProtected()) { - /* detect the effective set of modification: - - new added node -> add_node perm on the child - - new property added -> set_property permission - - property modified -> set_property permission - - modified nodes can be ignored for changes only included - child-item addition or removal or changes of protected - properties such as mixin-types which are covered separately - note: removed items are checked later on. - note: reordering of child nodes has been covered upfront as - this information isn't available here. - */ - Path path = stateMgr.getHierarchyMgr().getPath(itemState.getId()); - boolean isGranted = true; - if (itemState.isNode()) { - if (itemState.getStatus() == ItemState.STATUS_NEW) { - isGranted = accessMgr.isGranted(path, Permission.ADD_NODE); - } // else: modified node (see comment above) - } else { - // modified or new property: set_property permission - isGranted = accessMgr.isGranted(path, Permission.SET_PROPERTY); - } - - if (!isGranted) { - String msg = itemMgr.safeGetJCRPath(path) + ": not allowed to add or modify item"; - log.debug(msg); - throw new AccessDeniedException(msg); - } - } - - if (itemState.isNode()) { - // the transient item is a node - NodeState nodeState = (NodeState) itemState; - ItemId id = nodeState.getNodeId(); - NodeDefinition nodeDef = (NodeDefinition) def; - // primary type - NodeTypeImpl pnt = ntMgr.getNodeType(nodeState.getNodeTypeName()); - // effective node type (primary type incl. mixins) - EffectiveNodeType ent = getEffectiveNodeType(nodeState); - /** - * if the transient node was added (i.e. if it is 'new') or if - * its primary type has changed, check its node type against the - * required node type in its definition - */ - if (nodeState.getStatus() == ItemState.STATUS_NEW - || !nodeState.getNodeTypeName().equals( - ((NodeState) nodeState.getOverlayedState()).getNodeTypeName())) { - for (NodeType ntReq : nodeDef.getRequiredPrimaryTypes()) { - Name ntName = ((NodeTypeImpl) ntReq).getQName(); - if (!(pnt.getQName().equals(ntName) - || pnt.isDerivedFrom(ntName))) { - /** - * the transient node's primary node type does not - * satisfy the 'required primary types' constraint - */ - String msg = itemMgr.safeGetJCRPath(id) - + " must be of node type " + ntReq.getName(); - log.debug(msg); - throw new ConstraintViolationException(msg); - } - } - } - - // mandatory child properties - for (QPropertyDefinition pd : ent.getMandatoryPropDefs()) { - if (pd.getDeclaringNodeType().equals(NameConstants.MIX_VERSIONABLE) - || pd.getDeclaringNodeType().equals(NameConstants.MIX_SIMPLE_VERSIONABLE)) { - /** - * todo FIXME workaround for mix:versionable: - * the mandatory properties are initialized at a - * later stage and might not exist yet - */ - continue; - } - String msg = itemMgr.safeGetJCRPath(id) - + ": mandatory property " + pd.getName() - + " does not exist"; - if (!nodeState.hasPropertyName(pd.getName())) { - log.debug(msg); - throw new ConstraintViolationException(msg); - } else { - /* - there exists a property with the mandatory-name. - make sure the property really has the expected mandatory - property definition (and not another non-mandatory def, - such as e.g. multivalued residual instead of single-value - mandatory, named def). - */ - PropertyId pi = new PropertyId(nodeState.getNodeId(), pd.getName()); - ItemData childData = itemMgr.getItemData(pi, null, false); - if (!childData.getDefinition().isMandatory()) { - throw new ConstraintViolationException(msg); - } - } - } - // mandatory child nodes - for (QItemDefinition cnd : ent.getMandatoryNodeDefs()) { - String msg = itemMgr.safeGetJCRPath(id) - + ": mandatory child node " + cnd.getName() - + " does not exist"; - if (!nodeState.hasChildNodeEntry(cnd.getName())) { - log.debug(msg); - throw new ConstraintViolationException(msg); - } else { - /* - there exists a child node with the mandatory-name. - make sure the node really has the expected mandatory - node definition. - */ - boolean hasMandatoryChild = false; - for (ChildNodeEntry cne : nodeState.getChildNodeEntries(cnd.getName())) { - ItemData childData = itemMgr.getItemData(cne.getId(), null, false); - if (childData.getDefinition().isMandatory()) { - hasMandatoryChild = true; - break; - } - } - if (!hasMandatoryChild) { - throw new ConstraintViolationException(msg); - } - } - } - } else { - // the transient item is a property - PropertyState propState = (PropertyState) itemState; - ItemId propId = propState.getPropertyId(); - org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl propDef = (org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl) def; - - /** - * check value constraints - * (no need to check value constraints of protected properties - * as those are set by the implementation only, i.e. they - * cannot be set by the user through the api) - */ - if (!def.isProtected()) { - String[] constraints = propDef.getValueConstraints(); - if (constraints != null) { - InternalValue[] values = propState.getValues(); - try { - EffectiveNodeType.checkSetPropertyValueConstraints( - propDef.unwrap(), values); - } catch (RepositoryException e) { - // repack exception for providing more verbose error message - String msg = itemMgr.safeGetJCRPath(propId) + ": " + e.getMessage(); - log.debug(msg); - throw new ConstraintViolationException(msg); - } - - /** - * need to manually check REFERENCE value constraints - * as this requires a session (target node needs to - * be checked) - */ - if (constraints.length > 0 - && (propDef.getRequiredType() == PropertyType.REFERENCE - || propDef.getRequiredType() == PropertyType.WEAKREFERENCE)) { - for (InternalValue internalV : values) { - boolean satisfied = false; - String constraintViolationMsg = null; - try { - NodeId targetId = internalV.getNodeId(); - if (propDef.getRequiredType() == PropertyType.WEAKREFERENCE - && !itemMgr.itemExists(targetId)) { - // target of weakref doesn;t exist, skip - continue; - } - Node targetNode = session.getNodeById(targetId); - /** - * constraints are OR-ed, i.e. at least one - * has to be satisfied - */ - for (String constrNtName : constraints) { - /** - * a [WEAK]REFERENCE value constraint specifies - * the name of the required node type of - * the target node - */ - if (targetNode.isNodeType(constrNtName)) { - satisfied = true; - break; - } - } - if (!satisfied) { - NodeType[] mixinNodeTypes = targetNode.getMixinNodeTypes(); - String[] targetMixins = new String[mixinNodeTypes.length]; - for (int j = 0; j < mixinNodeTypes.length; j++) { - targetMixins[j] = mixinNodeTypes[j].getName(); - } - String targetMixinsString = Text.implode(targetMixins, ", "); - String constraintsString = Text.implode(constraints, ", "); - constraintViolationMsg = itemMgr.safeGetJCRPath(propId) - + ": is constraint to [" - + constraintsString - + "] but references [primaryType=" - + targetNode.getPrimaryNodeType().getName() - + ", mixins=" - + targetMixinsString + "]"; - } - } catch (RepositoryException re) { - String msg = itemMgr.safeGetJCRPath(propId) - + ": failed to check " - + ((propDef.getRequiredType() == PropertyType.REFERENCE) ? "REFERENCE" : "WEAKREFERENCE") - + " value constraint"; - log.debug(msg); - throw new ConstraintViolationException(msg, re); - } - if (!satisfied) { - log.debug(constraintViolationMsg); - throw new ConstraintViolationException(constraintViolationMsg); - } - } - } - } - } - - /** - * no need to check the protected flag as this is checked - * in PropertyImpl.setValue(Value) - */ - } - } - - // walk through list of removed transient items and check REMOVE permission - for (ItemState itemState : removed) { - QItemDefinition def; - try { - if (itemState.isNode()) { - def = itemMgr.getDefinition((NodeState) itemState).unwrap(); - } else { - def = itemMgr.getDefinition((PropertyState) itemState).unwrap(); - } - } catch (ConstraintViolationException e) { - // since identifier of assigned definition is not stored anymore - // with item state (see JCR-2170), correct definition cannot be - // determined for items which have been removed due to removal - // of a mixin (see also JCR-2130 & JCR-2408) - continue; - } - if (!def.isProtected()) { - Path path = stateMgr.getAtticAwareHierarchyMgr().getPath(itemState.getId()); - // check REMOVE permission - int permission = (itemState.isNode()) ? Permission.REMOVE_NODE : Permission.REMOVE_PROPERTY; - if (!accessMgr.isGranted(path, permission)) { - String msg = itemMgr.safeGetJCRPath(path) - + ": not allowed to remove item"; - log.debug(msg); - throw new AccessDeniedException(msg); - } - } - } - } - - /** - * walk through list of transient items marked 'removed' and - * definitively remove each one - */ - private void removeTransientItems(Iterable states) { - for (ItemState transientState : states) { - ItemState persistentState = transientState.getOverlayedState(); - /** - * remove persistent state - * - * this will indirectly (through stateDestroyed listener method) - * permanently invalidate all Item instances wrapping it - */ - stateMgr.destroy(persistentState); - } - } - - /** - * walk through list of transient items and persist each one - */ - private void persistTransientItems(Iterable states) - throws RepositoryException { - for (ItemState state : states) { - // persist state of transient item - itemMgr.getItem(state.getId()).makePersistent(); - } - } - - /** - * walk through list of transient states and re-apply transient changes - */ - private void restoreTransientItems(Iterable items) { - for (ItemState itemState : items) { - ItemId id = itemState.getId(); - ItemImpl item; - - try { - if (stateMgr.isItemStateInAttic(id)) { - // If an item has been removed and then again created, the - // item is lost after persistTransientItems() and the - // TransientItemStateManager will bark because of a deleted - // state in its attic. We therefore have to forge a new item - // instance ourself. - item = itemMgr.createItemInstance(itemState); - itemState.setStatus(ItemState.STATUS_NEW); - } else { - try { - item = itemMgr.getItem(id); - } catch (ItemNotFoundException infe) { - // itemState probably represents a 'new' item and the - // ItemImpl instance wrapping it has already been gc'ed; - // we have to re-create the ItemImpl instance - item = itemMgr.createItemInstance(itemState); - itemState.setStatus(ItemState.STATUS_NEW); - } - } - // re-apply transient changes - // for persistent nodes undo effect of item.makePersistent() - if (item.isNode()) { - NodeImpl node = (NodeImpl) item; - node.restoreTransient((NodeState) itemState); - } else { - PropertyImpl prop = (PropertyImpl) item; - prop.restoreTransient((PropertyState) itemState); - } - } catch (RepositoryException re) { - // something went wrong, log exception and carry on - String msg = itemMgr.safeGetJCRPath(id) - + ": failed to restore transient state"; - log.warn(msg, re); - } - } - } - - /** - * Process all items given in iterator and check whether mix:shareable - * or (some derived node type) has been added or removed: - *
    - *
  • If the mixin mix:shareable (or some derived node type), - * then initialize the shared set inside the state.
  • - *
  • If the mixin mix:shareable (or some derived node type) - * has been removed, throw.
  • - *
- */ - private void processShareableNodes(Iterable states) throws RepositoryException { - for (ItemState is : states) { - if (is.isNode()) { - NodeState ns = (NodeState) is; - boolean wasShareable = false; - if (ns.hasOverlayedState()) { - NodeState old = (NodeState) ns.getOverlayedState(); - EffectiveNodeType ntOld = getEffectiveNodeType(old); - wasShareable = ntOld.includesNodeType(NameConstants.MIX_SHAREABLE); - } - EffectiveNodeType ntNew = getEffectiveNodeType(ns); - boolean isShareable = ntNew.includesNodeType(NameConstants.MIX_SHAREABLE); - - if (!wasShareable && isShareable) { - // mix:shareable has been added - ns.addShare(ns.getParentId()); - - } else if (wasShareable && !isShareable) { - // mix:shareable has been removed: not supported - String msg = "Removing mix:shareable is not supported."; - log.debug(msg); - throw new UnsupportedRepositoryOperationException(msg); - } - } - } - } - - /** - * Initializes the version history of all new nodes of node type - * mix:versionable. - *

- * Called by {@link #save()}. - * - * @param states - * @return true if this call generated new transient state; otherwise false - * @throws RepositoryException - */ - private boolean initVersionHistories(Iterable states) throws RepositoryException { - // walk through list of transient items and search for new versionable nodes - boolean createdTransientState = false; - for (ItemState itemState : states) { - if (itemState.isNode()) { - NodeState nodeState = (NodeState) itemState; - EffectiveNodeType nt = getEffectiveNodeType(nodeState); - if (nt.includesNodeType(NameConstants.MIX_VERSIONABLE)) { - if (!nodeState.hasPropertyName(NameConstants.JCR_VERSIONHISTORY)) { - NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId()); - InternalVersionManager vMgr = session.getInternalVersionManager(); - /** - * check if there's already a version history for that - * node; this would e.g. be the case if a versionable - * node had been exported, removed and re-imported with - * either IMPORT_UUID_COLLISION_REMOVE_EXISTING or - * IMPORT_UUID_COLLISION_REPLACE_EXISTING; - * otherwise create a new version history - */ - VersionHistoryInfo history = - vMgr.getVersionHistory(session, nodeState, null); - InternalValue historyId = InternalValue.create( - history.getVersionHistoryId()); - InternalValue versionId = InternalValue.create( - history.getRootVersionId()); - node.internalSetProperty( - NameConstants.JCR_VERSIONHISTORY, historyId); - node.internalSetProperty( - NameConstants.JCR_BASEVERSION, versionId); - node.internalSetProperty( - NameConstants.JCR_ISCHECKEDOUT, - InternalValue.create(true)); - node.internalSetProperty( - NameConstants.JCR_PREDECESSORS, - new InternalValue[] { versionId }); - createdTransientState = true; - } - } else if (nt.includesNodeType(NameConstants.MIX_SIMPLE_VERSIONABLE)) { - // we need to check the version manager for an existing - // version history, since simple versioning does not - // expose it's reference in a property - InternalVersionManager vMgr = session.getInternalVersionManager(); - vMgr.getVersionHistory(session, nodeState, null); - - // create isCheckedOutProperty if not already exists - NodeImpl node = (NodeImpl) itemMgr.getItem(itemState.getId()); - if (!nodeState.hasPropertyName(NameConstants.JCR_ISCHECKEDOUT)) { - node.internalSetProperty( - NameConstants.JCR_ISCHECKEDOUT, - InternalValue.create(true)); - createdTransientState = true; - } - } - } - } - return createdTransientState; - } - - /** - * Helper method that builds the effective (i.e. merged and resolved) - * node type representation of the specified node's primary and mixin - * node types. - * - * @param state - * @return the effective node type - * @throws RepositoryException - */ - private EffectiveNodeType getEffectiveNodeType(NodeState state) - throws RepositoryException { - try { - NodeTypeRegistry registry = - session.getNodeTypeManager().getNodeTypeRegistry(); - return registry.getEffectiveNodeType( - state.getNodeTypeName(), state.getMixinTypeNames()); - } catch (NodeTypeConflictException e) { - throw new RepositoryException( - "Failed to build effective node type of node state " - + state.getId(), e); - } - } - - /** * Failsafe mapping of internal id to JCR path for use in * diagnostic output, error messages etc. * @@ -969,215 +322,7 @@ public abstract class ItemImpl implement // check state of this instance sanityCheck(); - perform(new SaveOperation()); - } - - private class SaveOperation extends SessionOperation { - - public SaveOperation() { - super("item save"); - } - - @Override - public void perform(SessionContext context) throws RepositoryException { - /** - * build list of transient (i.e. new & modified) states that - * should be persisted - */ - Collection dirty; - try { - dirty = getTransientStates(); - } catch (ConcurrentModificationException e) { - String msg = "Concurrent modification; session is closed"; - log.error(msg, e); - session.logout(); - throw e; - } - if (dirty.size() == 0) { - // no transient items, nothing to do here - return; - } - - /** - * build list of transient descendants in the attic - * (i.e. those marked as 'removed') - */ - Collection removed = getRemovedStates(); - - // All affected item states. The keys are used to look up whether - // an item is affected, and the values are iterated through below - Map affected = - new HashMap(dirty.size() + removed.size()); - for (ItemState state : dirty) { - affected.put(state.getId(), state); - } - for (ItemState state : removed) { - affected.put(state.getId(), state); - } - - /** - * 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 (ItemState transientState : affected.values()) { - if (transientState.isNode()) { - NodeState nodeState = (NodeState) transientState; - Set dependentIDs = new HashSet(); - 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 (!affected.containsKey(newParentId) - && stateMgr.hasTransientItemState(newParentId)) { - try { - NodeState parent = (NodeState) stateMgr.getTransientItemState(newParentId); - // check parent's renamed child node entries - for (ChildNodeEntry cne : parent.getRenamedChildNodeEntries()) { - 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 (ChildNodeEntry cne : nodeState.getRemovedChildNodeEntries()) { - dependentIDs.add(cne.getId()); - } - // added child node entries - for (ChildNodeEntry cne : nodeState.getAddedChildNodeEntries()) { - dependentIDs.add(cne.getId()); - } - - // now walk through dependencies and check whether they - // are within the scope of this save operation - for (NodeId id : dependentIDs) { - if (!affected.containsKey(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, removed); - - // 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); - - // process transient items that have change in mixins - processShareableNodes(dirty); - - // initialize version histories for new nodes (might generate new transient state) - if (initVersionHistories(dirty)) { - // 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); - - // 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 (ItemState transientState : dirty) { - // 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); - } - } - - // 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 (ItemState transientState : removed) { - // dispose the transient state, it is no longer used - stateMgr.disposeTransientItemStateInAttic(transientState); - } - } + perform(new ItemSaveOperation(isNode(), getItemState())); } /**