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 [2/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
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java?rev=957147&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java Wed Jun 23 10:14:26 2010
@@ -0,0 +1,937 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+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;
+import javax.jcr.Item;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.ItemDefinition;
+import javax.jcr.nodetype.NodeDefinition;
+import javax.jcr.nodetype.NodeType;
+
+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.InternalVersionManager;
+import org.apache.jackrabbit.core.version.VersionHistoryInfo;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.QItemDefinition;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+/**
+ * The session operation triggered by {@link Item#save()}.
+ */
+class ItemSaveOperation extends SessionOperation {
+
+ /**
+ * Logger instance.
+ */
+ private static final Logger log =
+ LoggerFactory.getLogger(ItemSaveOperation.class);
+
+ private final boolean isNode;
+
+ private final ItemState state;
+
+ public ItemSaveOperation(boolean isNode, ItemState state) {
+ super("item save");
+ this.isNode = isNode;
+ this.state = state;
+ }
+
+ @Override
+ public void perform(SessionContext context) throws RepositoryException {
+ SessionItemStateManager stateMgr = context.getItemStateManager();
+
+ /**
+ * build list of transient (i.e. new & modified) states that
+ * should be persisted
+ */
+ Collection<ItemState> dirty;
+ try {
+ dirty = getTransientStates(stateMgr);
+ } catch (ConcurrentModificationException e) {
+ String msg = "Concurrent modification; session is closed";
+ log.error(msg, e);
+ context.getSessionImpl().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(stateMgr);
+
+ // 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 =
+ context.getItemManager().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(context, 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(stateMgr, removed);
+
+ // process transient items that have change in mixins
+ processShareableNodes(context, dirty);
+
+ // initialize version histories for new nodes (might generate new transient state)
+ if (initVersionHistories(context, dirty)) {
+ // re-build the list of transient states because the previous call
+ // generated new transient state
+ dirty = getTransientStates(stateMgr);
+ }
+
+ // process 'new' or 'modified' transient states
+ persistTransientItems(context.getItemManager(), 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(context, 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);
+ }
+ }
+
+ /**
+ * 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(
+ SessionItemStateManager stateMgr)
+ 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) state.getId());
+ 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 (state.isTransient()) {
+ 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(
+ SessionItemStateManager stateMgr)
+ throws InvalidItemStateException, RepositoryException {
+ ArrayList<ItemState> removed = new ArrayList<ItemState>();
+ ItemState transientState;
+
+ if (isNode) {
+ Iterator<ItemState> iter = stateMgr.getDescendantTransientItemStatesInAttic((NodeId) state.getId());
+ 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(
+ SessionContext context,
+ Iterable<ItemState> dirty, Iterable<ItemState> removed)
+ throws RepositoryException {
+ SessionImpl session = context.getSessionImpl();
+ ItemManager itemMgr = context.getItemManager();
+ SessionItemStateManager stateMgr = context.getItemStateManager();
+
+ 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(context, 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(
+ SessionItemStateManager stateMgr, 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);
+ }
+ }
+
+ /**
+ * 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(
+ SessionContext context, 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(context, old);
+ wasShareable = ntOld.includesNodeType(NameConstants.MIX_SHAREABLE);
+ }
+ EffectiveNodeType ntNew = getEffectiveNodeType(context, 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(
+ SessionContext context, Iterable<ItemState> states)
+ throws RepositoryException {
+ SessionImpl session = context.getSessionImpl();
+ ItemManager itemMgr = context.getItemManager();
+
+ // 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(context, 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;
+ }
+
+ /**
+ * walk through list of transient items and persist each one
+ */
+ private void persistTransientItems(
+ ItemManager itemMgr, 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(
+ SessionContext context, Iterable<ItemState> items) {
+ ItemManager itemMgr = context.getItemManager();
+ SessionItemStateManager stateMgr = context.getItemStateManager();
+
+ 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);
+ }
+ }
+ }
+
+ /**
+ * 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(
+ SessionContext context, NodeState state)
+ throws RepositoryException {
+ try {
+ NodeTypeRegistry registry =
+ context.getRepositoryContext().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);
+ }
+ }
+
+}
\ No newline at end of file
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java?rev=957147&r1=957146&r2=957147&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java Wed Jun 23 10:14:26 2010
@@ -22,8 +22,6 @@ import org.apache.jackrabbit.api.Jackrab
import org.apache.jackrabbit.api.security.principal.PrincipalManager;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.commons.AbstractSession;
-import org.apache.jackrabbit.core.cluster.ClusterException;
-import org.apache.jackrabbit.core.cluster.ClusterNode;
import org.apache.jackrabbit.core.config.WorkspaceConfig;
import org.apache.jackrabbit.core.data.GarbageCollector;
import org.apache.jackrabbit.core.id.NodeId;
@@ -41,6 +39,8 @@ import org.apache.jackrabbit.core.sessio
import org.apache.jackrabbit.core.session.ClosedSessionState;
import org.apache.jackrabbit.core.session.SessionContext;
import org.apache.jackrabbit.core.session.SessionOperation;
+import org.apache.jackrabbit.core.session.SessionRefreshOperation;
+import org.apache.jackrabbit.core.session.SessionSaveOperation;
import org.apache.jackrabbit.core.state.NodeState;
import org.apache.jackrabbit.core.state.SessionItemStateManager;
import org.apache.jackrabbit.core.util.Dumpable;
@@ -870,49 +870,14 @@ public class SessionImpl extends Abstrac
* {@inheritDoc}
*/
public void save() throws RepositoryException {
- perform(new SessionOperation("save") {
- @Override
- public void perform(SessionContext context)
- throws RepositoryException {
- // JCR-2425: check whether session is allowed to read root node
- if (hasPermission("/", ACTION_READ)) {
- context.getItemManager().getRootNode().save();
- } else {
- NodeId id = context.getItemStateManager().getIdOfRootTransientNodeState();
- context.getItemManager().getItem(id).save();
- }
- }
- });
+ perform(new SessionSaveOperation());
}
/**
* {@inheritDoc}
*/
- public void refresh(final boolean keepChanges) throws RepositoryException {
- perform(new SessionOperation("refresh") {
- @Override
- public void perform(SessionContext context)
- throws RepositoryException {
- // JCR-1753: Ensure that we are up to date with cluster changes
- ClusterNode cluster = repositoryContext.getClusterNode();
- if (cluster != null && clusterSyncOnRefresh()) {
- try {
- cluster.sync();
- } catch (ClusterException e) {
- throw new RepositoryException(
- "Unable to synchronize with the cluster", e);
- }
- }
-
- if (!keepChanges) {
- context.getItemStateManager().disposeAllTransientItemStates();
- } else {
- // FIXME should reset Item#status field to STATUS_NORMAL
- // of all non-transient instances; maybe also
- // have to reset stale ItemState instances
- }
- }
- });
+ public void refresh(boolean keepChanges) throws RepositoryException {
+ perform(new SessionRefreshOperation(keepChanges, clusterSyncOnRefresh()));
}
/**
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionRefreshOperation.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionRefreshOperation.java?rev=957147&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionRefreshOperation.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionRefreshOperation.java Wed Jun 23 10:14:26 2010
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.session;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.cluster.ClusterException;
+import org.apache.jackrabbit.core.cluster.ClusterNode;
+
+/**
+ * Operation to refresh the state of a session.
+ */
+public class SessionRefreshOperation extends SessionOperation {
+
+ private final boolean keepChanges;
+
+ private final boolean clusterSync;
+
+ public SessionRefreshOperation(boolean keepChanges, boolean clusterSync) {
+ super("refresh");
+ this.keepChanges = keepChanges;
+ this.clusterSync = clusterSync;
+ }
+
+ @Override
+ public void perform(SessionContext context) throws RepositoryException {
+ // JCR-1753: Ensure that we are up to date with cluster changes
+ ClusterNode cluster = context.getRepositoryContext().getClusterNode();
+ if (cluster != null && clusterSync) {
+ try {
+ cluster.sync();
+ } catch (ClusterException e) {
+ throw new RepositoryException(
+ "Unable to synchronize with the cluster", e);
+ }
+ }
+
+ if (!keepChanges) {
+ context.getItemStateManager().disposeAllTransientItemStates();
+ } else {
+ // FIXME should reset Item#status field to STATUS_NORMAL
+ // of all non-transient instances; maybe also
+ // have to reset stale ItemState instances
+ }
+ }
+
+}
\ No newline at end of file
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionRefreshOperation.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionSaveOperation.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionSaveOperation.java?rev=957147&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionSaveOperation.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionSaveOperation.java Wed Jun 23 10:14:26 2010
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.session;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.core.id.NodeId;
+
+public class SessionSaveOperation extends SessionOperation {
+
+ public SessionSaveOperation() {
+ super("save");
+ }
+
+ @Override
+ public void perform(SessionContext context)
+ throws RepositoryException {
+ NodeId id;
+ // JCR-2425: check whether session is allowed to read root node
+ if (context.getSessionImpl().hasPermission("/", Session.ACTION_READ)) {
+ id = context.getRootNodeId();
+ } else {
+ id = context.getItemStateManager().getIdOfRootTransientNodeState();
+ }
+ context.getItemManager().getItem(id).save();
+ }
+
+}
\ No newline at end of file
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionSaveOperation.java
------------------------------------------------------------------------------
svn:eol-style = native