You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ck...@apache.org on 2010/10/01 10:19:42 UTC

svn commit: r1003428 - in /jackrabbit/branches/1.6/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/ main/java/org/apache/jackrabbit/core/state/ test/java/org/apache/jackrabbit/core/

Author: ckoell
Date: Fri Oct  1 08:19:42 2010
New Revision: 1003428

URL: http://svn.apache.org/viewvc?rev=1003428&view=rev
Log:
1.6: Modified Patch for JCR-2598

Added:
    jackrabbit/branches/1.6/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrencyTest3.java   (with props)
Modified:
    jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java
    jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java
    jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
    jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
    jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChildNodeEntries.java
    jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java

Modified: jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java?rev=1003428&r1=1003427&r2=1003428&view=diff
==============================================================================
--- jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java (original)
+++ jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java Fri Oct  1 08:19:42 2010
@@ -569,14 +569,17 @@ public class BatchedItemOperations exten
                 throw new UnsupportedRepositoryOperationException(msg);
             }
 
-            // remove child node entry from old parent
-            srcParent.removeChildNodeEntry(srcName.getName(), srcNameIndex);
+            // do move:
+            // 1. remove child node entry from old parent
+            boolean success = srcParent.removeChildNodeEntry(target.getNodeId());
+            if (success) {
 
-            // re-parent target node
-            target.setParentId(destParent.getNodeId());
+                // 2. re-parent target node
+                target.setParentId(destParent.getNodeId());
 
-            // add child node entry to new parent
-            destParent.addChildNodeEntry(destName.getName(), target.getNodeId());
+                // 3. add child node entry to new parent
+                destParent.addChildNodeEntry(destName.getName(), target.getNodeId());
+            }
         }
 
         // store states

Modified: jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java?rev=1003428&r1=1003427&r2=1003428&view=diff
==============================================================================
--- jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java (original)
+++ jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java Fri Oct  1 08:19:42 2010
@@ -850,10 +850,10 @@ public abstract class ItemImpl implement
         }
 
         // delegate the removal of the child item to the parent node
-        Path.Element thisName = getPrimaryPath().getNameElement();
         if (isNode()) {
-            parentNode.removeChildNode(thisName.getName(), thisName.getIndex());
+            parentNode.removeChildNode((NodeId) getId());
         } else {
+            Path.Element thisName = getPrimaryPath().getNameElement();
             parentNode.removeChildProperty(thisName.getName());
         }
     }

Modified: jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java?rev=1003428&r1=1003427&r2=1003428&view=diff
==============================================================================
--- jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java (original)
+++ jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java Fri Oct  1 08:19:42 2010
@@ -600,6 +600,8 @@ public class NodeImpl extends ItemImpl i
         itemMgr.getItem(propId).setRemoved();
     }
 
+    // The index may have changed because of changes by another session. Use removeChildNode(NodeId childId)
+    // instead
     protected void removeChildNode(Name nodeName, int index)
             throws RepositoryException {
         // modify the state of 'this', i.e. the parent node
@@ -627,7 +629,30 @@ public class NodeImpl extends ItemImpl i
             throw new RepositoryException(msg);
         }
     }
+    
+    protected void removeChildNode(NodeId childId) throws RepositoryException {
+        // modify the state of 'this', i.e. the parent node
+        NodeState thisState = (NodeState) getOrCreateTransientItemState();
+        ChildNodeEntry entry =
+                thisState.getChildNodeEntry(childId);
+        if (entry == null) {
+            String msg = "failed to remove child " + childId + " of " + this;
+            log.debug(msg);
+            throw new RepositoryException(msg);
+        }
+
+        // notify target of removal
+        NodeImpl childNode = itemMgr.getNode(childId, getNodeId());
+        childNode.onRemove(getNodeId());
 
+        // remove the child node entry
+        if (!thisState.removeChildNodeEntry(childId)) {
+            String msg = "failed to remove child " + childId + " of " + this;
+            log.debug(msg);
+            throw new RepositoryException(msg);
+        }
+    }
+    
     protected void onRedefine(NodeDef def) throws RepositoryException {
         NodeDefinitionImpl newDef =
                 session.getNodeTypeManager().getNodeDefinition(def);
@@ -670,7 +695,7 @@ public class NodeImpl extends ItemImpl i
                 NodeImpl childNode = itemMgr.getNode(childId, getNodeId());
                 childNode.onRemove(thisState.getNodeId());
                 // remove the child node entry
-                thisState.removeChildNodeEntry(entry.getName(), entry.getIndex());
+                thisState.removeChildNodeEntry(childId);
             }
         }
 
