You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2008/10/14 09:48:24 UTC

svn commit: r704361 [4/5] - in /jackrabbit/trunk: jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/o...

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java?rev=704361&r1=704360&r2=704361&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java Tue Oct 14 00:48:22 2008
@@ -73,6 +73,8 @@
 import javax.jcr.nodetype.NoSuchNodeTypeException;
 import javax.jcr.lock.LockException;
 import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
 import java.io.InputStream;
 
 /**
@@ -139,17 +141,15 @@
     public void save(ItemState state) throws ReferentialIntegrityException,
             InvalidItemStateException, RepositoryException {
         // shortcut, if no modifications are present
-        if (!hasPendingChanges()) {
+        if (!transientStateMgr.hasPendingChanges()) {
             return;
         }
-
         // collect the changes to be saved
-        ChangeLog changeLog = getChangeLog(state, true);
+        ChangeLog changeLog = transientStateMgr.getChangeLog(state, true);
         if (!changeLog.isEmpty()) {
             // only pass changelog if there are transient modifications available
             // for the specified item and its decendants.
             workspaceItemStateMgr.execute(changeLog);
-
             // remove states and operations just processed from the transient ISM
             transientStateMgr.dispose(changeLog);
             // now its save to clear the changeLog
@@ -168,20 +168,17 @@
      * another item needs to be canceled as well in another sub-tree.
      */
     public void undo(ItemState itemState) throws ConstraintViolationException, RepositoryException {
-        try {
-            ChangeLog changeLog = getChangeLog(itemState, false);
-            if (!changeLog.isEmpty()) {
-                // let changelog revert all changes
-                changeLog.undo();
-                // remove transient states and related operations from the t-statemanager
-                transientStateMgr.dispose(changeLog);
-                changeLog.reset();
-            }
-        } catch (InvalidItemStateException e) {
-            // should never get here
-            String msg = "Unable to undo item.";
-            log.debug(msg);
-            throw new RepositoryException(e);
+        // short cut
+        if (!transientStateMgr.hasPendingChanges()) {
+            return;
+        }
+        ChangeLog changeLog = transientStateMgr.getChangeLog(itemState, false);
+        if (!changeLog.isEmpty()) {
+            // let changelog revert all changes
+            changeLog.undo();
+            // remove transient states and related operations from the t-statemanager
+            transientStateMgr.dispose(changeLog);
+            changeLog.reset();
         }
     }
 
@@ -262,7 +259,8 @@
         NodeState parent = operation.getParentState();
         ItemDefinitionProvider defProvider = mgrProvider.getItemDefinitionProvider();
         QNodeDefinition def = defProvider.getQNodeDefinition(parent.getAllNodeTypeNames(), operation.getNodeName(), operation.getNodeTypeName());
-        addNodeState(parent, operation.getNodeName(), operation.getNodeTypeName(), operation.getUuid(), def, options);
+        List newStates = addNodeState(parent, operation.getNodeName(), operation.getNodeTypeName(), operation.getUuid(), def, options);
+        operation.addedState(newStates);
 
         transientStateMgr.addOperation(operation);
     }
@@ -336,11 +334,9 @@
             | ItemStateValidator.CHECK_VERSIONING
             | ItemStateValidator.CHECK_CONSTRAINTS;
         removeItemState(state, options);
-        // unless new state got removed remember operation and mark parent modified.
-        if (!Status.isTerminal(state.getStatus())) {
-            transientStateMgr.addOperation(operation);
-            operation.getParentState().markModified();
-        }
+        
+        transientStateMgr.addOperation(operation);
+        operation.getParentState().markModified();
     }
 
     /**
@@ -351,11 +347,12 @@
         // NOTE: nodestate is only modified upon save of the changes!
         Name[] mixinNames = operation.getMixinNames();
         NodeState nState = operation.getNodeState();
-        NodeEntry nEntry = (NodeEntry) nState.getHierarchyEntry();
+        NodeEntry nEntry = nState.getNodeEntry();
 
-        // new array of mixinNames to be set on the nodestate (and corresponding property state)
+        // assert the existence of the property entry and set the array of
+        // mixinNames to be set on the corresponding property state
         PropertyEntry mixinEntry = nEntry.getPropertyEntry(NameConstants.JCR_MIXINTYPES);
-        if (mixinNames != null && mixinNames.length > 0) {
+        if (mixinNames.length > 0) {
             // update/create corresponding property state
             if (mixinEntry != null) {
                 // execute value of existing property
@@ -372,22 +369,16 @@
             }
             nState.markModified();
             transientStateMgr.addOperation(operation);
-        } else {
+        } else if (mixinEntry != null) {
             // remove the jcr:mixinTypes property state if already present
-            if (mixinEntry != null) {
-                PropertyState pState = mixinEntry.getPropertyState();
-                boolean newMixinState = pState.getStatus() == Status.NEW;
-                int options = ItemStateValidator.CHECK_LOCK | ItemStateValidator.CHECK_VERSIONING;
-                removeItemState(pState, options);
-                // only added the remove-mixin operation if it doesn't revert
-                // a previous 'add-mixin' (which has been removed automatically
-                // upon notification of removing the prop-state).
-                if (!newMixinState) {
-                    nState.markModified();
-                    transientStateMgr.addOperation(operation);
-                }
-            }
-        }
+            PropertyState pState = mixinEntry.getPropertyState();
+            boolean newMixinState = pState.getStatus() == Status.NEW;
+            int options = ItemStateValidator.CHECK_LOCK | ItemStateValidator.CHECK_VERSIONING;
+            removeItemState(pState, options);
+
+            nState.markModified();
+            transientStateMgr.addOperation(operation);
+        } // else: empty Name array and no mixin-prop-entry (should not occur)
     }
 
     /**
@@ -538,40 +529,6 @@
     //--------------------------------------------< Internal State Handling >---
     /**
      *
-     * @param itemState
-     * @param throwOnStale Throws InvalidItemStateException if either the given
-     * <code>ItemState</code> or any of its decendants is stale and the flag is true.
-     * @return
-     * @throws InvalidItemStateException if a stale <code>ItemState</code> is
-     * encountered while traversing the state hierarchy. The <code>changeLog</code>
-     * might have been populated with some transient item states. A client should
-     * therefore not reuse the <code>changeLog</code> if such an exception is thrown.
-     * @throws RepositoryException if <code>state</code> is a new item state.
-     */
-    private ChangeLog getChangeLog(ItemState itemState, boolean throwOnStale) throws InvalidItemStateException, ConstraintViolationException, RepositoryException {
-        // build changelog for affected and decendant states only
-        ChangeLog changeLog = new ChangeLog(itemState);
-        // fail-fast test: check status of this item's state
-        if (itemState.getStatus() == Status.NEW) {
-            String msg = "Cannot save/revert an item with status NEW (" +itemState+ ").";
-            log.debug(msg);
-            throw new RepositoryException(msg);
-        }
-        if (throwOnStale && Status.isStale(itemState.getStatus())) {
-            String msg =  "Attempt to save/revert an item, that has been externally modified (" +itemState+ ").";
-            log.debug(msg);
-            throw new InvalidItemStateException(msg);
-        }
-        // collect transient/stale states that should be persisted or reverted
-        itemState.getHierarchyEntry().collectStates(changeLog, throwOnStale);
-
-        changeLog.collectOperations(transientStateMgr.getOperations());
-        changeLog.checkIsSelfContained();
-        return changeLog;
-    }
-
-    /**
-     *
      * @param parent
      * @param propertyName
      * @param propertyType
@@ -589,17 +546,17 @@
      * @throws VersionException
      * @throws RepositoryException
      */
