You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by dp...@apache.org on 2005/06/13 10:45:50 UTC

svn commit: r190372 [2/2] - in /incubator/jackrabbit/trunk/src: java/org/apache/jackrabbit/core/ java/org/apache/jackrabbit/core/lock/ java/org/apache/jackrabbit/core/state/ test/org/apache/jackrabbit/test/api/lock/

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/NodeState.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/NodeState.java?rev=190372&r1=190371&r2=190372&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/NodeState.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/NodeState.java Mon Jun 13 01:45:49 2005
@@ -19,6 +19,8 @@
 import org.apache.jackrabbit.core.NodeId;
 import org.apache.jackrabbit.core.QName;
 import org.apache.jackrabbit.core.nodetype.NodeDefId;
+import org.apache.commons.collections.map.ReferenceMap;
+import org.apache.commons.collections.MapIterator;
 
 import java.io.IOException;
 import java.io.ObjectInputStream;
@@ -30,8 +32,8 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
+import java.util.Map;
 
 /**
  * <code>NodeState</code> represents the state of a <code>Node</code>.
@@ -62,6 +64,12 @@
     protected List propertyEntries = new ArrayList();
 
     /**
+     * Listeners (weak references)
+     */
+    private final transient ReferenceMap listeners =
+            new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK);
+
+    /**
      * Constructor
      *
      * @param overlayedState the backing node state being overlayed
@@ -70,8 +78,9 @@
      */
     public NodeState(NodeState overlayedState, int initialStatus,
                      boolean isTransient) {
-        super(overlayedState, initialStatus, isTransient);
+        super(initialStatus, isTransient);
 
+        connect(overlayedState);
         pull();
     }
 