@@ -1307,7 +1332,7 @@ public class NodeImpl extends ItemImpl i
 
                 if (oldDef.isProtected()) {
                     // remove 'orphaned' protected child node immediately
-                    removeChildNode(entry.getName(), entry.getIndex());
+                    removeChildNode(entry.getId());
                     continue;
                 }
 
@@ -1322,7 +1347,7 @@ public class NodeImpl extends ItemImpl i
                 } catch (ConstraintViolationException cve) {
                     // no suitable definition found for this child node,
                     // remove it
-                    removeChildNode(entry.getName(), entry.getIndex());
+                    removeChildNode(entry.getId());
                 }
             }
             success = true;
@@ -4932,7 +4957,7 @@ public class NodeImpl extends ItemImpl i
                         NodeImpl node = (NodeImpl) itemMgr.getItem(nodeState.getId());
                         if (node.getDefinition().isProtected()) {
                             // remove 'orphaned' protected child node immediately
-                            removeChildNode(entry.getName(), entry.getIndex());
+                            removeChildNode(entry.getId());
                             continue;
                         }
                         NodeDefinitionImpl ndi = getApplicableChildNodeDefinition(
@@ -4945,7 +4970,7 @@ public class NodeImpl extends ItemImpl i
                     } catch (ConstraintViolationException cve) {
                         // no suitable definition found for this child node,
                         // remove it
-                        removeChildNode(entry.getName(), entry.getIndex());
+                        removeChildNode(entry.getId());
                     }
                 }
             } catch (ItemStateException ise) {

Modified: jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java?rev=1003428&r1=1003427&r2=1003428&view=diff
==============================================================================
--- jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java (original)
+++ jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java Fri Oct  1 08:19:42 2010
@@ -1111,19 +1111,25 @@ public class SessionImpl extends Abstrac
                 throw new UnsupportedRepositoryOperationException(msg);
             }
 
-            // do move:
-            // 1. remove child node entry from old parent
+            // Get the transient states
             NodeState srcParentState =
                     (NodeState) srcParentNode.getOrCreateTransientItemState();
-            srcParentState.removeChildNodeEntry(srcName.getName(), index);
-            // 2. re-parent target node
             NodeState targetState =
                     (NodeState) targetNode.getOrCreateTransientItemState();
-            targetState.setParentId(destParentNode.getNodeId());
-            // 3. add child node entry to new parent
             NodeState destParentState =
                     (NodeState) destParentNode.getOrCreateTransientItemState();
-            destParentState.addChildNodeEntry(destName.getName(), targetId);
+
+            // do move:
+            // 1. remove child node entry from old parent
+            boolean success = srcParentState.removeChildNodeEntry(targetId);
+            if (success) {
+
+                // 2. re-parent target node
+                targetState.setParentId(destParentNode.getNodeId());
+
+                // 3. add child node entry to new parent
+                destParentState.addChildNodeEntry(destName.getName(), targetId);
+            }
         }
 
         // change definition of target

Modified: jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChildNodeEntries.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChildNodeEntries.java?rev=1003428&r1=1003427&r2=1003428&view=diff
==============================================================================
--- jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChildNodeEntries.java (original)
+++ jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChildNodeEntries.java Fri Oct  1 08:19:42 2010
@@ -145,6 +145,8 @@ class ChildNodeEntries implements Clonea
         }
     }
 
