Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java (revision 957189) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java (revision ) @@ -401,7 +401,7 @@ } /** - * Same as {@link #getDescendantTransientItemStates(NodeId)} + * Same as {@link #getDescendantTransientItemStates(ItemId)} * except that item state instances in the attic are returned. * * @param id identifier of the common parent of the transient item state @@ -819,8 +819,8 @@ transientState.setModCount(local.getModCount()); transientState.setStatus(ItemState.STATUS_EXISTING_MODIFIED); } catch (ItemStateException e) { - // something went wrong, mark as stale - transientState.setStatus(ItemState.STATUS_STALE_MODIFIED); + // something went wrong + transientState.setStatus(ItemState.STATUS_UNDEFINED); } } visibleState = transientState; @@ -838,61 +838,7 @@ */ public void stateModified(ItemState modified) { ItemState visibleState = modified; - if (modified.getContainer() != this) { - // local state was modified - ItemState transientState = transientStore.get(modified.getId()); - if (transientState != null) { - if (transientState.isNode() && !transientState.isStale()) { - // try to silently merge non-conflicting changes (JCR-584) - NodeStateMerger.MergeContext context = - new NodeStateMerger.MergeContext() { - public boolean isAdded(ItemId id) { - ItemState is = transientStore.get(id); - return is != null - && is.getStatus() == ItemState.STATUS_NEW; - } - - public boolean isDeleted(ItemId id) { - return atticStore.contains(id); - } - - public boolean isModified(ItemId id) { - ItemState is = transientStore.get(id); - return is != null - && is.getStatus() == ItemState.STATUS_EXISTING_MODIFIED; - } - - public boolean allowsSameNameSiblings(NodeId id) { - try { - NodeState ns = (NodeState) getItemState(id); - NodeState parent = (NodeState) getItemState(ns.getParentId()); - Name name = parent.getChildNodeEntry(id).getName(); - EffectiveNodeType ent = ntReg.getEffectiveNodeType( - parent.getNodeTypeName(), - parent.getMixinTypeNames()); - QNodeDefinition def = ent.getApplicableChildNodeDef(name, ns.getNodeTypeName(), ntReg); - return def != null && def.allowsSameNameSiblings(); - } catch (Exception e) { - log.warn("Unable to get node definition", e); - return false; - } - } - }; - if (NodeStateMerger.merge((NodeState) transientState, context)) { - // merge succeeded - return; - } - } - transientState.setStatus(ItemState.STATUS_STALE_MODIFIED); - visibleState = transientState; - } - // check attic as well (JCR-1432) - transientState = atticStore.get(modified.getId()); - if (transientState != null) { - transientState.setStatus(ItemState.STATUS_STALE_MODIFIED); - visibleState = transientState; - } - } + // JCR-2650: ignore external changes, they will be considered/merged on save(). dispatcher.notifyStateModified(visibleState); } Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java (revision 962485) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java (revision ) @@ -770,48 +770,44 @@ NodeState transientState = data.getNodeState(); - NodeState persistentState = (NodeState) transientState.getOverlayedState(); - if (persistentState == null) { + NodeState localState = (NodeState) transientState.getOverlayedState(); + if (localState == null) { // this node is 'new' - persistentState = stateMgr.createNew(transientState); + localState = stateMgr.createNew(transientState); } - synchronized (persistentState) { - // check staleness of transient state first - if (transientState.isStale()) { - String msg = - this + ": the node cannot be saved because it has been" - + " modified externally."; - log.debug(msg); - throw new InvalidItemStateException(msg); - } + synchronized (localState) { // copy state from transient state: // parent id's - persistentState.setParentId(transientState.getParentId()); + localState.setParentId(transientState.getParentId()); // primary type - persistentState.setNodeTypeName(transientState.getNodeTypeName()); + localState.setNodeTypeName(transientState.getNodeTypeName()); // mixin types - persistentState.setMixinTypeNames(transientState.getMixinTypeNames()); + localState.setMixinTypeNames(transientState.getMixinTypeNames()); // child node entries - persistentState.setChildNodeEntries(transientState.getChildNodeEntries()); + localState.setChildNodeEntries(transientState.getChildNodeEntries()); // property entries - persistentState.setPropertyNames(transientState.getPropertyNames()); + localState.setPropertyNames(transientState.getPropertyNames()); // shared set - persistentState.setSharedSet(transientState.getSharedSet()); + localState.setSharedSet(transientState.getSharedSet()); + // modCount + if (localState.getModCount() != transientState.getModCount()) { + localState.setModCount(transientState.getModCount()); + } // make state persistent - stateMgr.store(persistentState); + stateMgr.store(localState); } // tell state manager to disconnect item state stateMgr.disconnectTransientItemState(transientState); - // swap transient state with persistent state - data.setState(persistentState); + // swap transient state with local state + data.setState(localState); // reset status data.setStatus(STATUS_NORMAL); if (isShareable() && data.getPrimaryParentId() == null) { - data.setPrimaryParentId(persistentState.getParentId()); + data.setPrimaryParentId(localState.getParentId()); } } Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRefreshOperation.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRefreshOperation.java (revision 962485) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemRefreshOperation.java (revision ) @@ -57,7 +57,6 @@ // check status of this item's state if (state.isTransient()) { switch (state.getStatus()) { - case ItemState.STATUS_STALE_MODIFIED: case ItemState.STATUS_STALE_DESTROYED: // add this item's state to the list transientStates.add(state); @@ -87,7 +86,6 @@ for (ItemState transientState : stateMgr.getDescendantTransientItemStates(state.getId())) { switch (transientState.getStatus()) { - case ItemState.STATUS_STALE_MODIFIED: case ItemState.STATUS_STALE_DESTROYED: case ItemState.STATUS_NEW: case ItemState.STATUS_EXISTING_MODIFIED: Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java (revision 957571) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java (revision ) @@ -1086,7 +1086,6 @@ */ case ItemState.STATUS_EXISTING_REMOVED: case ItemState.STATUS_EXISTING_MODIFIED: - case ItemState.STATUS_STALE_MODIFIED: ItemState persistentState = discarded.getOverlayedState(); // the state is a transient wrapper for the underlying // persistent state, therefore restore the persistent state Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemState.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemState.java (revision 792437) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemState.java (revision ) @@ -53,10 +53,6 @@ */ public static final int STATUS_NEW = 4; /** - * 'existing', i.e. persistent state that has been persistently modified by somebody else - */ - public static final int STATUS_STALE_MODIFIED = 5; - /** * 'existing', i.e. persistent state that has been destroyed by somebody else */ public static final int STATUS_STALE_DESTROYED = 6; @@ -286,14 +282,9 @@ * @return true if this item state has become stale, false otherwise. */ public boolean isStale() { - if (isTransient) { - return status == STATUS_STALE_MODIFIED - || status == STATUS_STALE_DESTROYED; - } else { - return overlayedState != null - && modCount != overlayedState.getModCount(); - } + return overlayedState != null + && modCount != overlayedState.getModCount(); + } - } /** * Returns the NodeId of the parent NodeState or null @@ -324,7 +315,6 @@ case STATUS_EXISTING: case STATUS_EXISTING_REMOVED: case STATUS_EXISTING_MODIFIED: - case STATUS_STALE_MODIFIED: case STATUS_STALE_DESTROYED: case STATUS_UNDEFINED: status = newStatus; Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentRenameTest.java =================================================================== --- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentRenameTest.java (revision 951559) +++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentRenameTest.java (revision ) @@ -27,8 +27,8 @@ */ public class ConcurrentRenameTest extends AbstractConcurrencyTest { - private static final int NUM_MOVES = 100; - private static final int NUM_THREADS = 2; + private static final int NUM_MOVES = 1000; + private static final int NUM_THREADS = 10; public void testConcurrentRename() throws Exception { runTask(new Task() { Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java (revision 962485) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java (revision ) @@ -290,7 +290,7 @@ /** * Builds a list of transient (i.e. new or modified) item states that are - * within the scope of this.{@link #save()}. The collection + * within the scope of this.{@link #perform(SessionContext)}. The collection * returned is ordered depth-first, i.e. the item itself (if transient) * comes last. * @@ -316,11 +316,6 @@ 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 " @@ -351,11 +346,6 @@ 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" @@ -380,7 +370,7 @@ /** * 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()}. + * this.{@link #perform(SessionContext)}. * * @return list of transient item states * @throws InvalidItemStateException @@ -394,14 +384,8 @@ for (ItemState transientState : sism.getDescendantTransientItemStatesInAttic(state.getId())) { // check if stale - switch (transientState.getStatus()) { - case ItemState.STATUS_STALE_MODIFIED: + if (transientState.getStatus() == ItemState.STATUS_STALE_DESTROYED) { throw new InvalidItemStateException( - "Item can't be removed because it has been" - + " modified externally: " - + transientState.getId()); - case ItemState.STATUS_STALE_DESTROYED: - throw new InvalidItemStateException( "Item can't be removed because it has already" + " been deleted externally: " + transientState.getId()); Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentMixinModificationTest.java =================================================================== --- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentMixinModificationTest.java (revision 789279) +++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentMixinModificationTest.java (revision ) @@ -27,12 +27,17 @@ public void testMixin() throws Exception { Node n = testRootNode.addNode(nodeName1); + assertEquals(n.getSession(), superuser); superuser.save(); + n.addMixin(mixReferenceable); - session.getNode(testRoot).getNode(nodeName1).addMixin(mixLockable); - superuser.save(); + + Node n1 = session.getNode(testRoot).getNode(nodeName1); + n1.addMixin(mixLockable); + + superuser.save(); // => saves n (mix:referenceable) try { - session.save(); + session.save(); // saves n1 (mix:lockable) fail("InvalidItemStateException expected"); } catch (InvalidItemStateException e) { // expected Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateMap.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateMap.java (revision 792437) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateMap.java (revision ) @@ -126,9 +126,6 @@ case ItemState.STATUS_STALE_DESTROYED: ps.print("[stale, destroyed] "); break; - case ItemState.STATUS_STALE_MODIFIED: - ps.print("[stale, modified] "); - break; case ItemState.STATUS_UNDEFINED: ps.print("[undefined] "); break;