-    private void addPropertyState(NodeState parent, Name propertyName,
+    private PropertyState addPropertyState(NodeState parent, Name propertyName,
                                   int propertyType, QValue[] values,
                                   QPropertyDefinition pDef, int options)
             throws LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
 
         validator.checkAddProperty(parent, propertyName, pDef, options);
         // create property state
-        transientStateMgr.createNewPropertyState(propertyName, parent, pDef, values, propertyType);
+        return transientStateMgr.createNewPropertyState(propertyName, parent, pDef, values, propertyType);
     }
 
-    private void addNodeState(NodeState parent, Name nodeName, Name nodeTypeName,
+    private List addNodeState(NodeState parent, Name nodeName, Name nodeTypeName,
                               String uuid, QNodeDefinition definition, int options)
             throws RepositoryException, ConstraintViolationException, AccessDeniedException,
             UnsupportedRepositoryOperationException, NoSuchNodeTypeException,
@@ -623,14 +580,17 @@
             }
         }
 
+        List addedStates = new ArrayList();
+
         // create new nodeState. NOTE, that the uniqueID is not added to the
-        // state for consistency between 'addNode' and importXML // TODO review
+        // state for consistency between 'addNode' and importXML
         NodeState nodeState = transientStateMgr.createNewNodeState(nodeName, null, nodeTypeName, definition, parent);
+        addedStates.add(nodeState);
         if (uuid != null) {
             QValue[] value = getQValues(uuid, qValueFactory);
             ItemDefinitionProvider defProvider = mgrProvider.getItemDefinitionProvider();
             QPropertyDefinition pDef = defProvider.getQPropertyDefinition(NameConstants.MIX_REFERENCEABLE, NameConstants.JCR_UUID, PropertyType.STRING, false);
-            addPropertyState(nodeState, NameConstants.JCR_UUID, PropertyType.STRING, value, pDef, 0);
+            addedStates.add(addPropertyState(nodeState, NameConstants.JCR_UUID, PropertyType.STRING, value, pDef, 0));
         }
 
         // add 'auto-create' properties defined in node type
@@ -642,7 +602,7 @@
                 if (autoValue != null) {
                     int propOptions = ItemStateValidator.CHECK_NONE;
                     // execute 'addProperty' without adding operation.
-                    addPropertyState(nodeState, pd.getName(), pd.getRequiredType(), autoValue, pd, propOptions);
+                    addedStates.add(addPropertyState(nodeState, pd.getName(), pd.getRequiredType(), autoValue, pd, propOptions));
                 }
             }
         }
@@ -653,8 +613,9 @@
             QNodeDefinition nd = nda[i];
             // execute 'addNode' without adding the operation.
             int opt = ItemStateValidator.CHECK_LOCK | ItemStateValidator.CHECK_COLLISION;
-            addNodeState(nodeState, nd.getName(), nd.getDefaultPrimaryType(), null, nd, opt);
+            addedStates.addAll(addNodeState(nodeState, nd.getName(), nd.getDefaultPrimaryType(), null, nd, opt));
         }