+    // The index may have changed because of changes by another session. Use remove(NodeId id)
+    // instead
     public ChildNodeEntry remove(Name nodeName, int index) {
         if (index < 1) {
             throw new IllegalArgumentException("index is 1-based");
@@ -224,7 +226,7 @@ class ChildNodeEntries implements Clonea
      * @return the removed entry or <code>null</code> if there is no such entry.
      */
     public ChildNodeEntry remove(ChildNodeEntry entry) {
-        return remove(entry.getName(), entry.getIndex());
+        return remove(entry.getId());
     }
 
     /**

Modified: jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java?rev=1003428&r1=1003427&r2=1003428&view=diff
==============================================================================
--- jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java (original)
+++ jackrabbit/branches/1.6/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java Fri Oct  1 08:19:42 2010
@@ -712,6 +712,14 @@ public class SharedItemStateManager
                 if (eventChannel != null) {
                     eventChannel.updatePrepared(this);
                 }
+                
+                try {
+                    validateHierarchy(local);
+                } catch (ItemStateException e) {
+                    throw e;
+                } catch (RepositoryException e) {
+                    throw new ItemStateException("Invalid hierarchy", e);
+                }
 
                 /* Push all changes from the local items to the shared items */
                 local.push();
@@ -1118,7 +1126,340 @@ public class SharedItemStateManager
         }
 
     }