@@ -400,7 +409,30 @@
      * @return the newly added <code>ChildNodeEntry<code>
      */
     public synchronized ChildNodeEntry addChildNodeEntry(QName nodeName, String uuid) {
-        return childNodeEntries.add(nodeName, uuid);
+        ChildNodeEntry entry = childNodeEntries.add(nodeName, uuid);
+        notifyNodeAdded(entry);
+        return entry;
+    }
+
+    /**
+     * Renames a new <code>ChildNodeEntry<code>.
+     *
+     * @param oldName <code>QName<code> object specifying the entry's old name
+     * @param index 1-based index if there are same-name child node entries
+     * @param newName <code>QName<code> object specifying the entry's new name
+     * @return <code>true</code> if the entry was sucessfully renamed;
+     *         otherwise <code>false</code>
+     */
+    public synchronized boolean renameChildNodeEntry(QName oldName, int index,
+                                                     QName newName) {
+        ChildNodeEntry oldEntry = childNodeEntries.remove(oldName, index);
+        if (oldEntry != null) {
+            ChildNodeEntry newEntry = addChildNodeEntry(newName, oldEntry.getUUID());
+            notifyNodeAdded(newEntry);
+            notifyNodeRemoved(oldEntry);
+            return true;
+        }
+        return false;
     }
 
     /**
@@ -412,7 +444,11 @@
      *         in the list of child node entries and could be removed.
      */
     public synchronized boolean removeChildNodeEntry(QName nodeName, int index) {
-        return childNodeEntries.remove(nodeName, index);
+        ChildNodeEntry entry = childNodeEntries.remove(nodeName, index);
+        if (entry != null) {
+            notifyNodeRemoved(entry);
+        }
+        return entry != null;
     }
 
     /**
@@ -429,6 +465,7 @@
     public synchronized void setChildNodeEntries(List nodeEntries) {
         childNodeEntries.removeAll();
         childNodeEntries.addAll(nodeEntries);
+        notifyNodesReplaced();
     }
 
     /**
@@ -715,6 +752,85 @@
         this.parentUUID = parentUUID;
     }
 
+    /**
+     * @see ItemState#addListener
+     *
+     * If the listener passed is at the same time a <code>NodeStateListener</code>
+     * we add it to our list of specialized listeners.
+     */
+    public void addListener(ItemStateListener listener) {
+        if (listener instanceof NodeStateListener) {
+            synchronized (listeners) {
+                if (!listeners.containsKey(listener)) {
+                    listeners.put(listener, listener);
+                }
+            }
+        }
+        super.addListener(listener);
+    }
+
+    /**
+     * @see ItemState#removeListener
+     *
+     * If the listener passed is at the same time a <code>NodeStateListener</code>
+     * we remove it from our list of specialized listeners.
+     */
+    public void removeListener(ItemStateListener listener) {
+        if (listener instanceof NodeStateListener) {
+            synchronized (listeners) {
+                listeners.remove(listener);
+            }
+        }
+        super.removeListener(listener);
+    }
+
+    /**
+     * Notify the listeners that some child node was added
+     */
+    protected void notifyNodeAdded(ChildNodeEntry added) {
+        synchronized (listeners) {
+            MapIterator iter = listeners.mapIterator();
+            while (iter.hasNext()) {
+                NodeStateListener l = (NodeStateListener) iter.next();
+                if (l != null) {
+                    l.nodeAdded(this, added.getName(),
+                            added.getIndex(), added.getUUID());
+                }
+            }
+        }
+    }
+
+    /**
+     * Notify the listeners that the children nodes were replaced
+     */
+    protected void notifyNodesReplaced() {
+        synchronized (listeners) {
+            MapIterator iter = listeners.mapIterator();
+            while (iter.hasNext()) {
+                NodeStateListener l = (NodeStateListener) iter.next();
+                if (l != null) {
+                    l.nodesReplaced(this);
+                }
+            }
+        }
+    }
+
+    /**
+     * Notify the listeners that some child node was removed
+     */
+    protected void notifyNodeRemoved(ChildNodeEntry removed) {
+        synchronized (listeners) {
+            MapIterator iter = listeners.mapIterator();
+            while (iter.hasNext()) {
+                NodeStateListener l = (NodeStateListener) iter.next();
+                if (l != null) {
+                    l.nodeRemoved(this, removed.getName(),
+                            removed.getIndex(), removed.getUUID());
+                }
+            }
+        }
+    }
+
     //-------------------------------------------------< Serializable support >
     private void writeObject(ObjectOutputStream out) throws IOException {
         // delegate to default implementation
@@ -776,19 +892,19 @@
         }
 
         public boolean remove(ChildNodeEntry entry) {
-            return remove(entry.getName(), entry.getIndex());
+            return remove(entry.getName(), entry.getIndex()) != null;
         }
 
