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 2006/10/26 13:02:04 UTC

svn commit: r467956 [1/3] - in /jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi: ./ state/ version/

Author: angela
Date: Thu Oct 26 04:02:02 2006
New Revision: 467956

URL: http://svn.apache.org/viewvc?view=rev&rev=467956
Log:
work in progress

- version always protected
- versionHistory always protected
- fix Node.getReferences && reference properties
- mixinTypes are 'active' upon save only (and not before). therefore
  cleanup after removeMixin is done only after save.
  adding new states defined by a new mixin are only added after the
  node has been saved.
- nodestates identified by a uuid only were created a second time upon
  ChildNodeEntry.resolve => fix needs to be improved
- Events/ChangeLog are processed by the save-target state
- ChangeLog.persisted not used any more

Added:
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateCache.java   (with props)
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateCreationListener.java   (with props)
Modified:
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemImpl.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/PropertyImpl.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/CachingItemStateManager.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ChangeLog.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemState.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateFactory.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateValidator.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/NodeState.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/Status.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientISFactory.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateFactory.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/TransientItemStateManager.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/WorkspaceItemStateFactory.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/WorkspaceItemStateManager.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/version/VersionHistoryImpl.java
    jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/version/VersionImpl.java

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemImpl.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemImpl.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemImpl.java Thu Oct 26 04:02:02 2006
@@ -21,8 +21,6 @@
 import org.apache.jackrabbit.jcr2spi.state.ItemStateException;
 import org.apache.jackrabbit.jcr2spi.state.StaleItemStateException;
 import org.apache.jackrabbit.jcr2spi.state.ItemStateValidator;
-import org.apache.jackrabbit.jcr2spi.state.NodeState;
-import org.apache.jackrabbit.jcr2spi.state.PropertyState;
 import org.apache.jackrabbit.jcr2spi.state.ItemStateLifeCycleListener;
 import org.apache.jackrabbit.jcr2spi.state.Status;
 import org.apache.jackrabbit.jcr2spi.operation.Remove;
@@ -30,7 +28,6 @@
 import org.apache.jackrabbit.jcr2spi.util.LogUtil;
 import org.apache.jackrabbit.name.NoPrefixDeclaredException;
 import org.apache.jackrabbit.name.Path;
-import org.apache.jackrabbit.spi.QPropertyDefinition;
 import org.apache.jackrabbit.name.QName;
 import org.apache.jackrabbit.name.PathFormat;
 import org.slf4j.LoggerFactory;
@@ -88,7 +85,7 @@
         notifyCreated();
 
         // add this item as listener to events of the underlying state object
-        this.state.addListener(this);
+        state.addListener(this);
     }
 
     //-----------------------------------------------------< Item interface >---
@@ -457,11 +454,9 @@
      *
      * @throws UnsupportedRepositoryOperationException
      * @throws RepositoryException
-     * @see ItemStateValidator#checkAddNode(NodeState, QName, QName, int)
-     * @see ItemStateValidator#checkAddProperty(NodeState, QName, QPropertyDefinition, int)
-     * @see ItemStateValidator#checkSetProperty(PropertyState, int)
+     * @see ItemStateValidator
      */