+        return addedStates;
     }
 
     private void removeItemState(ItemState itemState, int options) throws RepositoryException {

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/Status.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/Status.java?rev=704361&r1=704360&r2=704361&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/Status.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/Status.java Tue Oct 14 00:48:22 2008
@@ -63,7 +63,7 @@
     public static final int MODIFIED = 7;
 
     /**
-     * a new state was deleted and is now 'removed'
+     * a new state was removed and is now 'removed'
      * or an existing item has been removed by a workspace operation or
      * by an external modification.
      */
@@ -137,7 +137,7 @@
      *
      * @param status the status to check.
      * @return <code>true</code> if <code>status</code> indicates that an item
-     *         state is stale.
+     *         state is transiently modified.
      */
     public static boolean isTransient(int status) {
         return status == EXISTING_MODIFIED || status == EXISTING_REMOVED || status == NEW;
@@ -184,10 +184,10 @@
                 break;
             case STALE_MODIFIED:
             case STALE_DESTROYED:
-                isValid = (oldStatus == EXISTING_MODIFIED);
+                isValid = (oldStatus == EXISTING_MODIFIED || oldStatus == EXISTING_REMOVED || oldStatus == STALE_MODIFIED);
                 break;
             case REMOVED:
-                // external removal always possible -> getNewStatus(int, int)
+                // removal always possible -> getNewStatus(int, int)
                 isValid = true;
                 break;
             case MODIFIED:
@@ -220,7 +220,6 @@
                     // temporarily set the state to MODIFIED in order to inform listeners.
                     newStatus = Status.MODIFIED;
                 } else if (oldStatus == Status.EXISTING_MODIFIED) {
-                    // TODO: try to merge changes
                     newStatus = Status.STALE_MODIFIED;
                 } else {
                     // old status is EXISTING_REMOVED (or any other) => ignore.
@@ -229,7 +228,7 @@
                 }
                 break;
             case Status.REMOVED:
-                if (oldStatus == Status.EXISTING_MODIFIED) {
+                if (oldStatus == Status.EXISTING_MODIFIED || oldStatus == Status.STALE_MODIFIED) {
                     newStatus = Status.STALE_DESTROYED;
                 } else {
                     // applies both to NEW or to any other status

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientISFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientISFactory.java?rev=704361&r1=704360&r2=704361&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientISFactory.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientISFactory.java Tue Oct 14 00:48:22 2008
@@ -23,6 +23,7 @@
 import org.apache.jackrabbit.spi.PropertyId;
 import org.apache.jackrabbit.spi.QPropertyDefinition;
 import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.QValue;
 import org.apache.jackrabbit.jcr2spi.hierarchy.NodeEntry;
 import org.apache.jackrabbit.jcr2spi.hierarchy.PropertyEntry;
 import org.apache.jackrabbit.jcr2spi.nodetype.ItemDefinitionProvider;
@@ -71,12 +72,10 @@
      * @inheritDoc
      * @see TransientItemStateFactory#createNewPropertyState(PropertyEntry, QPropertyDefinition)
      */
-    public PropertyState createNewPropertyState(PropertyEntry entry, QPropertyDefinition definition) {
-        PropertyState propState = new PropertyState(entry, this, definition, defProvider);
-
+    public PropertyState createNewPropertyState(PropertyEntry entry, QPropertyDefinition definition, QValue[] values, int propertyType) throws RepositoryException {
+        PropertyState propState = new PropertyState(entry, this, definition, defProvider, values, propertyType);
         // notify listeners that a property state has been created
         notifyCreated(propState);
-
         return propState;
     }
 

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateFactory.java?rev=704361&r1=704360&r2=704361&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateFactory.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateFactory.java Tue Oct 14 00:48:22 2008
@@ -21,6 +21,9 @@
 import org.apache.jackrabbit.jcr2spi.hierarchy.PropertyEntry;
 import org.apache.jackrabbit.spi.QNodeDefinition;
 import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.spi.QValue;
+
+import javax.jcr.RepositoryException;
 
 /**
  * <code>TransientItemStateFactory</code> extends the item state factory and
@@ -46,8 +49,12 @@
      *
      * @param entry
      * @param definition
+     * @param values
+     * @param propertyType
      * @return the created <code>PropertyState</code>.
      */
     public PropertyState createNewPropertyState(PropertyEntry entry,
-                                                QPropertyDefinition definition);
+                                                QPropertyDefinition definition,
+                                                QValue[] values, int propertyType)
+            throws RepositoryException;
 }

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateManager.java?rev=704361&r1=704360&r2=704361&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateManager.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateManager.java Tue Oct 14 00:48:22 2008
@@ -16,19 +16,26 @@
  */
 package org.apache.jackrabbit.jcr2spi.state;
 
-import org.apache.jackrabbit.jcr2spi.operation.Operation;
+import org.apache.commons.collections.iterators.IteratorChain;
+import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyEntry;
 import org.apache.jackrabbit.jcr2spi.hierarchy.NodeEntry;
+import org.apache.jackrabbit.jcr2spi.hierarchy.PropertyEntry;
+import org.apache.jackrabbit.jcr2spi.operation.Operation;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.QNodeDefinition;
 import org.apache.jackrabbit.spi.QPropertyDefinition;
 import org.apache.jackrabbit.spi.QValue;
-import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import javax.jcr.InvalidItemStateException;
 import javax.jcr.ItemExistsException;
 import javax.jcr.RepositoryException;
 import javax.jcr.nodetype.ConstraintViolationException;
+import java.util.Collection;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
 
 /**
  * <code>TransientItemStateManager</code> adds support for transient changes on
@@ -47,23 +54,40 @@
     private static final Logger log = LoggerFactory.getLogger(TransientItemStateManager.class);
 
     /**
-     * The change log which keeps track of changes and maintains hard references
-     * to changed item states.
+     * Added states
+     */
+    private final Set addedStates = new LinkedHashSet();
+
+    /**
+     * Modified states
+     */
+    private final Set modifiedStates = new LinkedHashSet();
+
+    /**
+     * Removed states
      */
-    private final ChangeLog changeLog;
+    private final Set removedStates = new LinkedHashSet();
+    /**
+     * Stale states
+     */
+    private final Set staleStates = new LinkedHashSet();
+
+    /**
+     * Set of operations
+     */
+    private Set operations = new LinkedHashSet();
 
     /**
      *
      */
     TransientItemStateManager() {
-        this.changeLog = new ChangeLog(null);
     }
 
     /**
      * @return the operations that have been recorded until now.
      */
     Iterator getOperations() {
-        return changeLog.getOperations();
+        return operations.iterator();
     }
 
     /**
@@ -73,14 +97,121 @@
      * @param operation
      */
     void addOperation(Operation operation) {
-        changeLog.addOperation(operation);
+        operations.add(operation);
     }
 
     /**
      * @return <code>true</code> if this transient ISM has pending changes.
      */
     boolean hasPendingChanges() {
-        return !changeLog.isEmpty();
+        return !operations.isEmpty();
+    }
+
+    /**
+     * Create the change log for the tree starting at <code>target</code>. This
+     * includes a  check if the ChangeLog to be created is totally 'self-contained'
+     * and independant; items within the scope of this update operation (i.e.
+     * below the target) must not have dependencies outside of this tree (e.g.
+     * moving a node requires that the target node including both old and new
+     * parents are saved).
+     *
+     * @param target
+     * @param throwOnStale Throws InvalidItemStateException if either the given
+     * <code>ItemState</code> or any of its decendants is stale and the flag is true.
+     * @return
+     * @throws InvalidItemStateException if a stale <code>ItemState</code> is
+     * encountered while traversing the state hierarchy. The <code>changeLog</code>
+     * might have been populated with some transient item states. A client should
+     * therefore not reuse the <code>changeLog</code> if such an exception is thrown.
+     * @throws RepositoryException if <code>state</code> is a new item state.
+     */
+    ChangeLog getChangeLog(ItemState target, boolean throwOnStale) throws InvalidItemStateException, ConstraintViolationException, RepositoryException {
+        // fail-fast test: check status of this item's state
+        if (target.getStatus() == Status.NEW) {
+            String msg = "Cannot save/revert an item with status NEW (" +target+ ").";
+            log.debug(msg);
+            throw new RepositoryException(msg);
+        }
+        if (throwOnStale && Status.isStale(target.getStatus())) {
+            String msg =  "Attempt to save/revert an item, that has been externally modified (" +target+ ").";
+            log.debug(msg);
+            throw new InvalidItemStateException(msg);
+        }
+
+        Set ops = new LinkedHashSet();
+        Set affectedStates = new LinkedHashSet();
+
+        HierarchyEntry he = target.getHierarchyEntry();
+        if (he.getParent() == null) {
+            // the root entry -> the complete change log can be used for
+            // simplicity. collecting ops, states can be omitted.
+            if (throwOnStale && !staleStates.isEmpty()) {
+                String msg = "Cannot save changes: States has been modified externally.";
+                log.debug(msg);
+                throw new InvalidItemStateException(msg);
+            } else {
+                affectedStates.addAll(staleStates);
+            }
+            ops.addAll(operations);
+            affectedStates.addAll(addedStates);
+            affectedStates.addAll(modifiedStates);
+            affectedStates.addAll(removedStates);
+        } else {
+            // not root entry:
+            // - check if there is a stale state in the scope (save only)
+            if (throwOnStale) {
+                for (Iterator it = staleStates.iterator(); it.hasNext();) {
+                    ItemState state = (ItemState) it.next();
+                    if (containedInTree(target, state)) {
+                        String msg = "Cannot save changes: States has been modified externally.";
+                        log.debug(msg);
+                        throw new InvalidItemStateException(msg);
+                    }
+                }
+            }
+            // - collect all affected states within the scope of save/undo
+            Iterator[] its = new Iterator[] {
+                    addedStates.iterator(),
+                    removedStates.iterator(),
+                    modifiedStates.iterator()
+            };
+            IteratorChain chain = new IteratorChain(its);
+            if (!throwOnStale) {
+                chain.addIterator(staleStates.iterator());
+            }
+            while (chain.hasNext()) {
+                ItemState state = (ItemState) chain.next();
+                if (containedInTree(target, state)) {
+                    affectedStates.add(state);
+                }
+            }
+            // - collect the set of operations and
+            //   check if the affected states listed by the operations are all
+            //   listed in the modified,removed or added states collected by this
+            //   changelog.
+            for (Iterator it = operations.iterator(); it.hasNext();) {
+                Operation op = (Operation) it.next();
+                Collection opStates = op.getAffectedItemStates();
+                for (Iterator osIt = opStates.iterator(); osIt.hasNext();) {
+                    ItemState state = (ItemState) osIt.next();
+                    if (affectedStates.contains(state)) {
+                        // operation needs to be included
+                        if (!affectedStates.containsAll(opStates)) {
+                            // incomplete changelog: need to save a parent as well
+                            String msg = "ChangeLog is not self contained.";
+                            throw new ConstraintViolationException(msg);
+                        }
+                        // no violation: add operation an stop iteration over
+                        // all affected states present in the operation.
+                        ops.add(op);
+                        break;
+                    }
+                }
+            }
+        }
+
+        ChangeLog cl = new ChangeLog(target, ops, affectedStates);
+        return cl;
     }
 
     /**
@@ -99,15 +230,14 @@
     NodeState createNewNodeState(Name nodeName, String uniqueID, Name nodeTypeName,
                                  QNodeDefinition definition, NodeState parent)
             throws RepositoryException {
-        NodeState nodeState = ((NodeEntry) parent.getHierarchyEntry()).addNewNodeEntry(nodeName, uniqueID, nodeTypeName, definition);
+        NodeEntry ne = ((NodeEntry) parent.getHierarchyEntry()).addNewNodeEntry(nodeName, uniqueID, nodeTypeName, definition);
         try {
             parent.markModified();
         } catch (RepositoryException e) {
-            nodeState.getHierarchyEntry().remove();
+            ne.remove();
             throw e;
         }
-
-        return nodeState;
+        return ne.getNodeState();
     }
 
     /**
@@ -129,15 +259,14 @@
             throws ItemExistsException, ConstraintViolationException, RepositoryException {
         // NOTE: callers must make sure, the property type is not 'undefined'
         NodeEntry nodeEntry = (NodeEntry) parent.getHierarchyEntry();
-        PropertyState propState = nodeEntry.addNewPropertyEntry(propName, definition);
+        PropertyEntry pe = nodeEntry.addNewPropertyEntry(propName, definition, values, propertyType);
         try {
-            propState.setValues(values, propertyType);
             parent.markModified();
         } catch (RepositoryException e) {
-            propState.getHierarchyEntry().remove();
+            pe.remove();
             throw e;
         }
-        return propState;
+        return pe.getPropertyState();
     }
 
     /**
@@ -145,19 +274,74 @@
      * transiently modified item states.
      */
     void dispose() {
-        changeLog.reset();
+        addedStates.clear();
+        modifiedStates.clear();
+        removedStates.clear();
+        staleStates.clear();
+        // also clear all operations
+        operations.clear();
     }
 
     /**
-     * Remove the states and operations listed in the changeLog from the
-     * internal changeLog.
+     * Remove the states and operations listed in the changeLog from internal
+     * list of modifications.
      *
      * @param subChangeLog
      */
     void dispose(ChangeLog subChangeLog) {
-        changeLog.removeAll(subChangeLog);
+        Set affectedStates = subChangeLog.getAffectedStates();
+        addedStates.removeAll(affectedStates);
+        modifiedStates.removeAll(affectedStates);
+        removedStates.removeAll(affectedStates);
+        staleStates.removeAll(affectedStates);
+
+        operations.removeAll(subChangeLog.getOperations());
+    }
+
+    /**
+     * A state has been removed. If the state is not a new state
+     * (not in the collection of added ones), then remove
+     * it from the modified states collection and add it to the
+     * removed states collection.
+     *
+     * @param state state that has been removed
+     */
+    private void removed(ItemState state) {
+        if (!addedStates.remove(state)) {
+            modifiedStates.remove(state);
+        }
+        removedStates.add(state);
     }
 
+   /**
+     *
+     * @param parent
+     * @param state
+     * @return
+     */
+    private static boolean containedInTree(ItemState parent, ItemState state) {
+        HierarchyEntry he = state.getHierarchyEntry();
+       HierarchyEntry pHe = parent.getHierarchyEntry();
+       // short cuts first
+       if (he == pHe || he.getParent() == pHe) {
+           return true;
+       }
+       if (!parent.isNode() || he == pHe.getParent()) {
+           return false;
+       }
+       // none of the simple cases: walk up hierarchy
+       HierarchyEntry pe = he.getParent();
+       while (pe != null) {
+           if (pe == pHe) {
+               return true;
+           }
+           pe = pe.getParent();
+       }
+
+       // state isn't descendant of 'parent'
+       return false;
+   }
+
     //-----------------------------------------< ItemStateLifeCycleListener >---
     /**
      * Depending on status of the given state adapt change log.
@@ -168,28 +352,77 @@
      * @see ItemStateLifeCycleListener#statusChanged(ItemState, int)
      */
     public void statusChanged(ItemState state, int previousStatus) {
-        if (changeLog.isEmpty()) {
-            return;
-        }
+        /*
+        Update the collections of states that were transiently modified.
+        NOTE: cleanup of operations is omitted here. this is expected to
+        occur upon {@link ChangeLog#save()} and {@link ChangeLog#undo()}.
+        External modifications in contrast that clash with transient modifications
+        render the corresponding states stale.
+        */
         switch (state.getStatus()) {
-            case Status.EXISTING:
+            case (Status.EXISTING):
+                switch (previousStatus) {
+                    case Status.EXISTING_MODIFIED:
+                        // was modified and got persisted or reverted
+                        modifiedStates.remove(state);
+                        break;
+                    case Status.EXISTING_REMOVED:
+                        // was transiently removed and is now reverted
+                        removedStates.remove(state);
+                        break;
+                    case Status.STALE_MODIFIED:
+                        // was modified and stale and is now reverted
+                        staleStates.remove(state);
+                        break;
+                    case Status.NEW:
+                        // was new and has been saved now
+                        addedStates.remove(state);
+                        break;
+                    //default:
+                        // INVALIDATED, MODIFIED ignore. no effect to transient modifications.
+                        // any other status change is invalid -> see Status#isValidStatusChange(int, int
+                }
+                break;
             case Status.EXISTING_MODIFIED:
-            case Status.EXISTING_REMOVED:
-            case Status.REMOVED:
-                changeLog.statusChanged(state, previousStatus);
+                // transition from EXISTING to EXISTING_MODIFIED
+                modifiedStates.add(state);
+                break;
+            case (Status.EXISTING_REMOVED):
+                // transition from EXISTING or EXISTING_MODIFIED to EXISTING_REMOVED
+                removed(state);
+                break;
+            case (Status.REMOVED):
+                switch (previousStatus) {
+                    case Status.EXISTING_REMOVED:
+                        // was transiently removed and removal was persisted.
+                        // -> ignore
+                        break;
+                    case Status.NEW:
+                        // a new entry was removed again: remember as removed
+                        // in order to keep the operations and the affected
+                        // states in sync
+                        removed(state);
+                        break;
+                }
                 break;
             case Status.STALE_DESTROYED:
             case Status.STALE_MODIFIED:
-                // state is now stale. keep in modified. wait until refreshed
+                /**
+                state is stale due to external modification -> move it to
+                the collection of stale item states.
+                validation omitted for only 'existing_modified' states can
+                become stale see {@link Status#isValidStatusChange(int, int)}
+                 */
+                modifiedStates.remove(state);
+                staleStates.add(state);
+                break;
             case Status.MODIFIED:
-                // MODIFIED is only possible on EXISTING states -> thus, there
-                // must not be any transient modifications for that state.
-                // we ignore it.
             case Status.INVALIDATED:
-                // -> nothing to do here.
+                // MODIFIED, INVALIDATED: ignore.
+                log.debug("Item " + state.getName() + " changed status from " + Status.getName(previousStatus) + " to " + Status.getName(state.getStatus()) + ".");
                 break;
             default:
-                log.error("ItemState has invalid status: " + state.getStatus());
+                log.error("ItemState "+ state.getName() + " has invalid status: " + state.getStatus());
         }
     }
 
@@ -200,7 +433,7 @@
     public void created(ItemState state) {
         // new state has been created
         if (state.getStatus() == Status.NEW) {
-            changeLog.added(state);
+            addedStates.add(state);
         }
     }
 }

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/WorkspaceItemStateFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/WorkspaceItemStateFactory.java?rev=704361&r1=704360&r2=704361&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/WorkspaceItemStateFactory.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/WorkspaceItemStateFactory.java Tue Oct 14 00:48:22 2008
@@ -145,7 +145,9 @@
     public PropertyState createDeepPropertyState(PropertyId propertyId, NodeEntry anyParent) throws ItemNotFoundException, RepositoryException {
         try {
             PropertyInfo info = service.getPropertyInfo(sessionInfo, propertyId);
-            return createDeepPropertyState(info, anyParent, null);
+            PropertyState propState = createDeepPropertyState(info, anyParent, null);
+            assertValidState(propState, info);
+            return propState;
         } catch (PathNotFoundException e) {
             throw new ItemNotFoundException(e.getMessage());
         }
@@ -210,7 +212,7 @@
                 // the given NodeEntry -> retrieve NodeState before executing
                 // validation check.
                 nodeState = createDeepNodeState(first, entry, infos);
-                assertMatchingPath(first, nodeState.getNodeEntry());
+                assertValidState(nodeState, first);
             } else {
                 // 'isDeep' == false -> the given NodeEntry must match to the
                 // first ItemInfo retrieved from the iterator.
@@ -261,9 +263,10 @@
             parent.setUniqueID(uniqueID);
         }
 
-        // now build the nodestate itself
-        NodeState state = new NodeState(entry, info, this, definitionProvider);
-        state.setMixinTypeNames(info.getMixins());
+        if (Status.isTransient(entry.getStatus()) || Status.isStale(entry.getStatus())) {
+            log.debug("Node has pending changes; omit resetting the state.");
+            return entry.getNodeState();
+        }
 
         // update NodeEntry from the information present in the NodeInfo (prop entries)
         List propNames = new ArrayList();
@@ -273,7 +276,7 @@
             propNames.add(propertyName);
         }
         try {
-            entry.addPropertyEntries(propNames);
+            entry.setPropertyEntries(propNames);
         } catch (ItemExistsException e) {
             // should not get here
             log.warn("Internal error", e);
@@ -286,8 +289,18 @@
             entry.setNodeEntries(childInfos);
         }
 
