You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2010/06/23 12:14:27 UTC
svn commit: r957147 [1/2] - in
/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core:
ItemImpl.java ItemSaveOperation.java SessionImpl.java
session/SessionRefreshOperation.java session/SessionSaveOperation.java
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 <code>this.{@link #save()}</code>. 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<ItemState> getTransientStates()
- throws InvalidItemStateException, RepositoryException {
- // list of transient states that should be persisted
- ArrayList<ItemState> dirty = new ArrayList<ItemState>();
- ItemState transientState;
-
- if (isNode()) {
- // build list of 'new' or 'modified' descendants
- Iterator<ItemState> 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
- * <code>this.{@link #save()}</code>.
- *
- * @return list of transient item states
- * @throws InvalidItemStateException
- * @throws RepositoryException
- */
- private Collection<ItemState> getRemovedStates()
- throws InvalidItemStateException, RepositoryException {
- ArrayList<ItemState> removed = new ArrayList<ItemState>();
- ItemState transientState;
-
- if (isNode()) {
- Iterator<ItemState> 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<ItemState> dirty, Iterable<ItemState> 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<ItemState> 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<ItemState> 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<ItemState> 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 <code>mix:shareable</code>
- * or (some derived node type) has been added or removed:
- * <ul>
- * <li>If the mixin <code>mix:shareable</code> (or some derived node type),
- * then initialize the shared set inside the state.</li>
- * <li>If the mixin <code>mix:shareable</code> (or some derived node type)
- * has been removed, throw.</li>
- * </ul>
- */
- private void processShareableNodes(Iterable<ItemState> 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
- * <code>mix:versionable</code>.
- * <p/>
- * Called by {@link #save()}.
- *
- * @param states
- * @return true if this call generated new transient state; otherwise false
- * @throws RepositoryException
- */
- private boolean initVersionHistories(Iterable<ItemState> 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 <code>id</code> 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<ItemState> 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<ItemState> 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<ItemId, ItemState> affected =
- new HashMap<ItemId, ItemState>(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<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 (!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()));
}
/**