-    void checkIsWritable() throws UnsupportedRepositoryOperationException, ConstraintViolationException, RepositoryException {
+    protected void checkIsWritable() throws UnsupportedRepositoryOperationException, ConstraintViolationException, RepositoryException {
         checkSupportedOption(Repository.LEVEL_2_SUPPORTED);
         checkStatus();
     }
@@ -491,7 +486,7 @@
      *
      * @return state associated with this <code>Item</code>
      */
-    ItemState getItemState() {
+    protected ItemState getItemState() {
         return state;
     }
 

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java Thu Oct 26 04:02:02 2006
@@ -375,7 +375,7 @@
     public Property setProperty(String name, String value, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
         // validation performed in subsequent method
         Value v = (value == null) ? null : session.getValueFactory().createValue(value, type);
-        return setProperty(name, v);
+        return setProperty(name, v, type);
     }
 
     /**
@@ -384,7 +384,7 @@
     public Property setProperty(String name, InputStream value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
         // validation performed in subsequent method
         Value v = (value == null ? null : session.getValueFactory().createValue(value));
-        return setProperty(name, v);
+        return setProperty(name, v, PropertyType.BINARY);
     }
 
     /**
@@ -392,7 +392,7 @@
      */
     public Property setProperty(String name, boolean value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
         // validation performed in subsequent method
-        return setProperty(name, session.getValueFactory().createValue(value));
+        return setProperty(name, session.getValueFactory().createValue(value), PropertyType.BOOLEAN);
     }
 
     /**
@@ -400,7 +400,7 @@
      */
     public Property setProperty(String name, double value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
         // validation performed in subsequent method
-        return setProperty(name, session.getValueFactory().createValue(value));
+        return setProperty(name, session.getValueFactory().createValue(value), PropertyType.DOUBLE);
     }
 
     /**
@@ -408,7 +408,7 @@
      */
     public Property setProperty(String name, long value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
         // validation performed in subsequent method
-        return setProperty(name, session.getValueFactory().createValue(value));
+        return setProperty(name, session.getValueFactory().createValue(value), PropertyType.LONG);
     }
 
     /**
@@ -417,16 +417,24 @@
     public Property setProperty(String name, Calendar value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
         // validation performed in subsequent method
         Value v = (value == null ? null : session.getValueFactory().createValue(value));
-        return setProperty(name, v);
+        return setProperty(name, v, PropertyType.DATE);
     }
 
     /**
      * @see Node#setProperty(String, Node)
      */
     public Property setProperty(String name, Node value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
-        // validation performed in subsequent method
-        Value v = (value == null ? null : session.getValueFactory().createValue(value));
-        return setProperty(name, v);
+        // duplicate check to make sure, writability is asserted before value
+        // validation below.
+        checkIsWritable();
+        Value v;
+        if (value == null) {
+            v = null;
+        } else {
+            PropertyImpl.checkValidReference(value, PropertyType.REFERENCE, this);
+            v = session.getValueFactory().createValue(value);
+        }
+        return setProperty(name, v, PropertyType.REFERENCE);
     }
 
     /**
@@ -536,11 +544,12 @@
      */
     public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException {
         checkStatus();
-        if (!isNodeType(QName.MIX_REFERENCEABLE)) {
+        String uuid = getNodeState().getUUID();
+        if (!isNodeType(QName.MIX_REFERENCEABLE) && uuid != null) {
             throw new UnsupportedRepositoryOperationException();
         }
         // Node is referenceable -> NodeId must contain a UUID part
-        return getNodeId().getUUID();
+        return uuid;
     }
 
     /**
@@ -574,12 +583,12 @@
         checkStatus();
         try {
             ItemStateManager itemStateMgr = session.getItemStateManager();
-            if (itemStateMgr.hasReferingStates(getNodeState())) {
-                Collection refStates = itemStateMgr.getReferingStates(getNodeState());
-                return new LazyItemIterator(itemMgr, refStates);
-            } else {
+            Collection refStates = itemStateMgr.getReferingStates(getNodeState());
+            if (refStates.isEmpty()) {
                 // there are no references, return empty iterator
                 return IteratorHelper.EMPTY;
+            } else {
+                return new LazyItemIterator(itemMgr, refStates);
             }
         } catch (ItemStateException e) {
             String msg = "Unable to retrieve REFERENCE properties that refer to " + safeGetJCRPath();
@@ -754,6 +763,9 @@
         } catch (VersionException e) {
             log.debug("Cannot add mixin '" + mixinName + "': " + e.getMessage());
             return false;
+        } catch (ConstraintViolationException e) {
+            log.debug("Cannot add mixin '" + mixinName + "': " + e.getMessage());
+            return false;
         }
     }
 
@@ -991,7 +1003,7 @@
         checkSessionHasPendingChanges();
         // check for version-enabled and lock are performed with subsequent calls.
         Version v = getVersionHistory().getVersion(versionName);
-        restore(v, removeExisting);
+        restore(this, null, v, removeExisting);
     }
 
     /**
@@ -1074,10 +1086,6 @@
      * @throws RepositoryException
      */
     private void restore(NodeImpl targetNode, Path relQPath, Version version, boolean removeExisting) throws PathNotFoundException, ItemExistsException, VersionException, ConstraintViolationException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
-        targetNode.checkIsWritable();
-        targetNode.checkIsLocked();
-
-
         if (relQPath == null) {
             /* restore target already exists. */
             // target must be versionable
@@ -1092,6 +1100,8 @@
             if (vH.getRootVersion().isSame(version)) {
                 throw new VersionException("Attempt to restore root version.");
             }
+            targetNode.checkIsWritable();
+            targetNode.checkIsLocked();
         } else {
             /* If no node exists at relPath then a VersionException is thrown if
                the parent node is not checked out. */
@@ -1101,6 +1111,7 @@
                     + LogUtil.safeGetJCRPath(relQPath, session.getNamespaceResolver())
                     + "' must be checked out.");
             }
+            targetNode.checkIsLocked();
             // NOTE: check for nodetype constraint violation is left to the 'server'
         }
 
@@ -1270,7 +1281,7 @@
         if (!isNodeType(QName.MIX_LOCKABLE)) {
             String msg = "Unable to perform locking operation on non-lockable node: " + getPath();
             log.debug(msg);
-            throw new UnsupportedRepositoryOperationException(msg);
+            throw new LockException(msg);
         }
     }
 

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/PropertyImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/PropertyImpl.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/PropertyImpl.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/PropertyImpl.java Thu Oct 26 04:02:02 2006
@@ -262,23 +262,9 @@
         if (value == null) {
             setInternalValues(null, reqType);
         } else {
-            if (reqType == PropertyType.REFERENCE) {
-                if (value instanceof NodeImpl) {
-                    NodeImpl targetNode = (NodeImpl)value;
-                    if (targetNode.isNodeType(QName.MIX_REFERENCEABLE)) {
-                        QValue qValue = QValue.create(targetNode.getUUID(), PropertyType.REFERENCE);
-                        setInternalValues(new QValue[]{qValue}, reqType);
-                    } else {
-                        throw new ValueFormatException("Target node must be of node type mix:referenceable");
-                    }
-                } else {
-                    String msg = "Incompatible Node object: " + value + "(" + safeGetJCRPath() + ")";
-                    log.debug(msg);
-                    throw new RepositoryException(msg);
-                }
-            } else {
-                throw new ValueFormatException("Property must be of type REFERENCE (" + safeGetJCRPath() + ")");
-            }
+            checkValidReference(value, reqType, this);
+            QValue qValue = QValue.create(((NodeImpl)value).getUUID(), PropertyType.REFERENCE);
+            setInternalValues(new QValue[]{qValue}, reqType);
         }
     }
 