-        public boolean remove(QName nodeName, int index) {
+        public ChildNodeEntry remove(QName nodeName, int index) {
             if (index < 1) {
                 throw new IllegalArgumentException("index is 1-based");
             }
             List siblings = (List) names.get(nodeName);
             if (siblings == null) {
-                return false;
+                return null;
             }
             if (index > siblings.size()) {
-                return false;
+                return null;
             }
             // remove from siblings list
             ChildNodeEntry removedEntry = (ChildNodeEntry) siblings.remove(index - 1);
@@ -798,7 +914,7 @@
             if (siblings.size() == 0) {
                 // short cut
                 names.remove(nodeName);
-                return true;
+                return removedEntry;
             }
 
             // update indices of subsequent same-name siblings
@@ -811,7 +927,7 @@
                 entries.set(entries.indexOf(oldEntry), newEntry);
             }
 
-            return true;
+            return removedEntry;
         }
 
         List get(QName nodeName) {

Added: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/NodeStateListener.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/NodeStateListener.java?rev=190372&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/NodeStateListener.java (added)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/NodeStateListener.java Mon Jun 13 01:45:49 2005
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ *                     as applicable.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.state;
+
+import org.apache.jackrabbit.core.QName;
+
+/**
+ * Extends the <code>ItemStateListener</code> allowing a client to be
+ * additionally informed about changes on a <code>NodeState</code>.
+ *
+ * @see NodeState#addListener
+ */
+public interface NodeStateListener extends ItemStateListener {
+
+    /**
+     * Called when a child node has been added
+     * @param state node state that changed
+     * @param name name of node that was added
+     * @param index index of new node
+     * @param uuid uuid of new node
+     */
+    public void nodeAdded(NodeState state,
+                          QName name, int index, String uuid);
+
+    /**
+     * Called when the children nodes were replaced by other nodes, typically
+     * as result of a reorder operation.
+     * @param state node state that changed
+     */
+    public void nodesReplaced(NodeState state);
+
+    /**
+     * Called when a child node has been removed
+     * @param state node state that changed
+     * @param name name of node that was removed
+     * @param index index of removed node
+     * @param uuid uuid of removed node
+     */
+    public void nodeRemoved(NodeState state,
+                            QName name, int index, String uuid);
+}

Propchange: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/NodeStateListener.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/PropertyState.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/PropertyState.java?rev=190372&r1=190371&r2=190372&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/PropertyState.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/PropertyState.java Mon Jun 13 01:45:49 2005
@@ -56,8 +56,9 @@
      */
     public PropertyState(PropertyState overlayedState, int initialStatus,
                          boolean isTransient) {
-        super(overlayedState, initialStatus, isTransient);
+        super(initialStatus, isTransient);
 
+        connect(overlayedState);
         pull();
     }
 

Modified: incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java?rev=190372&r1=190371&r2=190372&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java (original)
+++ incubator/jackrabbit/trunk/src/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java Mon Jun 13 01:45:49 2005
@@ -19,7 +19,6 @@
 import org.apache.jackrabbit.core.CachingHierarchyManager;
 import org.apache.jackrabbit.core.Constants;
 import org.apache.jackrabbit.core.HierarchyManager;
-import org.apache.jackrabbit.core.HierarchyManagerImpl;
 import org.apache.jackrabbit.core.ItemId;
 import org.apache.jackrabbit.core.MalformedPathException;
 import org.apache.jackrabbit.core.NamespaceResolver;
@@ -63,7 +62,7 @@
     /**
      * Hierarchy manager
      */
-    private HierarchyManager hierMgr;
+    private CachingHierarchyManager hierMgr;
 
     /**
      * Creates a new <code>SessionItemStateManager</code> instance.
@@ -81,33 +80,18 @@
         // create transient item state manager
         transientStateMgr = new TransientItemStateManager();
         // create hierarchy manager that uses both transient and persistent state
-        hierMgr = new HierarchyManagerImpl(rootNodeUUID, this, nsResolver,
-                transientStateMgr.getAttic());
+        hierMgr = new CachingHierarchyManager(rootNodeUUID, this,
+                nsResolver, transientStateMgr.getAttic());
     }
 
     /**
      * En-/Disable chaching of path values.
      * <p/>
-     * Please keep in mind that the cache of <code>Path</code>s is not automatically
-     * updated when the underlying hierarchy is changing. Therefore it should only be
-     * turned on with caution and in special situations (usually only locally
-     * within a narrow scope) where the underlying hierarchy is not expected to
-     * change.
-     *
-     * @param enable
+     * Paths are always cached, therefore this method has no implementation.
+     * @param enable <code>true</code> to enable caching;
+     *               <code>false</code> to disable
      */
     public void enablePathCaching(boolean enable) {
-        if (enable) {
-            if (!(hierMgr instanceof CachingHierarchyManager)) {
-                hierMgr = new CachingHierarchyManager(hierMgr);
-            }
-        } else {
-            if (hierMgr instanceof CachingHierarchyManager) {
-                CachingHierarchyManager chm = (CachingHierarchyManager) hierMgr;
-                chm.clearCache();
-                hierMgr = chm.unwrap();
-            }
-        }
     }
 
     /**
@@ -231,6 +215,20 @@
     }
 
     /**
+     * Customized variant of {@link #createNew(String, QName, String)} that
+     * connects the newly created persistent state with the transient state.
+     */
+    public NodeState createNew(NodeState transientState)
+            throws IllegalStateException {
+
+        NodeState persistentState = createNew(transientState.getUUID(),
+                transientState.getNodeTypeName(),
+                transientState.getParentUUID());
+        transientState.connect(persistentState);
+        return persistentState;
+    }
+
+    /**
      * {@inheritDoc}
      */
     public PropertyState createNew(QName propName, String parentUUID)
@@ -239,6 +237,20 @@
     }
 
     /**
+     * Customized variant of {@link #createNew(String, QName, String)} that
+     * connects the newly created persistent state with the transient state.
+     */
+    public PropertyState createNew(PropertyState transientState)
+            throws IllegalStateException {
+
+        PropertyState persistentState = createNew(transientState.getName(),
+                transientState.getParentUUID());
+        transientState.connect(persistentState);
+        return persistentState;
+    }
+
+
+    /**
      * {@inheritDoc}
      */
     public void store(ItemState state) throws IllegalStateException {
@@ -566,7 +578,11 @@
      */
     public NodeState createTransientNodeState(NodeState overlayedState, int initialStatus)
             throws ItemStateException {
-        return transientStateMgr.createNodeState(overlayedState, initialStatus);
+
+        NodeState state = transientStateMgr.createNodeState(
+                overlayedState, initialStatus);
+        hierMgr.stateOverlaid(state);
+        return state;
     }
 
     /**
@@ -589,7 +605,22 @@
      */
     public PropertyState createTransientPropertyState(PropertyState overlayedState, int initialStatus)
             throws ItemStateException {
-        return transientStateMgr.createPropertyState(overlayedState, initialStatus);
+
+        PropertyState state = transientStateMgr.createPropertyState(
+                overlayedState, initialStatus);
+        hierMgr.stateOverlaid(state);
+        return state;
+    }
+
+    /**
+     * Disconnect a transient item state from its underlying persistent state.
+     * Notifies the <code>HierarchyManager</code> about the changed identity.
+     * @param state the transient <code>ItemState</code> instance that should
+     *              be disconnected
+     */
+    public void disconnectTransientItemState(ItemState state) {
+        hierMgr.stateUncovered(state);
+        state.disconnect();
     }
 
     /**

Modified: incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/lock/LockTest.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/lock/LockTest.java?rev=190372&r1=190371&r2=190372&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/lock/LockTest.java (original)
+++ incubator/jackrabbit/trunk/src/test/org/apache/jackrabbit/test/api/lock/LockTest.java Mon Jun 13 01:45:49 2005
@@ -580,6 +580,35 @@
     }
 
     /**
+     * Tests if locks are maintained when child nodes are reordered
+     */
+    public void testReorder2() throws Exception {
+        // create three lockable nodes with same name
+        Node testNode = testRootNode.addNode(nodeName1);
+        testNode.addMixin(mixLockable);
+        testNode = testRootNode.addNode(nodeName1);
+        testNode.addMixin(mixLockable);
+        testNode = testRootNode.addNode(nodeName1);
+        testNode.addMixin(mixLockable);
+        testRootNode.save();
+
+        // lock first node (1)
+        testRootNode.getNode(nodeName1 + "[1]").lock(false, true);
+
+        // assert: first node locked
+        assertTrue("First child node locked",
+                testRootNode.getNode(nodeName1 + "[1]").isLocked());
+
+        // move first node to last
+        testRootNode.orderBefore(nodeName1 + "[1]", null);
+        testRootNode.save();
+
+        // assert: third node locked
+        assertTrue("Third child node locked",
+                testRootNode.getNode(nodeName1 + "[3]").isLocked());
+    }
+
+    /**
      * Return a flag indicating whether the indicated session contains
      * a specific lock token
      */