+    
+    /**
+     * Validates the hierarchy consistency of the changes in the changelog.
+     * 
+     * @param changeLog
+     *            The local changelog the should be validated
+     * @throws ItemStateException
+     *             If the hierarchy changes are inconsistent.
+     * @throws RepositoryException
+     *             If the consistency could not be validated
+     * 
+     */
+    private void validateHierarchy(ChangeLog changeLog) throws ItemStateException, RepositoryException {
+
+        // Check the deleted node states
+        validateDeleted(changeLog);
+
+        // Check the added node states
+        validateAdded(changeLog);
+
+        // Check the modified node states
+        validateModified(changeLog);
+    }
+
+    /**
+     * Checks the parents and children of all deleted node states in the changelog.
+     * 
+     * @param changeLog
+     *            The local changelog the should be validated
+     * @throws ItemStateException
+     *             If the hierarchy changes are inconsistent.
+     */
+    private void validateDeleted(ChangeLog changeLog) throws ItemStateException {
+
+        // Check each deleted nodestate
+        for (Iterator it = changeLog.deletedStates(); it.hasNext(); ) {
+        	ItemState removedState = (ItemState) it.next();
+            if (removedState instanceof NodeState) {
+
+                // Get the next state
+                NodeState removedNodeState = (NodeState) removedState;
+                NodeId id = removedNodeState.getNodeId();
+
+                // Get and check the corresponding overlayed state
+                NodeState overlayedState = (NodeState) removedState.getOverlayedState();
+                if (overlayedState == null) {
+                    String message = "Unable to load persistent state for removed node " + id;
+                    overlayedState = (NodeState) SharedItemStateManager.this.getItemState(id);
+                    if (overlayedState == null) {
+                        log.error(message);
+                        throw new ItemStateException(message);
+                    }
+                }
+
+                // Check whether an version of this node has been restored
+                boolean addedAndRemoved = changeLog.has(removedNodeState.getId());
+                if (!addedAndRemoved) {
+
+                    // Check the old parent
+                    NodeId oldParentId = overlayedState.getParentId();
+                    if (changeLog.deleted(oldParentId)) {
+                        // parent has been deleted aswell
+                    } else if (changeLog.isModified(oldParentId)) {
+                        // the modified state will be check later on
+                    } else {
+                        String message = "Node with id " + id
+                                + " has been removed, but the parent node isn't part of the changelog " + oldParentId;
+                        log.error(message);
+                        throw new ItemStateException(message);
+                    }
+
+                    // Get the original list of child ids
+                    for (Iterator it2 = overlayedState.getChildNodeEntries().iterator(); it2.hasNext(); ) {
+                    	ChildNodeEntry entry = (ChildNodeEntry) it2.next();
+                        // Check the next child
+                        NodeId childId = entry.getId();
+
+                        if (changeLog.deleted(childId)) {
+                            // child has been deleted aswell
+                        } else if (changeLog.isModified(childId)) {
+
+                            // the modified state will be check later on
+                        } else {
+                            String message = "Node with id " + id
+                                    + " has been removed, but the old child node isn't part of the changelog "
+                                    + childId;
+                            log.error(message);
+                            throw new ItemStateException(message);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks the parents and children of all added node states in the changelog.
+     * 
+     * @param changeLog
+     *            The local changelog the should be validated
+     * @throws ItemStateException
+     *             If the hierarchy changes are inconsistent.
+     */
+    private void validateAdded(ChangeLog changeLog) throws ItemStateException {
+
+        // Check each added node
+        for (Iterator it = changeLog.addedStates(); it.hasNext(); ) {
+        	ItemState state = (ItemState) it.next();
+            if (state instanceof NodeState) {
+
+                // Get the next added node
+                NodeState addedNodeState = (NodeState) state;
+                NodeId id = addedNodeState.getNodeId();
+
+                // Check the parent
+                NodeId parentId = addedNodeState.getParentId();
+                if (changeLog.has(parentId)) { // Added or modified
+                    // the modified state will be check later on
+                    checkParent(changeLog, addedNodeState, parentId);
+                } else {
+                    String message = "Node with id " + id
+                            + " has been added, but the parent node isn't part of the changelog " + parentId;
+                    log.error(message);
+                    throw new ItemStateException(message);
+                }
+
+                // Check the children
+                for (Iterator it2 = addedNodeState.getChildNodeEntries().iterator(); it2.hasNext(); ) {
+                	ChildNodeEntry entry = (ChildNodeEntry) it2.next();
+                    // Get the next child
+                    NodeId childId = entry.getId();
+
+                    if (changeLog.has(childId)) {
+                        NodeState childState = (NodeState) changeLog.get(childId);
+                        checkParent(changeLog, childState, id);
+                        // the child state will be check later on
+
+                    } else {
+                        String message = "Node with id " + id
+                                + " has been added, but the child node isn't part of the changelog " + childId;
+                        log.error(message);
+                        throw new ItemStateException(message);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks the parents and children of all modified node states in the changelog.
+     * 
+     * @param changeLog
+     *            The local changelog the should be validated
+     * @throws ItemStateException
+     *             If the hierarchy changes are inconsistent.
+     */
+    private void validateModified(ChangeLog changeLog) throws ItemStateException, RepositoryException {
+
+        // Check all modified nodes
+        for (Iterator it = changeLog.modifiedStates(); it.hasNext(); ) {
+        	ItemState state = (ItemState) it.next();
+            if (state instanceof NodeState) {
 
+                // Check the next node
+                NodeState modifiedNodeState = (NodeState) state;
+                NodeId id = modifiedNodeState.getNodeId();
+
+                // Check whether to overlayed state is present for determining diffs
+                NodeState overlayedState = (NodeState) modifiedNodeState.getOverlayedState();
+                if (overlayedState == null) {
+                    String message = "Unable to load persistent state for modified node " + id;
+                    log.error(message);
+                    throw new ItemStateException(message);
+                }
+
+                // Check the parent
+                NodeId parentId = modifiedNodeState.getParentId();
+                NodeId oldParentId = overlayedState.getParentId();
+
+                // The parent should not be deleted
+                if (parentId != null && changeLog.deleted(parentId)) {
+                    String message = "Parent of node with id " + id + " has been deleted";
+                    log.error(message);
+                    throw new ItemStateException(message);
+                }
+
+                if (parentId != null && changeLog.has(parentId)) {
+                    checkParent(changeLog, modifiedNodeState, parentId);
+                }
+
+                // Check whether this node is the root node
+                if (parentId == null && oldParentId == null) {
+                    // The root node can be ignored
+
+                } else if (!parentId.equals(oldParentId)) {
+
+                    // This node has been moved, check whether the parent has been modified aswell
+                    if (changeLog.has(parentId)) {
+                        checkParent(changeLog, modifiedNodeState, parentId);
+                    } else if (!isShareable(modifiedNodeState)) {
+                        String message = "New parent of node " + id + " is not present in the changelog " + id;
+                        log.error(message);
+                        throw new ItemStateException(message);
+                    }
+
+                    // The old parent must be modified or deleted
+                    if (!changeLog.isModified(oldParentId) && !changeLog.deleted(oldParentId)) {
+                        String message = "Node with id " + id
+                                + " has been move, but the original parent is not part of the changelog: "
+                                + oldParentId;
+                        log.error(message);
+                        throw new ItemStateException(message);
+                    }
+                }
+
+                // Check all assigned children
+                for (Iterator it2 = modifiedNodeState.getChildNodeEntries().iterator(); it2.hasNext(); ) {
+                	ChildNodeEntry entry = (ChildNodeEntry) it2.next(); 
+
+                    NodeId childId = entry.getId();
+
+                    // Check whether this node has a deleted childid
+                    if (changeLog.deleted(childId) && !changeLog.has(childId)) { // Versionable
+                        String message = "Node with id " + id + " has a deleted childid: " + childId;
+                        log.error(message);
+                        throw new ItemStateException(message);
+                    }
+
+                    if (changeLog.has(childId)) {
+                        NodeState childState = (NodeState) changeLog.get(childId);
+                        checkParent(changeLog, childState, id);
+                    }
+                }
+
+                // Check all children the have been added
+                for (Iterator it2 = modifiedNodeState.getAddedChildNodeEntries().iterator(); it2.hasNext(); ) {
+                	ChildNodeEntry entry = (ChildNodeEntry) it2.next();
+                    NodeId childId = entry.getId();
+                    if (!changeLog.has(childId)) {
+                        String message = "ChildId " + childId + " has been added to parent " + id
+                                + ", but is not present in the changelog";
+                        log.error(message);
+                        throw new ItemStateException(message);
+                    }
+                }
+
+                // Check all children the have been moved or removed
+                for (Iterator it2 = modifiedNodeState.getRemovedChildNodeEntries().iterator(); it2.hasNext(); ) {
+                	ChildNodeEntry entry = (ChildNodeEntry) it2.next();
+                    NodeId childId = entry.getId();
+                    if (!changeLog.isModified(childId) && !changeLog.deleted(childId)) {
+                        String message = "Child node entry with id " + childId
+                                + " has been removed, but is not present in the changelog";
+                        log.error(message);
+                        throw new ItemStateException(message);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Check the consistency of a parent/child relationship.
+     * 
+     * @param changeLog
+     *            The changelog to check
+     * @param childState
+     *            The id of the node for which the parent/child relationship should be validated.
+     * @param expectedParent
+     *            The expected parent id of the child node.
+     * @throws ItemStateException
+     *             If a inconsistency has been detected.
+     */
+    void checkParent(ChangeLog changeLog, NodeState childState, NodeId expectedParent) throws ItemStateException {
+
+        // Check whether the the changelog contains an entry for the parent aswell.
+        NodeId parentId = childState.getParentId();
+        if (!parentId.equals(expectedParent)) {
+            Set sharedSet = childState.getSharedSet();
+            if (sharedSet.contains(expectedParent)) {
+                return;
+            }
+            String message = "Child node has another parent id " + parentId + ", expected " + expectedParent;
+            log.error(message);
+            throw new ItemStateException(message);
+        }
+
+        if (!changeLog.has(parentId)) {
+            String message = "Parent not part of changelog";
+            log.error(message);
+            throw new ItemStateException(message);
+        }
+
+        // Get the parent from the changelog
+        NodeState parent = (NodeState) changeLog.get(parentId);
+
+        // Get and check the child node entry from the parent
+        NodeId childId = childState.getNodeId();
+        ChildNodeEntry childNodeEntry = parent.getChildNodeEntry(childId);
+        if (childNodeEntry == null) {
+            String message = "Child not present in parent";
+            log.error(message);
+            throw new ItemStateException(message);
+        }
+    }
+
+    /**
+     * Determines whether the specified node is <i>shareable</i>, i.e. whether the mixin type <code>mix:shareable</code>
+     * is either directly assigned or indirectly inherited.
+     * 
+     * @param state
+     *            node state to check
+     * @return true if the specified node is <i>shareable</i>, false otherwise.
+     * @throws RepositoryException
+     *             if an error occurs
+     */
+    private boolean isShareable(NodeState state) throws RepositoryException {
+        // shortcut: check some wellknown built-in types first
+        Name primary = state.getNodeTypeName();
+        Set mixins = state.getMixinTypeNames();
+        if (mixins.contains(NameConstants.MIX_SHAREABLE)) {
+            return true;
+        }
+
+        try {
+            EffectiveNodeType type = ntReg.getEffectiveNodeType(primary, mixins);
+            return type.includesNodeType(NameConstants.MIX_SHAREABLE);
+        } catch (NodeTypeConflictException ntce) {
+            String msg = "internal error: failed to build effective node type for node " + state.getNodeId();
+            log.debug(msg);
+            throw new RepositoryException(msg, ntce);
+        }
+    }
+    
     /**
      * Begin update operation. This will return an object that can itself be
      * ended/canceled.

Added: jackrabbit/branches/1.6/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrencyTest3.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/1.6/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrencyTest3.java?rev=1003428&view=auto
==============================================================================
--- jackrabbit/branches/1.6/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrencyTest3.java (added)
+++ jackrabbit/branches/1.6/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrencyTest3.java Fri Oct  1 08:19:42 2010
@@ -0,0 +1,204 @@
+/*
+ * 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.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+
+public class ConcurrencyTest3 extends TestCase {
+
+    private static final int NUM_ITERATIONS = 1;
+
+    private static final int NUM_THREADS = 5;
+
+    private File repoDescriptor;
+
+    private File repoHome;
+
+    private RepositoryImpl repository;
+
+    public void setUp() throws IOException, RepositoryException {
+        File baseDir = new File(System.getProperty("basedir", "."));
+        File repoBaseDir = new File(baseDir, "target/corruption-test3");
+        FileUtils.deleteQuietly(repoBaseDir);
+
+        repoDescriptor = new File(repoBaseDir, "repository.xml");
+        repoHome = new File(repoBaseDir, "repository");
+        repoHome.mkdirs();
+
+        File repositoryDescriptor = new File(baseDir, "src/test/repository/repository.xml");
+        FileUtils.copyFile(repositoryDescriptor, repoDescriptor);
+    }
+
+    public void tearDown() throws IOException, InterruptedException {
+        FileUtils.deleteQuietly(repoHome.getParentFile());
+    }
+
+    /**
+     * Runs the test.
+     */
+    public void testConcurrentWritingSessions() throws Exception {
+
+        startRepository();
+
+        // Create test root node
+        Session session = login();
+        Node testRoot = session.getRootNode().addNode("test");
+        Node testRoot2 = session.getRootNode().addNode("test2");
+        testRoot.addMixin("mix:referenceable");
+        session.save();
+
+        for (int i = 0; i < NUM_ITERATIONS; i++) {
+            JcrTestThread[] threads = new JcrTestThread[NUM_THREADS];
+            Session[] sessions = new Session[NUM_THREADS];
+            for (int j = 0; j < threads.length; j++) {
+                // create new session and a new thread
+                Session threadSession = login();
+                JcrTestThread thread = new JcrTestThread(testRoot.getUUID(), threadSession);
+                thread.setName("Iteration " + i + " - Thread " + j);
+                thread.start();
+                threads[j] = thread;
+                sessions[j] = threadSession;
+            }
+            for (int j = 0; j < threads.length; j++) {
+                if (threads[j] != null) {
+                    threads[j].join();
+                }
+            }
+            for (int j = 0; j < sessions.length; j++) {
+                if (sessions[j] != null) {
+                    sessions[j].logout();
+                }
+            }
+        }
+
+        session.logout();
+        stopRepository();
+
+        // Restart with an empty index, scan and delete test root node
+        deleteIndex();
+        startRepository();
+        session = login();
+        testRoot = session.getRootNode().getNode("test");
+        testRoot.addNode("new node");
+        session.save();
+        scan(testRoot);
+        testRoot.remove();
+        session.save();
+        session.logout();
+        stopRepository();
+    }
+
+    private void startRepository() throws RepositoryException {
+        repository =
+            RepositoryImpl.create(RepositoryConfig.create(repoDescriptor.getAbsolutePath(), repoHome
+                .getAbsolutePath()));
+    }
+
+    private Session login() throws RepositoryException {
+        return repository.login(new SimpleCredentials("admin", "admin".toCharArray()), null);
+    }
+
+    private void stopRepository() throws RepositoryException {
+        repository.shutdown();
+    }
+
+    private void deleteIndex() throws IOException {
+        FileUtils.deleteDirectory(new File(repoHome, "workspaces/default/index"));
+    }
+
+    private void scan(Node node) throws RepositoryException {
+        // System.err.println(node.getName() + " - " + node.getPath());
+        for (NodeIterator it = node.getNodes(); it.hasNext();) {
+            Node child = it.nextNode();
+            scan(child);
+        }
+    }
+
+    class JcrTestThread extends Thread {
+
+        private String testRootUuid;
+
+        private Session jcrSession;
+
+        private ArrayList nodes = new ArrayList();
+
+        private int RUN_SIZE = 100;
+
+        private int ACTION_SIZE = 1000;
+
+        JcrTestThread(String uuid, Session session) throws RepositoryException {
+            testRootUuid = uuid;
+            jcrSession = session;
+        }
+
+        public void run() {
+            outer: for (int i = 0; i < RUN_SIZE; i++) {
+                for (int j = 0; j < ACTION_SIZE; j++) {
+                    int random = (int) Math.floor(2 * Math.random());
+                    if (random == 0) {
+                        try {
+                            // Add a node called "P"
+                            Node addedNode = jcrSession.getNodeByUUID(testRootUuid).addNode("P");
+                            addedNode.addMixin("mix:referenceable");
+                            nodes.add(addedNode.getUUID());
+                            
+                        } catch (RepositoryException ignore) {
+                        }
+                    } else {
+                        if (nodes.size() > 0) {
+                            int randomIndex = (int) Math.floor(nodes.size() * Math.random());
+                            try {
+                                // Remove a random node we created within this
+                                // thread and within this session
+                                Node removeNode = jcrSession.getNodeByUUID((String) nodes.get(randomIndex));
+                                String path = removeNode.getPath();
+                                if (path.indexOf("test2") == -1) {
+                                    jcrSession.move(removeNode.getPath(), removeNode.getParent().getPath()
+                                            + "2/P");
+                                } else {
+                                    removeNode.remove();
+                                    nodes.remove(randomIndex);
+                                }
+                            } catch (RepositoryException ignore) {
+                                System.err.println(" 1 " + ignore.toString());
+                            }
+                        }
+                    }
+                }
+                try {
+                    jcrSession.save();
+                } catch (RepositoryException e) {
+                    System.err.println(" 2 " + e.toString());
+                    break outer;
+                }
+            }
+        }
+    }
+}

Propchange: jackrabbit/branches/1.6/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrencyTest3.java
------------------------------------------------------------------------------
    svn:eol-style = native