@@ -477,7 +463,7 @@
      */
     private QValue getQValue() throws ValueFormatException, RepositoryException {
         checkStatus();
-        if (isMultiple()) {
+        if (isMultiple()) {                                                            
             throw new ValueFormatException(safeGetJCRPath() + " is multi-valued and can therefore only be retrieved as an array of values");
         }
         // avoid unnecessary object creation if possible
@@ -553,5 +539,30 @@
      */
     private PropertyState getPropertyState() {
         return (PropertyState) getItemState();
+    }
+
+    /**
+     * 
+     * @param value
+     * @param propertyType
+     * @param itemImpl
+     * @throws ValueFormatException
+     * @throws RepositoryException
+     */
+    static void checkValidReference(Node value, int propertyType, ItemImpl itemImpl) throws ValueFormatException, RepositoryException {
+        if (propertyType == PropertyType.REFERENCE) {
+            if (value instanceof NodeImpl) {
+                NodeImpl targetNode = (NodeImpl)value;
+                if (!targetNode.isNodeType(QName.MIX_REFERENCEABLE)) {
+                    throw new ValueFormatException("Target node must be of node type mix:referenceable");
+                }
+            } else {
+                String msg = "Incompatible Node object: " + value + "(" + itemImpl.safeGetJCRPath() + ")";
+                log.debug(msg);
+                throw new RepositoryException(msg);
+            }
+        } else {
+            throw new ValueFormatException("Property must be of type REFERENCE (" + itemImpl.safeGetJCRPath() + ")");
+        }
     }
 }

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java Thu Oct 26 04:02:02 2006
@@ -545,7 +545,8 @@
      *                  of a workspace operation. In that case there are no
      *                  local transient changes.
      */
-    private void onEventReceived(EventIterator events, boolean isLocal, ChangeLog changeLog) {
+    private void onEventReceived(EventIterator events, boolean isLocal,
+                                 ChangeLog changeLog) {
         // notify listener
         // need to copy events into a list because we notify multiple listeners
         List eventList = new ArrayList();
@@ -832,7 +833,7 @@
 
         public void visit(LockOperation operation) throws AccessDeniedException, InvalidItemStateException, UnsupportedRepositoryOperationException, LockException, RepositoryException {
             NodeId nId = operation.getNodeState().getNodeId();
-            events = service.lock(sessionInfo, nId, operation.isDeep());
+            events = service.lock(sessionInfo, nId, operation.isDeep(), operation.isSessionScoped());
         }
 
         public void visit(LockRefresh operation) throws AccessDeniedException, InvalidItemStateException, UnsupportedRepositoryOperationException, LockException, RepositoryException {

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/CachingItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/CachingItemStateManager.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/CachingItemStateManager.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/CachingItemStateManager.java Thu Oct 26 04:02:02 2006
@@ -21,12 +21,9 @@
 import org.apache.jackrabbit.spi.NodeId;
 import org.apache.jackrabbit.spi.IdFactory;
 import org.apache.jackrabbit.spi.PropertyId;
-import org.apache.commons.collections.map.ReferenceMap;
-import org.apache.commons.collections.map.LRUMap;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.Map;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.Collections;
@@ -51,14 +48,9 @@
     private final ItemStateFactory isf;
 
     /**
-     * Maps a String uuid to a {@link NodeState}.
+     * Cache
      */
-    private final Map uuid2NodeState;
-
-    /**
-     * Map of recently used <code>ItemState</code>.
-     */
-    private final Map recentlyUsed;
+    private final ItemStateCache cache;
 
     /**
      * The root node of the workspace or <code>null</code> if it has not been
@@ -72,12 +64,6 @@
     private final IdFactory idFactory;
 
     /**
-     * An {@link ItemStateLifeCycleListener} to maintain the LRU and UUID
-     * reference cache.
-     */
-    private final ItemStateLifeCycleListener lifeCycleListener;
-
-    /**
      * Creates a new <code>CachingItemStateManager</code>.
      *
      * @param isf       the item state factory to create item state instances.
@@ -86,9 +72,9 @@
     public CachingItemStateManager(ItemStateFactory isf, IdFactory idFactory) {
         this.isf = isf;
         this.idFactory = idFactory;
-        this.uuid2NodeState = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.WEAK);
-        this.recentlyUsed = new LRUMap(1000); // TODO: make configurable
-        this.lifeCycleListener = new ISLifeCycleListener();
+        this.cache = new ItemStateCache();
+
+        isf.setCache(cache);
     }
 
     //---------------------------------------------------< ItemStateManager >---
@@ -96,10 +82,6 @@
     public NodeState getRootState() throws ItemStateException {
         if (root == null) {
             root = isf.createRootState(this);
-            if (root.getUUID() != null) {
-                uuid2NodeState.put(root.getUUID(), root);
-            }
-            root.addListener(lifeCycleListener);
         }
         return root;
     }
@@ -131,16 +113,16 @@
      * @param nodeState
      */
     public Collection getReferingStates(NodeState nodeState) throws ItemStateException {
-        if (hasReferingStates(nodeState)) {
-            Set refStates = new HashSet();
-            Iterator it =  nodeState.getNodeReferences().iterator();
-            while (it.hasNext()) {
-                PropertyId pId = (PropertyId) it.next();
-                refStates.add(getItemState(pId));
-            }
-            return Collections.unmodifiableCollection(refStates);
-        } else {
+        Set refStates = new HashSet();
+        Iterator it =  nodeState.getNodeReferences().iterator();
+        while (it.hasNext()) {
+            PropertyId pId = (PropertyId) it.next();
+            refStates.add(getItemState(pId));
+        }
+        if (refStates.isEmpty()) {
             return Collections.EMPTY_SET;
+        } else {
+            return Collections.unmodifiableCollection(refStates);
         }
     }
 
@@ -151,7 +133,7 @@
      */
     public boolean hasReferingStates(NodeState nodeState) {
         NodeReferences nr = nodeState.getNodeReferences();
-        return nr != null && !nr.isEmpty();
+        return !nr.isEmpty();
     }
 
     //------------------------------< internal >--------------------------------
@@ -164,16 +146,6 @@
     }
 
     /**
-     * Called whenever an item state is accessed. Calling this method will update
-     * the LRU map which keeps track of most recently used item states.
-     *
-     * @param state the touched state.
-     */
-    protected void touch(ItemState state) {
-        recentlyUsed.put(state, state);
-    }
-
-    /**
      * Resolves the id into an <code>ItemState</code>.
      *
      * @param id the id of the <code>ItemState</code> to resolve.
@@ -189,13 +161,11 @@
         NodeState nodeState;
         // resolve uuid part
         if (uuid != null) {
-            nodeState = (NodeState) uuid2NodeState.get(uuid);
+            nodeState = cache.getNodeState(uuid);
             if (nodeState == null) {
-                // state identified by the uuid is not yet cached -> get from ISM
+                // state identified by the uuid is not yet cached -> get from ISF
                 NodeId refId = (path == null) ? (NodeId) id : idFactory.createNodeId(uuid);
                 nodeState = isf.createNodeState(refId, this);
-                nodeState.addListener(lifeCycleListener);
-                uuid2NodeState.put(uuid, nodeState);
             }
         } else {
             // start with root node if no uuid part in id
@@ -206,7 +176,6 @@
         if (path != null) {
             s = PathResolver.resolve(nodeState, path);
         }
-        touch(s);
         return s;
     }
 
@@ -222,7 +191,7 @@
         ItemState state;
         // resolve UUID
         if (id.getUUID() != null) {
-            state = (ItemState) uuid2NodeState.get(id.getUUID());
+            state = cache.getNodeState(id.getUUID());
             if (state == null) {
                 // not cached
                 return null;
@@ -248,23 +217,5 @@
         }
 
         return state;
-    }
-
-    //-----------------------------------------< ItemStateLifeCycleListener >---
-
-    private class ISLifeCycleListener implements ItemStateLifeCycleListener {
-
-        public void statusChanged(ItemState state, int previousStatus) {
-            if (state.getStatus() == Status.REMOVED ||
-                state.getStatus() == Status.STALE_DESTROYED) {
-                recentlyUsed.remove(state);
-                if (state.isNode()) {
-                    NodeState nodeState = (NodeState) state;
-                    if (nodeState.getUUID() != null) {
-                        uuid2NodeState.remove(nodeState.getUUID());
-                    }
-                }
-            }
-        }
     }
 }

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ChangeLog.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ChangeLog.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ChangeLog.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ChangeLog.java Thu Oct 26 04:02:02 2006
@@ -238,28 +238,6 @@
 
     //-----------------------------< Inform ChangeLog about Success/Failure >---
     /**
-     * After the states have actually been persisted, update their
-     * internal states and notify listeners.
-     */
-    public void persisted() {
-        Iterator iter = modifiedStates();
-        while (iter.hasNext()) {
-            ItemState state = (ItemState) iter.next();
-            state.setStatus(Status.EXISTING);
-        }
-        iter = deletedStates();
-        while (iter.hasNext()) {
-            ItemState state = (ItemState) iter.next();
-            state.setStatus(Status.REMOVED);
-        }
-        iter = addedStates();
-        while (iter.hasNext()) {
-            ItemState state = (ItemState) iter.next();
-            state.setStatus(Status.EXISTING);
-        }
-    }
-
-    /**
      * Reset this change log, removing all members inside the
      * maps we built.
      */

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemState.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemState.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemState.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemState.java Thu Oct 26 04:02:02 2006
@@ -31,6 +31,8 @@
 import javax.jcr.RepositoryException;
 import java.util.Collection;
 import java.util.Set;
+import java.util.Iterator;
+import java.util.Collections;
 
 /**
  * <code>ItemState</code> represents the state of an <code>Item</code>.
@@ -44,7 +46,7 @@
 
     /**
      * Flag used to distinguish workspace states from session states. The first
-     * accepts call to {@link #refresh(Event, ChangeLog)}, while the latter
+     * accepts call to {@link #refresh(Event)}, while the latter
      * will be able to handle the various methods related to transient
      * modifications.
      */
@@ -67,6 +69,12 @@
     final IdFactory idFactory;
 
     /**
+     * The <code>ItemStateFactory</code> which is used to create new
+     * <code>ItemState</code> instances.
+     */
+    final ItemStateFactory isf;
+
+    /**
      * The parent <code>NodeState</code> or <code>null</code> if this
      * instance represents the root node.
      */
@@ -83,7 +91,7 @@
      * @param parent
      * @param initialStatus the initial status of the item state object
      */
-    protected ItemState(NodeState parent, int initialStatus, IdFactory idFactory,
+    protected ItemState(NodeState parent, int initialStatus, ItemStateFactory isf, IdFactory idFactory,
                         boolean isWorkspaceState) {
         switch (initialStatus) {
             case Status.EXISTING:
@@ -99,6 +107,7 @@
         overlayedState = null;
 
         this.idFactory = idFactory;
+        this.isf = isf;
         this.isWorkspaceState = isWorkspaceState;
     }
 
@@ -110,7 +119,7 @@
      * @param initialStatus the initial status of the new <code>ItemState</code> instance
      */
     protected ItemState(ItemState overlayedState, NodeState parent,
-                        int initialStatus, IdFactory idFactory) {
+                        int initialStatus, ItemStateFactory isf, IdFactory idFactory) {
         switch (initialStatus) {
             case Status.EXISTING:
             case Status.EXISTING_MODIFIED:
@@ -124,6 +133,7 @@
         }
         this.parent = parent;
         this.idFactory = idFactory;
+        this.isf = isf;
         this.isWorkspaceState = false;
 
         connect(overlayedState);
@@ -141,7 +151,7 @@
      * @return
      */
     public boolean isValid() {
-        return status == Status.EXISTING || status == Status.EXISTING_MODIFIED || status == Status.NEW;
+        return Status.isValid(getStatus());
     }
 
     /**
@@ -174,7 +184,7 @@
      */
     public Path getQPath() throws ItemNotFoundException, RepositoryException {
         // shortcut for root state
-        if (getParent() == null) {
+        if (parent == null) {
             return Path.ROOT;
         }
 
@@ -183,14 +193,6 @@
             Path.PathBuilder builder = new Path.PathBuilder();
             buildPath(builder, this);
             return builder.getPath();
-        } catch (NoSuchItemStateException e) {
-            String msg = "Failed to build path of " + this;
-            log.debug(msg);
-            throw new ItemNotFoundException(msg, e);
-        } catch (ItemStateException e) {
-            String msg = "Failed to build path of " + this;
-            log.debug(msg);
-            throw new RepositoryException(msg, e);
         } catch (MalformedPathException e) {
             String msg = "Failed to build path of " + this;
             throw new RepositoryException(msg, e);
@@ -205,7 +207,7 @@
      * @param state   item to find path of
      */
     private void buildPath(Path.PathBuilder builder, ItemState state)
-            throws ItemStateException, RepositoryException {
+        throws ItemNotFoundException {
         NodeState parentState = state.getParent();
         // shortcut for root state
         if (parentState == null) {
@@ -269,7 +271,7 @@
             return;
         }
 
-        if (Status.isTerminalStatus(oldStatus)) {
+        if (Status.isTerminal(oldStatus)) {
             throw new IllegalStateException("State is already in terminal status " + oldStatus);
         }
         if (Status.isValidStatusChange(oldStatus, newStatus, isWorkspaceState)) {
@@ -284,8 +286,8 @@
             la = (ItemStateLifeCycleListener[]) listeners.toArray(new ItemStateLifeCycleListener[listeners.size()]);
         }
         for (int i = 0; i < la.length; i++) {
-            if (la[i] instanceof ItemStateLifeCycleListener) {
-                ((ItemStateLifeCycleListener) la[i]).statusChanged(this, oldStatus);
+            if (la[i] != null) {
+                la[i].statusChanged(this, oldStatus);
             }
         }
         if (status == Status.MODIFIED) {
@@ -304,7 +306,6 @@
      */
     public void addListener(ItemStateLifeCycleListener listener) {
         synchronized (listeners) {
-            assert (!listeners.contains(listener));
             listeners.add(listener);
         }
     }
@@ -320,6 +321,53 @@
         }
     }
 
+    /**
+     * Unmodifiable iterator over the listeners present on this item state.
+     * 
+     * @return
+     */
+    public Iterator getListeners() {
+        return Collections.unmodifiableCollection(listeners).iterator();
+    }
+    //-----------------------------------------< ItemStateLifeCycleListener >---
+    /**
+     *
+     * @param state
+     * @param previousStatus
+     */
+    public void statusChanged(ItemState state, int previousStatus) {
+        checkIsSessionState();
+
+        // the given state is the overlayed state this state (session) is listening to.
+        if (state == overlayedState) {
+            switch (state.getStatus()) {
+                case Status.MODIFIED:
+                    // underlying state has been modified by external changes
+                    if (status == Status.EXISTING) {
+                        synchronized (this) {
+                            reset();
+                        }
+                    } else if (status == Status.EXISTING_MODIFIED) {
+                        setStatus(Status.STALE_MODIFIED);
+                    }
+                    // else: this status is EXISTING_REMOVED => ignore.
+                    // no other status is possible.
+                    break;
+                case Status.REMOVED:
+                    if (status == Status.EXISTING_MODIFIED) {
+                        setStatus(Status.STALE_DESTROYED);
+                    } else {
+                        setStatus(Status.REMOVED);
+                    }
+                    break;
+                default:
+                    // Should never occur, since 'setStatus(int)' already validates
+                    log.error("Workspace state cannot have its state changed to " + state.getStatus());
+                    break;
+            }
+        }
+    }
+
     //--------------------------------------------------------< State types >---
     /**
      * @return true if this state is a workspace state.
@@ -367,28 +415,52 @@
     public boolean hasOverlayedState() {
         return overlayedState != null;
     }
+
     //--------------------------------------------------< Workspace - State >---
     /**
      * Used on 'workspace' states in order to update the state according to
-     * the given event (and ev. changelog).
+     * an external modification indicated by the given event.
      *
      * @param event
-     * @param changeLog
      * @throws IllegalStateException if this state is a 'session' state.
      */
-    abstract void refresh(Event event, ChangeLog changeLog);
+    abstract void refresh(Event event);
 
+    /**
+     * Returns the overlaying item state or <code>null</code> if that state
+     * has not been created yet or has been disconnected.
+     *
+     * @return
+     */
+    ItemState getSessionState() {
+        checkIsWorkspaceState();
+        ItemStateLifeCycleListener[] la;
+        synchronized (listeners) {
+            la = (ItemStateLifeCycleListener[]) listeners.toArray(new ItemStateLifeCycleListener[listeners.size()]);
+        }
+        for (int i = 0; i < la.length; i++) {
+            if (la[i] instanceof ItemState) {
+                return (ItemState) la[i];
+            }
+        }
+        return null;
+    }
 
     //----------------------------------------------------< Session - State >---
     /**
-     * Copy all state information from overlayed state to this state
+     * Used on the target state of a save call AFTER the changelog has been
+     * successfully submitted to the SPI..
+     *
+     * @param events
+     * @param changeLog
+     * @throws IllegalStateException if this state is a 'session' state.
      */
-    abstract void reset();
+    abstract void refresh(Collection events, ChangeLog changeLog) throws IllegalStateException;
 
     /**
-     * Merge the state information from the overlayed state into this state
+     * Copy all state information from overlayed state to this state
      */
-    abstract void merge();
+    abstract void reset();
 
     /**
      * Connect this state to some underlying overlayed state.
@@ -405,18 +477,6 @@
     }
 
     /**
-     * Returns <code>true</code> if this item state represents new or modified
-     * state or <code>false</code> if it represents existing, unmodified state.
-     *
-     * @return <code>true</code> if this item state is modified or new,
-     *         otherwise <code>false</code>
-     */
-    private boolean isTransient() {
-        checkIsSessionState();
-        return status == Status.EXISTING_MODIFIED || status == Status.NEW;
-    }
-
-    /**
      * Removes this item state. This will change the status of this property
      * state to either {@link Status#EXISTING_REMOVED} or {@link
      * Status#REMOVED} depending on the current status.
@@ -440,14 +500,12 @@
      * Checks if this <code>ItemState</code> is transiently modified or new and
      * adds itself to the <code>Set</code> of <code>transientStates</code> if
      * that is the case. It this <code>ItemState</code> has children it will
-     * call the method {@link #collectTransientStates(java.util.Set)} on those
+     * call the method {@link #collectTransientStates(Collection)} on those
      * <code>ItemState</code>s.
      *
      * @param transientStates the <code>Set</code> of transient <code>ItemState</code>,
-     *                        collected while the <code>ItemState</code>
-     *                        hierarchy is traversed.
      */
-    abstract void collectTransientStates(Set transientStates);
+    abstract void collectTransientStates(Collection transientStates);
 
     /**
      * Marks this item state as modified.
@@ -477,46 +535,19 @@
                 throw new IllegalStateException(msg);
         }
     }
-    //-----------------------------------------< ItemStateLifeCycleListener >---
+
+    //--------------------------------------------------------------------------
     /**
      *
-     * @param state
-     * @param previousStatus
+     * @param events
+     * @param processedState
      */
-    public void statusChanged(ItemState state, int previousStatus) {
-        // workspace-states never are listening to another state
-        checkIsSessionState();
-        state.checkIsWorkspaceState();
-
-        switch (state.getStatus()) {
-            case Status.EXISTING:
-                // nothing to do
-                break;
-            case Status.MODIFIED:
-                if (previousStatus == Status.EXISTING) {
-                    // underlying state has been modified
-                    if (isTransient()) {
-                        setStatus(Status.STALE_MODIFIED);
-                    } else {
-                        synchronized (this) {
-                            // this instance represents existing state, update it
-                            merge();
-                            setStatus(Status.EXISTING);
-                        }
-                    }
-                }
-                break;
-            case Status.REMOVED:
-                if (isTransient()) {
-                    setStatus(Status.STALE_DESTROYED);
-                } else {
-                    setStatus(Status.REMOVED);
-                }
-                break;
-            default:
-                // Should never occur, since 'setStatus(int)' already validates
-                log.error("Workspace state cannot have its state changed to " + state.getStatus());
+    static void removeEvent(Collection events, ItemState processedState) {
+        for (Iterator it = events.iterator(); it.hasNext();) {
+            if (((Event)it.next()).getItemId().equals(processedState.getId())) {
+                it.remove();
                 break;
+            }
         }
     }
 }

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateCache.java?view=auto&rev=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateCache.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateCache.java Thu Oct 26 04:02:02 2006
@@ -0,0 +1,96 @@
+/*
+ * 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.state;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.commons.collections.map.ReferenceMap;
+import org.apache.jackrabbit.spi.PropertyId;
+import org.apache.jackrabbit.spi.NodeId;
+
+import java.util.Map;
+
+/**
+ * <code>ItemStateCache</code>...
+ */
+public class ItemStateCache implements ItemStateCreationListener {
+
+    private static Logger log = LoggerFactory.getLogger(ItemStateCache.class);
+
+    /**
+     * Maps a String uuid to a {@link NodeState}.
+     */
+    private final Map uuid2NodeState;
+
+    /**
+     * Creates a new <code>CachingItemStateManager</code>.
+     *
+     */
+    public ItemStateCache() {
+        this.uuid2NodeState = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.WEAK);
+    }
+
+
+    public NodeState getNodeState(String uuid) {
+        return (NodeState) uuid2NodeState.get(uuid);
+    }
+
+    public NodeState getNodeState(NodeId nodeId) {
+        String uuid = nodeId.getUUID();
+        if (uuid != null && nodeId.getPath() == null) {
+            return getNodeState(uuid);
+        } else {
+            // TODO: missing caching for NodeState that are not only identified by uuid.
+            return null;
+        }
+    }
+
+    public PropertyState getPropertyState(PropertyId propertyId) {
+        // TODO: missing caching.
+        return null;
+    }
+    //------------------------------------------< ItemStateCreationListener >---
+
+    public void statusChanged(ItemState state, int previousStatus) {
+        if (Status.isTerminal(state.getStatus())) {
+            if (state.isNode()) {
+                NodeState nodeState = (NodeState) state;
+                String uuid = nodeState.getUUID();
+                if (uuid != null) {
+                    uuid2NodeState.remove(uuid);
+                }
+            }
+            state.removeListener(this);
+        } else {
+            putToCache(state);
+        }
+    }
+
+    public void created(ItemState state) {
+        putToCache(state);
+    }
+
+    private void putToCache(ItemState state) {
+        if (state.isNode() && (state.getStatus() == Status.EXISTING || state.getStatus() == Status.MODIFIED)) {
+            NodeState nodeState = (NodeState) state;
+            String uuid = nodeState.getUUID();
+            if (uuid != null) {
+                uuid2NodeState.put(uuid, nodeState);
+            }
+        }
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateCache.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateCache.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateCreationListener.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateCreationListener.java?view=auto&rev=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateCreationListener.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateCreationListener.java Thu Oct 26 04:02:02 2006
@@ -0,0 +1,29 @@
+/*
+ * 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.state;
+
+/**
+ * <code>ItemStateCreationListener</code>...
+ */
+public interface ItemStateCreationListener extends ItemStateLifeCycleListener {
+
+    /**
+     *
+     * @param state
+     */
+    public void created(ItemState state);
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateCreationListener.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateCreationListener.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateFactory.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateFactory.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateFactory.java Thu Oct 26 04:02:02 2006
@@ -26,6 +26,12 @@
  */
 public interface ItemStateFactory {
 
+    /**
+     *
+     * @param ism
+     * @return
+     * @throws ItemStateException
+     */
     public NodeState createRootState(ItemStateManager ism) throws ItemStateException;
 
     /**
@@ -71,4 +77,13 @@
     public PropertyState createPropertyState(PropertyId propertyId,
                                              NodeState parent)
             throws NoSuchItemStateException, ItemStateException;
+
+
+    /**
+     * Set the cache used to retrieve item states that have already been
+     * built before.
+     *
+     * @param cache
+     */
+    public void setCache(ItemStateCache cache);
 }

Modified: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateValidator.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateValidator.java?view=diff&rev=467956&r1=467955&r2=467956
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateValidator.java (original)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateValidator.java Thu Oct 26 04:02:02 2006
@@ -92,6 +92,7 @@
      */
     public static final int CHECK_COLLISION = 32;
 
+    public static final int CHECK_NONE = 0;
     public static final int CHECK_ALL = CHECK_ACCESS | CHECK_LOCK | CHECK_VERSIONING | CHECK_CONSTRAINTS | CHECK_COLLISION | CHECK_REFERENCES;
 
     /**
@@ -769,16 +770,7 @@
         if (ent.includesNodeType(QName.MIX_REFERENCEABLE)) {
             ItemStateManager stateMgr = mgrProvider.getItemStateManager();
             if (stateMgr.hasReferingStates(targetState)) {
-                try {
-                    if (!stateMgr.getReferingStates(targetState).isEmpty()) {
-                        throw new ReferentialIntegrityException(safeGetJCRPath(targetState)
-                            + ": cannot remove node with references");
-                    }
-                } catch (ItemStateException ise) {
-                    String msg = "internal error: failed to check references on " + safeGetJCRPath(targetState);
-                    log.error(msg, ise);
-                    throw new RepositoryException(msg, ise);
-                }
+                throw new ReferentialIntegrityException(safeGetJCRPath(targetState) + ": cannot remove node with references");
             }
         }
     }