-        notifyCreated(state);
-        return state;
+        // now build or update the nodestate itself
+        NodeState tmp = new NodeState(entry, info, this, definitionProvider);
+        entry.setItemState(tmp);
+
+        NodeState nState = entry.getNodeState();
+        if (nState == tmp) {
+            // tmp state was used as resolution for the given entry i.e. the
+            // entry was not available before. otherwise the 2 states were
+            // merged. see HierarchyEntryImpl#setItemState
+            notifyCreated(nState);
+        }
+        return nState;
     }
 
     /**
@@ -298,7 +311,8 @@
      * @param entry
      * @return the new <code>PropertyState</code>.
      */
-    private PropertyState createPropertyState(PropertyInfo info, PropertyEntry entry) {
+    private PropertyState createPropertyState(PropertyInfo info, PropertyEntry entry)
+            throws RepositoryException {
         // make sure uuid part of id is correct
         String uniqueID = info.getId().getUniqueID();
         if (uniqueID != null) {
@@ -307,11 +321,23 @@
             parent.setUniqueID(uniqueID);
         }
 
-        // build the PropertyState
-        PropertyState state = new PropertyState(entry, info, this, definitionProvider);
-
-        notifyCreated(state);
-        return state;
+        if (Status.isTransient(entry.getStatus()) || Status.isStale(entry.getStatus())) {
+            log.debug("Property has pending changes; omit resetting the state.");
+            return entry.getPropertyState();
+        }
+
+        // now build or update the nodestate itself
+        PropertyState tmp = new PropertyState(entry, info, this, definitionProvider);
+        entry.setItemState(tmp);
+
+        PropertyState pState = entry.getPropertyState();
+        if (pState == tmp) {
+            // tmp state was used as resolution for the given entry i.e. the
+            // entry was not available before. otherwise the 2 states were
+            // merged. see HierarchyEntryImpl#setItemState
+            notifyCreated(pState);
+        }
+        return pState;
     }
 
     /**
@@ -326,10 +352,15 @@
             // node for nodeId exists -> build missing entries in hierarchy
             // Note, that the path contained in NodeId does not reveal which
             // entries are missing -> calculate relative path.
-            Path anyParentPath = anyParent.getPath();
+            Path anyParentPath = anyParent.getWorkspacePath();
             Path relPath = anyParentPath.computeRelativePath(info.getPath());
             Path.Element[] missingElems = relPath.getElements();
 
+            if (startsWithIllegalElement(missingElems)) {
+                log.error("Relative path to NodeEntry starts with illegal element -> ignore NodeInfo with path " + info.getPath());
+                return null;
+            }
+
             NodeEntry entry = anyParent;
             for (int i = 0; i < missingElems.length; i++) {
                 Name name = missingElems[i].getName();
@@ -360,8 +391,16 @@
             Path anyParentPath = anyParent.getWorkspacePath();
             Path relPath = anyParentPath.computeRelativePath(info.getPath());
             Path.Element[] missingElems = relPath.getElements();
-            NodeEntry entry = anyParent;
 
+            // make sure the missing elements don't start with . or .. in which
+            // case the info is not within the tree as it is expected
+            // (see also JCR-1797)
+            if (startsWithIllegalElement(missingElems)) {
+                log.error("Relative path to PropertyEntry starts with illegal element -> ignore PropertyInfo with path " + info.getPath());
+                return null;
+            }
+
+            NodeEntry entry = anyParent;
             int i = 0;
             // NodeEntries except for the very last 'missingElem'
             while (i < missingElems.length - 1) {
@@ -402,6 +441,23 @@
     }
 
     /**
+     * Validation check: make sure the state is not null (was really created)
+     * and matches with the specified ItemInfo (path).
+     *
+     * @param state
+     * @param info
+     * @throws ItemNotFoundException
+     * @throws RepositoryException
+     */
+    private static void assertValidState(ItemState state, ItemInfo info)
+            throws ItemNotFoundException, RepositoryException {
+        if (state == null) {
+            throw new ItemNotFoundException("HierarchyEntry does not belong to any existing ItemInfo. No ItemState was created.");
+        }
+        assertMatchingPath(info, state.getHierarchyEntry());
+    }
+
+    /**
      * Validation check: Path of the given ItemInfo must match to the Path of
      * the HierarchyEntry. This is required for Items that are identified by
      * a uniqueID that may move within the hierarchy upon restore or clone.
@@ -421,6 +477,22 @@
     }
 
     /**
+     * Returns true if the given <code>missingElems</code> start with a parent (..),
+     * a current (.) or the root element, in which case the info is not within
+     * the tree as it is expected.
+     * See also #JCR-1797 for the corresponding enhancement request.
+     *
+     * @param missingElems
+     * @return
+     */
+    private static boolean startsWithIllegalElement(Path.Element[] missingElems) {
+        if (missingElems.length > 0) {
+            return !missingElems[0].denotesName();
+        }
+        return false;
+    }
+
+    /**
      * @param entry
      * @param degree
      * @return the ancestor entry at the specified degree.

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/util/LogUtil.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/util/LogUtil.java?rev=704361&r1=704360&r2=704361&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/util/LogUtil.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/util/LogUtil.java Tue Oct 14 00:48:22 2008
@@ -65,9 +65,8 @@
         try {
             return safeGetJCRPath(itemState.getHierarchyEntry().getPath(), pathResolver);
         } catch (RepositoryException e) {
-            ItemId id = itemState.getId();
-            log.error("failed to convert " + id + " to JCR path.");
-            return id.toString();
+            log.error("failed to convert " + itemState.toString() + " to JCR path.");
+            return itemState.toString();
         }
     }
 

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/util/StateUtility.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/util/StateUtility.java?rev=704361&r1=704360&r2=704361&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/util/StateUtility.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/util/StateUtility.java Tue Oct 14 00:48:22 2008
@@ -16,13 +16,11 @@
  */
 package org.apache.jackrabbit.jcr2spi.util;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.jcr2spi.hierarchy.NodeEntry;
+import org.apache.jackrabbit.jcr2spi.state.NodeState;
 import org.apache.jackrabbit.jcr2spi.state.PropertyState;
 import org.apache.jackrabbit.jcr2spi.state.Status;
-import org.apache.jackrabbit.jcr2spi.state.NodeState;
-import org.apache.jackrabbit.jcr2spi.hierarchy.NodeEntry;
+import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.QValue;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
 
@@ -33,8 +31,6 @@
  */
 public class StateUtility {
 
-    private static Logger log = LoggerFactory.getLogger(StateUtility.class);
-
     /**
      *
      * @param ps

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/SessionImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/SessionImporter.java?rev=704361&r1=704360&r2=704361&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/SessionImporter.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/SessionImporter.java Tue Oct 14 00:48:22 2008
@@ -453,19 +453,14 @@
             Operation an = AddNode.create(parent, nodeInfo.getName(), ntName, nodeInfo.getUUID());
             stateMgr.execute(an);
             // retrieve id of state that has been created during execution of AddNode
-            NodeState childState;
-            List cne = parent.getNodeEntry().getNodeEntries(nodeInfo.getName());
-            if (def.allowsSameNameSiblings()) {
-                // TODO TOBEFIXED find proper solution. problem with same-name-siblings
-                childState = ((NodeEntry)cne.get(cne.size()-1)).getNodeState();
-            } else {
-                childState = ((NodeEntry)cne.get(0)).getNodeState();
-            }
+            NodeState childState = (NodeState) ((AddNode) an).getAddedStates().get(0);
 
             // and set mixin types
-            // TODO: missing validation
-            Operation sm = SetMixin.create(childState, nodeInfo.getMixinNames());
-            stateMgr.execute(sm);
+            Name[] mixinNames = nodeInfo.getMixinNames();
+            if (mixinNames != null && mixinNames.length > 0) {
+                Operation sm = SetMixin.create(childState, nodeInfo.getMixinNames());
+                stateMgr.execute(sm);
+            }
             return childState;
         }
     }

Added: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/AddPropertyTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/AddPropertyTest.java?rev=704361&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/AddPropertyTest.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/AddPropertyTest.java Tue Oct 14 00:48:22 2008
@@ -0,0 +1,164 @@
+/*
+ * 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.jcr2spi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.apache.jackrabbit.jcr2spi.state.Status;
+
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+import javax.jcr.Property;
+import javax.jcr.Node;
+
+/** <code>AddPropertyTest</code>... */
+public class AddPropertyTest extends AbstractJCRTest {
+
+    private static Logger log = LoggerFactory.getLogger(AddPropertyTest.class);
+
+    private Node testNode;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        testNode = testRootNode.addNode(nodeName1);
+        testNode.setProperty(propertyName1, "existingProp");
+        testRootNode.save();
+    }
+
+    private static void assertItemStatus(Item item, int status) throws NotExecutableException {
+        if (!(item instanceof ItemImpl)) {
+            throw new NotExecutableException("org.apache.jackrabbit.jcr2spi.ItemImpl expected");
+        }
+        int st = ((ItemImpl) item).getItemState().getStatus();
+        assertEquals("Expected status to be " + Status.getName(status) + ", was " + Status.getName(st), status, st);
+    }
+
+    public void testReplacingProperty() throws RepositoryException,
+            NotExecutableException {
+        Property p1 = testNode.setProperty(propertyName1, "value1");
+        p1.remove();
+
+        Property p2 = testNode.setProperty(propertyName1, "value2");
+        p2.remove();
+
+        Property p3 = testNode.setProperty(propertyName1, "value3");
+        testNode.save();
+
+        assertTrue(testNode.hasProperty(propertyName1));
+        assertEquals("value3", testNode.getProperty(propertyName1).getString());
+
+        assertItemStatus(p1, Status.REMOVED);
+        assertItemStatus(p2, Status.REMOVED);
+        assertItemStatus(p3, Status.EXISTING);
+    }
+
+    public void testReplacingProperty2() throws RepositoryException,
+            NotExecutableException {
+        Property p1 = testNode.setProperty(propertyName2, "value1");
+        p1.remove();
+
+        Property p2 = testNode.setProperty(propertyName2, "value2");
+        p2.remove();
+
+        Property p3 = testNode.setProperty(propertyName2, "value3");
+        p3.remove();
+        testNode.save();
+
+        assertFalse(testNode.hasProperty(propertyName2));
+
+        assertItemStatus(p1, Status.REMOVED);
+        assertItemStatus(p2, Status.REMOVED);
+        assertItemStatus(p3, Status.REMOVED);
+    }
+
+    public void testRevertReplacingProperty() throws RepositoryException,
+            NotExecutableException {
+        String val = testNode.getProperty(propertyName1).getString();
+        Property p1 = testNode.setProperty(propertyName1, "value1");
+        p1.remove();
+
+        Property p2 = testNode.setProperty(propertyName1, "value2");
+        p2.remove();
+
+        Property p3 = testNode.setProperty(propertyName1, "value3");
+        testNode.refresh(false);
+
+        assertTrue(testNode.hasProperty(propertyName1));
+        assertEquals(val, p1.getString());
+
+        assertItemStatus(p1, Status.EXISTING);
+        assertItemStatus(p2, Status.REMOVED);
+        assertItemStatus(p3, Status.REMOVED);
+    }
+
+    public void testAddingProperty() throws RepositoryException,
+            NotExecutableException {
+        Property p1 = testNode.setProperty(propertyName2, "value1");
+        p1.remove();
+
+        Property p2 = testNode.setProperty(propertyName2, "value2");
+        p2.remove();
+
+        Property p3 = testNode.setProperty(propertyName2, "value3");
+        testNode.save();
+
+        assertTrue(testNode.hasProperty(propertyName2));
+
+        assertItemStatus(p1, Status.REMOVED);
+        assertItemStatus(p2, Status.REMOVED);
+        assertItemStatus(p3, Status.EXISTING);
+    }
+
+    public void testAddingProperty2() throws RepositoryException,
+            NotExecutableException {
+        Property p1 = testNode.setProperty(propertyName2, "value1");
+        p1.remove();
+
+        Property p2 = testNode.setProperty(propertyName2, "value2");
+        p2.remove();
+
+        Property p3 = testNode.setProperty(propertyName2, "value3");
+        p3.remove();
+        testNode.save();
+
+        assertFalse(testNode.hasProperty(propertyName2));
+
+        assertItemStatus(p1, Status.REMOVED);
+        assertItemStatus(p2, Status.REMOVED);
+        assertItemStatus(p3, Status.REMOVED);
+    }
+
+    public void testRevertAddingProperty() throws RepositoryException,
+            NotExecutableException {
+        Property p1 = testNode.setProperty(propertyName2, "value1");
+        p1.remove();
+
+        Property p2 = testNode.setProperty(propertyName2, "value2");
+        p2.remove();
+
+        Property p3 = testNode.setProperty(propertyName2, "value3");
+        testNode.refresh(false);
+
+        assertFalse(testNode.hasProperty(propertyName2));
+
+        assertItemStatus(p1, Status.REMOVED);
+        assertItemStatus(p2, Status.REMOVED);
+        assertItemStatus(p3, Status.REMOVED);
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/AddPropertyTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/AddPropertyTest.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ExternalModificationTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ExternalModificationTest.java?rev=704361&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ExternalModificationTest.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ExternalModificationTest.java Tue Oct 14 00:48:22 2008
@@ -0,0 +1,189 @@
+/*
+ * 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.jcr2spi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.apache.jackrabbit.jcr2spi.state.Status;
+
+import javax.jcr.Session;
+import javax.jcr.RepositoryException;
+import javax.jcr.Node;
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.Item;
+
+/** <code>ExternalModificationTest</code>... */
+public class ExternalModificationTest extends AbstractJCRTest {
+
+    private static Logger log = LoggerFactory.getLogger(ExternalModificationTest.class);
+
+    private Node destParentNode;
+    private Node refNode;
+    private Session testSession;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // create a referenceable node and destination parent.
+        destParentNode = testRootNode.addNode(nodeName1, testNodeType);
+        refNode = testRootNode.addNode(nodeName2, getProperty("nodetype2"));
+        refNode.addMixin(mixReferenceable);
+        testRootNode.save();
+
+        testSession = helper.getReadWriteSession();
+    }
+
+    protected void tearDown() throws Exception {
+        if (testSession != null) {
+            testSession.logout();
+        }
+        super.tearDown();
+    }
+
+    private static boolean isItemStatus(Item item, int status) throws NotExecutableException {
+        if (!(item instanceof ItemImpl)) {
+            throw new NotExecutableException("org.apache.jackrabbit.jcr2spi.ItemImpl expected");
+        }
+        int st = ((ItemImpl) item).getItemState().getStatus();
+        return st == status;
+    }
+
+    private static void assertItemStatus(Item item, int status) throws NotExecutableException {
+        if (!(item instanceof ItemImpl)) {
+            throw new NotExecutableException("org.apache.jackrabbit.jcr2spi.ItemImpl expected");
+        }
+        int st = ((ItemImpl) item).getItemState().getStatus();
+        assertEquals("Expected status to be " + Status.getName(status) + ", was " + Status.getName(st), status, st);
+    }
+
+    public void testMovedReferenceableNode() throws RepositoryException, NotExecutableException {
+        Node refNode2 = (Node) testSession.getItem(refNode.getPath());
+
+        superuser.move(refNode.getPath(), destParentNode.getPath() + "/" + nodeName2);
+        superuser.save();
+
+        try {
+            // modify some prop of the moved node with session 2
+            refNode2.setProperty(propertyName1, "test");
+            testSession.save();
+            // node has been automatically moved to new place
+            // -> check if the parent is correct.
+            assertTrue(testSession.getItem(destParentNode.getPath()).isSame(refNode.getParent()));
+        } catch (InvalidItemStateException e) {
+            // no automatic move of the externally moved node. ok.
+            log.debug(e.getMessage());
+        }
+    }
+
+    public void testRefreshMovedReferenceableNode() throws RepositoryException, NotExecutableException {
+        Node refNode2 = (Node) testSession.getItem(refNode.getPath());
+
+        superuser.move(refNode.getPath(), destParentNode.getPath() + "/" + nodeName2);
+        superuser.save();
+
+        try {
+            refNode2.refresh(true);
+            Node parent = refNode2.getParent();
+            if (parent.isSame(testSession.getItem(destParentNode.getPath()))) {
+                // node has been automatically moved to new place
+                assertItemStatus(refNode2, Status.EXISTING);
+            } else {
+                assertItemStatus(refNode2, Status.REMOVED);
+            }
+        } catch (InvalidItemStateException e) {
+            // no automatic move of the externally moved node. ok.
+            log.debug(e.getMessage());
+            // since node had no pending changes -> status should be changed
+            // to REMOVED.
+            assertItemStatus(refNode2, Status.REMOVED);
+        }
+    }
+
+    public void testConflictingAddMixin() throws RepositoryException, NotExecutableException {
+        Node refNode2 = (Node) testSession.getItem(refNode.getPath());
+        refNode2.addMixin(mixLockable);
+
+        superuser.move(refNode.getPath(), destParentNode.getPath() + "/" + nodeName2);
+        superuser.save();
+
+        try {
+            refNode2.refresh(true);
+            Node parent = refNode2.getParent();
+            if (parent.isSame(testSession.getItem(destParentNode.getPath()))) {
+                // node has been automatically moved to new place
+                assertItemStatus(refNode2, Status.EXISTING_MODIFIED);
+            } else if (!isItemStatus(refNode2, Status.EXISTING_MODIFIED)) {
+                // external removal was detected either by observation or be
+                // batch-reading the parent -> status must be stale.
+                assertItemStatus(refNode2, Status.STALE_DESTROYED);
+            }
+        } catch (InvalidItemStateException e) {
+            // no automatic move of the externally moved node. ok.
+            log.debug(e.getMessage());
+            // since refNode2 has pending modifications its status should be
+            // changed to STALE_DESTROYED.
+            assertItemStatus(refNode2, Status.STALE_DESTROYED);
+            Node refAgain = testSession.getNodeByUUID(refNode.getUUID());
+            assertTrue(refAgain.getParent().isSame(testSession.getItem(destParentNode.getPath())));
+            assertFalse(refAgain.isNodeType(mixLockable));
+        }
+    }
+
+    public void testStaleDestroyed() throws RepositoryException, NotExecutableException {
+        Node refNode2 = (Node) testSession.getItem(refNode.getPath());
+        refNode2.addMixin(mixLockable);
+
+        superuser.move(refNode.getPath(), destParentNode.getPath() + "/" + nodeName2);
+        superuser.save();
+
+        try {
+            refNode2.refresh(true);
+            Node parent = refNode2.getParent();
+        } catch (InvalidItemStateException e) {
+        }
+
+        if (isItemStatus(refNode2, Status.STALE_DESTROYED)) {
+            try {
+                refNode2.refresh(false);
+                fail();
+            } catch (InvalidItemStateException e) {
+                // correct behaviour
+            }
+        }
+    }
+
+    public void testStaleDestroyed2() throws RepositoryException, NotExecutableException {
+        Node refNode2 = (Node) testSession.getItem(refNode.getPath());
+        refNode2.addMixin(mixLockable);
+
+        superuser.move(refNode.getPath(), destParentNode.getPath() + "/" + nodeName2);
+        superuser.save();
+
+        try {
+            refNode2.refresh(true);
+            Node parent = refNode2.getParent();
+        } catch (InvalidItemStateException e) {
+        }
+
+        if (isItemStatus(refNode2, Status.STALE_DESTROYED)) {
+            testSession.refresh(false);
+            assertItemStatus(refNode2, Status.REMOVED);
+        }
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ExternalModificationTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ExternalModificationTest.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/GetPropertyTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/GetPropertyTest.java?rev=704361&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/GetPropertyTest.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/GetPropertyTest.java Tue Oct 14 00:48:22 2008
@@ -0,0 +1,85 @@
+/*
+ * 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.jcr2spi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.Session;
+import javax.jcr.RepositoryException;
+
+/** <code>GetPropertyTest</code>... */
+public class GetPropertyTest extends AbstractJCRTest {
+
+    private static Logger log = LoggerFactory.getLogger(GetPropertyTest.class);
+
+    private String prop1Path;
+    private String prop2Path;
+
+    private Session readOnly;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        Node n = testRootNode.addNode(nodeName1, testNodeType);
+        Property p = n.setProperty(propertyName1, "string1");
+        prop1Path = p.getPath();
+
+        p = n.setProperty(propertyName2, "string2");
+        prop2Path = p.getPath();
+
+        testRootNode.save();
+
+        readOnly = helper.getReadOnlySession();
+    }
+
+    protected void tearDown() throws Exception {
+        if (readOnly != null) {
+            readOnly.logout();
+        }
+        super.tearDown();
+    }
+
+    public void testItemExists() throws RepositoryException {
+        assertTrue(readOnly.itemExists(prop1Path));
+        assertTrue(readOnly.itemExists(prop2Path));
+    }
+
+    public void testGetItem() throws RepositoryException {
+        assertFalse(readOnly.getItem(prop1Path).isNode());
+        assertFalse(readOnly.getItem(prop2Path).isNode());
+    }
+
+    public void testHasProperty() throws RepositoryException {
+        String testPath = testRootNode.getPath();
+        Node trn = (Node) readOnly.getItem(testPath);
+
+        assertTrue(trn.hasProperty(prop1Path.substring(testPath.length() + 1)));
+        assertTrue(trn.hasProperty(prop2Path.substring(testPath.length() + 1)));
+    }
+
+    public void testGetProperty() throws RepositoryException {
+        String testPath = testRootNode.getPath();
+        Node trn = (Node) readOnly.getItem(testPath);
+
+        trn.getProperty(prop1Path.substring(testPath.length() + 1));
+        trn.getProperty(prop2Path.substring(testPath.length() + 1));
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/GetPropertyTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/GetPropertyTest.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/HierarchyNodeTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/HierarchyNodeTest.java?rev=704361&r1=704360&r2=704361&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/HierarchyNodeTest.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/HierarchyNodeTest.java Tue Oct 14 00:48:22 2008
@@ -88,7 +88,11 @@
 
     public void testGetProperties() throws RepositoryException {
         Session readSession = helper.getReadOnlySession();
-        dump((Node) readSession.getItem(fileNode.getPath()));
+        try {
+            dump((Node) readSession.getItem(fileNode.getPath()));
+        } finally {
+            readSession.logout();
+        }
     }
 
     /** Recursively outputs the contents of the given node. */

Added: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MixinModificationTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MixinModificationTest.java?rev=704361&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MixinModificationTest.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MixinModificationTest.java Tue Oct 14 00:48:22 2008
@@ -0,0 +1,134 @@
+/*
+ * 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.jcr2spi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.jcr2spi.state.Status;
+
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.Session;
+import javax.jcr.ItemNotFoundException;
+
+/** <code>MixinModificationTest</code>... */
+public class MixinModificationTest extends AbstractJCRTest {
+
+    private static Logger log = LoggerFactory.getLogger(MixinModificationTest.class);
+
+    private static void assertItemStatus(Item item, int status) throws NotExecutableException {
+        if (!(item instanceof ItemImpl)) {
+            throw new NotExecutableException("org.apache.jackrabbit.jcr2spi.ItemImpl expected");
+        }
+        int st = ((ItemImpl) item).getItemState().getStatus();
+        assertEquals("Expected status to be " + Status.getName(status) + ", was " + Status.getName(st), status, st);
+    }
+
+    public void testAddMixin() throws RepositoryException,
+            NotExecutableException {
+        Node n = testRootNode.addNode(nodeName1);
+        testRootNode.save();
+
+        if (n.isNodeType(mixVersionable)) {
+            throw new NotExecutableException();
+        }
+        try {
+            n.addMixin(mixVersionable);
+        } catch (RepositoryException e) {
+            throw new NotExecutableException();
+        }
+
+        assertItemStatus(n, Status.EXISTING_MODIFIED);
+        assertTrue(n.hasProperty(jcrMixinTypes));
+        Property p = n.getProperty(jcrMixinTypes);
+
+        n.save();
+
+        // after saving the affected target node must be marked 'invalidated'.
+        // the property however should be set existing.
+        assertItemStatus(n, Status.INVALIDATED);
+        assertItemStatus(p, Status.EXISTING);
+    }
+
+    public void testAddMixin2() throws RepositoryException,
+            NotExecutableException {
+        Node n;
+        try {
+            n = testRootNode.addNode(nodeName1);
+            n.addMixin(mixVersionable);
+            testRootNode.save();
+        } catch (RepositoryException e) {
+            throw new NotExecutableException();
+        }
+
+        // after saving the affected target node must be marked 'invalidated'
+        // even if adding the node and setting a mixin was achieved in the
+        // same batch.
+        assertItemStatus(n, Status.INVALIDATED);
+    }
+
+    public void testRemoveMixin() throws RepositoryException, NotExecutableException {
+        String nPath;
+        try {
+            Node n = testRootNode.addNode(nodeName1);
+            nPath = n.getPath();
+            n.addMixin(mixReferenceable);
+            testRootNode.save();
+        } catch (RepositoryException e) {
+            throw new NotExecutableException();
+        }
+
+        Session testSession = helper.getReadWriteSession();
+        try {
+            Node n = (Node) testSession.getItem(nPath);
+            String uuid = n.getUUID();
+
+            // remove the mixin again.
+            n.removeMixin(mixReferenceable);
+            assertFalse(n.hasProperty(jcrMixinTypes));
+            n.save();
+
+            // accessing node by uuid should not be possible any more.
+            try {
+                Node n2 = testSession.getNodeByUUID(uuid);
+                fail();
+            } catch (ItemNotFoundException e) {
+                // ok
+            }
+
+            // however: the added node should still be valid. but not referenceable
+            assertItemStatus(n, Status.EXISTING);
+            assertFalse(n.isNodeType(mixReferenceable));
+            assertTrue(testSession.itemExists(nPath));
+
+            try {
+                Node n2 = superuser.getNodeByUUID(uuid);
+                fail();
+            } catch (ItemNotFoundException e) {
+                // ok
+            }
+        } finally {
+            if (testSession != null) {
+                testSession.logout();
+            }
+        }
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MixinModificationTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MixinModificationTest.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MoveCombinedTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MoveCombinedTest.java?rev=704361&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MoveCombinedTest.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MoveCombinedTest.java Tue Oct 14 00:48:22 2008
@@ -0,0 +1,99 @@
+/*
+ * 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.jcr2spi;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/** <code>MoveCombinedTest</code>... */
+public class MoveCombinedTest extends AbstractMoveTest {
+
+    private static Logger log = LoggerFactory.getLogger(MoveCombinedTest.class);
+
+    private Session testSession;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        testSession = helper.getReadOnlySession();
+    }
+
+    protected void tearDown() throws Exception {
+        if (testSession != null) {
+            testSession.logout();
+        }
+        super.tearDown();
+    }
+
+    protected boolean isSessionMove() {
+        return true;
+    }
+
+    public void testMoveAndAddNode() throws RepositoryException {
+        doMove(moveNode.getPath(), destinationPath);
+        Node n = moveNode.addNode(nodeName3);
+        superuser.save();
+
+        assertTrue(testSession.itemExists(n.getPath()));
+    }
+
+    public void testMoveAndAddProperty() throws RepositoryException {
+        doMove(moveNode.getPath(), destinationPath);
+        Property p = moveNode.setProperty(propertyName1, "someValue");
+        superuser.save();
+
+        assertTrue(testSession.itemExists(p.getPath()));
+    }
+
+    public void testMoveAndSetPropertyValue() throws RepositoryException {
+        Property p = moveNode.setProperty(propertyName1, "someValue");
+        moveNode.save();
+
+        doMove(moveNode.getPath(), destinationPath);
+        p = moveNode.setProperty(propertyName1, "changedValue");
+        superuser.save();
+
+        assertTrue(testSession.itemExists(p.getPath()));
+    }
+
+    public void testMoveAndRemove() throws RepositoryException {
+        Node n = moveNode.addNode(nodeName3);
+        String nPath = n.getPath();
+        superuser.save();
+
+        doMove(moveNode.getPath(), destinationPath);
+        n.remove();
+        superuser.save();
+
+        assertFalse(testSession.itemExists(nPath));
+        assertFalse(testSession.itemExists(destinationPath + "/" + nodeName3));
+    }
+
+    public void testMoveAndSetMixin() throws RepositoryException {
+
+        doMove(moveNode.getPath(), destinationPath);
+        moveNode.addMixin(mixVersionable);
+        superuser.save();
+
+        Node n = (Node) testSession.getItem(destinationPath);
+        assertTrue(n.isNodeType(mixVersionable));
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MoveCombinedTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MoveCombinedTest.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MoveNewTreeTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MoveNewTreeTest.java?rev=704361&r1=704360&r2=704361&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MoveNewTreeTest.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/MoveNewTreeTest.java Tue Oct 14 00:48:22 2008
@@ -22,6 +22,9 @@
 import javax.jcr.RepositoryException;
 import javax.jcr.Item;
 import javax.jcr.PathNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Iterator;
 
 /**
  * <code>MoveTreeTest</code>...
@@ -46,7 +49,6 @@
         assertTrue("Moving a node must move all child items as well.", ancestor.isSame(destParentNode));
         ancestor = grandChildNode.getAncestor(degree);
         assertTrue("Moving a node must move all child items as well.", ancestor.isSame(destParentNode));
-
     }
 
     public void testTreeEntries() throws RepositoryException {
@@ -109,4 +111,23 @@
             // OK
         }
     }
+
+    public void testRefreshMovedTree() throws RepositoryException {
+        testRootNode.refresh(true);
+        String msg = "Refresh must not revert a moved tree.";
+
+        assertFalse(msg, superuser.itemExists(srcPath + "/" + nodeName2 + "/" + nodeName3));
+        int degree = destParentNode.getDepth();
+
+        List l = new ArrayList();
+        l.add(childNode);
+        l.add(childProperty);
+        l.add(grandChildNode);
+
+        for (Iterator it = l.iterator(); it.hasNext();) {
+            Item item = (Item) it.next();
+            assertTrue(msg, item.isNew());
+            assertTrue(msg, childNode.getAncestor(degree).isSame(destParentNode));
+        }
+    }
 }