jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dpfis...@apache.org
Subject svn commit: r638398 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/ main/java/org/apache/jackrabbit/core/observation/ main/java/org/apache/jackrabbit/core/persistence/bundle/util/ main/java/org/apache/jackrabbit/core/s...
Date Tue, 18 Mar 2008 15:03:04 GMT
Author: dpfister
Date: Tue Mar 18 08:02:58 2008
New Revision: 638398

URL: http://svn.apache.org/viewvc?rev=638398&view=rev
Log:
JCR-1104 - JSR 283 support
- shareble nodes (work in progress)

Added:
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/BundleBinding.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/ItemStateBinding.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/NodePropBundle.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java
    jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.xml
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestAll.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java Tue Mar 18 08:02:58 2008
@@ -19,6 +19,7 @@
 import org.apache.jackrabbit.core.lock.LockManager;
 import org.apache.jackrabbit.core.nodetype.EffectiveNodeType;
 import org.apache.jackrabbit.core.nodetype.NodeDef;
+import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException;
 import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
 import org.apache.jackrabbit.core.nodetype.PropDef;
 import org.apache.jackrabbit.core.nodetype.PropDefId;
@@ -50,6 +51,7 @@
 import javax.jcr.PropertyType;
 import javax.jcr.ReferentialIntegrityException;
 import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.lock.LockException;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.version.VersionException;
@@ -199,6 +201,88 @@
     }
 
     //-------------------------------------------< high-level item operations >
+    
+    /**
+     * Clones the subtree at the node <code>srcAbsPath</code> in to the new
+     * location at <code>destAbsPath</code>. This operation is only supported:
+     * <ul>
+     * <li>If the source element has the mixin <code>mix:shareable</code> (or some
+     * derived node type)</li>
+     * <li>If the parent node of <code>destAbsPath</code> has not already a shareable
+     * node in the same shared set as the node at <code>srcPath</code>.</li>
+     * </ul>
+     * 
+     * @param srcPath source path
+     * @param destPath destination path
+     * @return the node id of the destination's parent
+     * 
+     * @throws ConstraintViolationException if the operation would violate a
+     * node-type or other implementation-specific constraint.
+     * @throws VersionException if the parent node of <code>destAbsPath</code> is
+     * versionable and checked-in, or is non-versionable but its nearest versionable ancestor is
+     * checked-in. This exception will also be thrown if <code>removeExisting</code> is <code>true</code>,
+     * and a UUID conflict occurs that would require the moving and/or altering of a node that is checked-in.
+     * @throws AccessDeniedException if the current session does not have
+     * sufficient access rights to complete the operation.
+     * @throws PathNotFoundException if the node at <code>srcAbsPath</code> in
+     * <code>srcWorkspace</code> or the parent of <code>destAbsPath</code> in this workspace does not exist.
+     * @throws ItemExistsException if a property already exists at
+     * <code>destAbsPath</code> or a node already exist there, and same name
+     * siblings are not allowed or if <code>removeExisting</code> is false and a
+     * UUID conflict occurs.
+     * @throws LockException if a lock prevents the clone.
+     * @throws RepositoryException if the last element of <code>destAbsPath</code>
+     * has an index or if another error occurs.
+     */
+    public NodeId clone(Path srcPath, Path destPath) 
+            throws ConstraintViolationException, AccessDeniedException,
+                   VersionException, PathNotFoundException, ItemExistsException,
+                   LockException, RepositoryException, IllegalStateException {
+
+        // check precondition
+        checkInEditMode();
+
+        // 1. check paths & retrieve state
+        NodeState srcState = getNodeState(srcPath);
+
+        Path.Element destName = destPath.getNameElement();
+        Path destParentPath = destPath.getAncestor(1);
+        NodeState destParentState = getNodeState(destParentPath);
+        int ind = destName.getIndex();
+        if (ind > 0) {
+            // subscript in name element
+            String msg = "invalid destination path (subscript in name element is not allowed)";
+            log.debug(msg);
+            throw new RepositoryException(msg);
+        }
+        
+        // 2. check access rights, lock status, node type constraints, etc.
+        checkAddNode(destParentState, destName.getName(),
+                srcState.getNodeTypeName(), CHECK_ACCESS | CHECK_LOCK
+                | CHECK_VERSIONING | CHECK_CONSTRAINTS);
+        
+        // 3. verify that source has mixin mix:shareable
+        if (!isShareable(srcState)) {
+            String msg = "Cloning inside a workspace is only allowed for shareable nodes.";
+            log.debug(msg);
+            throw new RepositoryException(msg);
+        }
+        
+        // 4. do clone operation (modify and store affected states)
+        if (!srcState.addShare(destParentState.getNodeId())) {
+            String msg = "Adding a shareable node twice to the same parent is not supported.";
+            log.debug(msg);
+            throw new UnsupportedRepositoryOperationException(msg);
+        }
+        destParentState.addChildNodeEntry(destName.getName(), srcState.getNodeId());
+
+        // store states
+        stateMgr.store(srcState);
+        stateMgr.store(destParentState);
+        return destParentState.getNodeId();
+    }
+
+    
     /**
      * Copies the tree at <code>srcPath</code> to the new location at
      * <code>destPath</code>. Returns the id of the node at its new position.
@@ -268,10 +352,8 @@
             LockException, RepositoryException, IllegalStateException {
 
         // check precondition
-        if (!stateMgr.inEditMode()) {
-            throw new IllegalStateException("not in edit mode");
-        }
-
+        checkInEditMode();
+        
         // 1. check paths & retrieve state
 
         NodeState srcState = getNodeState(srcStateMgr, srcHierMgr, srcPath);
@@ -440,10 +522,19 @@
             destParent.renameChildNodeEntry(srcName.getName(), srcNameIndex,
                     destName.getName());
         } else {
+            // check shareable case
+            if (target.isShareable()) {
+                String msg = "Moving a shareable node is not supported.";
+                log.debug(msg);
+                throw new UnsupportedRepositoryOperationException(msg);
+            }
+
             // remove child node entry from old parent
             srcParent.removeChildNodeEntry(srcName.getName(), srcNameIndex);
+
             // re-parent target node
             target.setParentId(destParent.getNodeId());
+
             // add child node entry to new parent
             destParent.addChildNodeEntry(destName.getName(), target.getNodeId());
         }
@@ -1789,5 +1880,48 @@
             vh = vMgr.createVersionHistory(session, node);
         }
         return vh;
+    }
+    
+    /**
+     * Check that the updatable item state manager is in edit mode.
+     * 
+     * @throws IllegalStateException if it isn't
+     */
+    private void checkInEditMode() throws IllegalStateException {
+        if (!stateMgr.inEditMode()) {
+            throw new IllegalStateException("not in edit mode");
+        }
+    }
+    
+    /**
+     * Determines whether the specified node is <i>shareable</i>, i.e.
+     * whether the mixin type <code>mix:shareable</code> is either
+     * directly assigned or indirectly inherited.
+     *
+     * @param state node state to check
+     * @return true if the specified node is <i>shareable</i>, false otherwise.
+     * @throws ItemStateException if an error occurs
+     */
+    private boolean isShareable(NodeState state) throws RepositoryException {
+        // shortcut: check some wellknown built-in types first
+        Name primary = state.getNodeTypeName();
+        Set mixins = state.getMixinTypeNames();
+        if (mixins.contains(NameConstants.MIX_SHAREABLE)) {
+            return true;
+        }
+        // build effective node type
+        Name[] types = new Name[mixins.size() + 1];
+        mixins.toArray(types);
+        // primary type
+        types[types.length - 1] = primary;
+        
+        try {
+            return ntReg.getEffectiveNodeType(types).includesNodeType(NameConstants.MIX_REFERENCEABLE);
+        } catch (NodeTypeConflictException ntce) {
+            String msg = "internal error: failed to build effective node type for node "
+                    + state.getNodeId();
+            log.debug(msg);
+            throw new RepositoryException(msg, ntce);
+        }
     }
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java Tue Mar 18 08:02:58 2008
@@ -93,6 +93,19 @@
      * @throws RepositoryException
      */
     Name getName(ItemId id) throws ItemNotFoundException, RepositoryException;
+    
+    /**
+     * Returns the name of the specified item, with the given parent id. If the
+     * given item is not shareable, this is identical to {@link #getName(ItemId)}.
+     * 
+     * @param id node id
+     * @param parentId parent node id
+     * @return name
+     * @throws ItemNotFoundException
+     * @throws RepositoryException
+     */
+    Name getName(NodeId id, NodeId parentId) 
+            throws ItemNotFoundException, RepositoryException;
 
     /**
      * Returns the depth of the specified item which is equivalent to

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java Tue Mar 18 08:02:58 2008
@@ -423,7 +423,6 @@
             throws ItemNotFoundException, RepositoryException {
         if (itemId.denotesNode()) {
             NodeId nodeId = (NodeId) itemId;
-            NodeState parentState;
             try {
                 NodeState nodeState = (NodeState) getItemState(nodeId);
                 NodeId parentId = getParentId(nodeState);
@@ -432,7 +431,7 @@
                     // FIXME
                     return EMPTY_NAME;
                 }
-                parentState = (NodeState) getItemState(parentId);
+                return getName(nodeId, parentId);
             } catch (NoSuchItemStateException nsis) {
                 String msg = "failed to resolve name of " + nodeId;
                 log.debug(msg);
@@ -442,20 +441,41 @@
                 log.debug(msg);
                 throw new RepositoryException(msg, ise);
             }
-
-            NodeState.ChildNodeEntry entry =
-                    getChildNodeEntry(parentState, nodeId);
-            if (entry == null) {
-                String msg = "failed to resolve name of " + nodeId;
-                log.debug(msg);
-                throw new RepositoryException(msg);
-            }
-            return entry.getName();
         } else {
             return ((PropertyId) itemId).getName();
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public Name getName(NodeId id, NodeId parentId)
+            throws ItemNotFoundException, RepositoryException {
+
+        NodeState parentState;
+        
+        try {
+            parentState = (NodeState) getItemState(parentId);
+        } catch (NoSuchItemStateException nsis) {
+            String msg = "failed to resolve name of " + id;
+            log.debug(msg);
+            throw new ItemNotFoundException(id.toString());
+        } catch (ItemStateException ise) {
+            String msg = "failed to resolve name of " + id;
+            log.debug(msg);
+            throw new RepositoryException(msg, ise);
+        }
+
+        NodeState.ChildNodeEntry entry =
+                getChildNodeEntry(parentState, id);
+        if (entry == null) {
+            String msg = "failed to resolve name of " + id;
+            log.debug(msg);
+            throw new RepositoryException(msg);
+        }
+        return entry.getName();
+    }
+    
     /**
      * {@inheritDoc}
      */

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java Tue Mar 18 08:02:58 2008
@@ -54,6 +54,7 @@
 import javax.jcr.ReferentialIntegrityException;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.lock.LockException;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.nodetype.NoSuchNodeTypeException;
@@ -73,7 +74,7 @@
 /**
  * <code>ItemImpl</code> implements the <code>Item</code> interface.
  */
-public abstract class ItemImpl implements Item, ItemStateListener {
+public abstract class ItemImpl implements Item {
 
     private static Logger log = LoggerFactory.getLogger(ItemImpl.class);
 
@@ -733,6 +734,47 @@
             }
         }
     }
+    
+    /**
+     * Process all items given in iterator and check whether <code>mix:shareable</code>
+     * or (some derived node type) has been added or removed:
+     * <ul>
+     * <li>If the mixin <code>mix:shareable</code> (or some derived node type),
+     * then initialize the shared set inside the state.</li>
+     * <li>If the mixin <code>mix:shareable</code> (or some derived node type)
+     * has been removed, throw.</li>
+     * </ul>
+     */
+    private void processShareableNodes(Iterator iter) throws RepositoryException {
+        NodeTypeManagerImpl ntMgr = session.getNodeTypeManager();
+        ItemValidator validator = new ItemValidator(ntMgr.getNodeTypeRegistry(),
+                session.getHierarchyManager(), session);
+        while (iter.hasNext()) {
+            ItemState is = (ItemState) iter.next();
+            if (is.isNode()) {
+                NodeState ns = (NodeState) is;
+                boolean wasShareable = false;
+                if (ns.hasOverlayedState()) {
+                    NodeState old = (NodeState) ns.getOverlayedState();
+                    EffectiveNodeType ntOld = validator.getEffectiveNodeType(old);
+                    wasShareable = ntOld.includesNodeType(NameConstants.MIX_SHAREABLE);
+                }
+                EffectiveNodeType ntNew = validator.getEffectiveNodeType(ns);
+                boolean isShareable = ntNew.includesNodeType(NameConstants.MIX_SHAREABLE);
+
+                if (!wasShareable && isShareable) {
+                    // mix:shareable has been added
+                    ns.addShare(ns.getParentId());
+                    
+                } else if (wasShareable && !isShareable) {
+                    // mix:shareable has been removed: not supported
+                    String msg = "Removing mix:shareable is not supported.";
+                    log.debug(msg);
+                    throw new UnsupportedRepositoryOperationException(msg);
+                }
+            }
+        }
+    }
 
     /**
      * Initializes the version history of all new nodes of node type
@@ -1113,7 +1155,11 @@
                             if (newParentId == null) {
                                 // node has been removed, add old parent
                                 // to dependencies
-                                dependentIDs.add(oldParentId);
+                                if (overlayedState.isShareable()) {
+                                    dependentIDs.addAll(overlayedState.getSharedSet());
+                                } else {
+                                    dependentIDs.add(oldParentId);
+                                }
                             } else {
                                 if (!oldParentId.equals(newParentId)) {
                                     // node has been moved to a new location,
@@ -1209,6 +1255,9 @@
 
                 // process transient items marked as 'removed'
                 removeTransientItems(removed.iterator());
+                
+                // process transient items that have change in mixins
+                processShareableNodes(dirty.iterator());
 
                 // initialize version histories for new nodes (might generate new transient state)
                 if (initVersionHistories(dirty.iterator())) {

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java Tue Mar 18 08:02:58 2008
@@ -92,7 +92,12 @@
      * A cache for item instances created by this <code>ItemManager</code>
      */
     private final Map itemCache;
-
+    
+    /**
+     * Shareable node cache.
+     */
+    private final ShareableNodesCache shareableNodesCache;
+    
     /**
      * Creates a new per-session instance <code>ItemManager</code> instance.
      *
@@ -115,6 +120,9 @@
         // setup item cache with weak references to items
         itemCache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.WEAK);
         itemStateProvider.addListener(this);
+        
+        // setup shareable nodes cache
+        shareableNodesCache = new ShareableNodesCache();
     }
 
     /**
@@ -124,6 +132,7 @@
         synchronized (itemCache) {
             itemCache.clear();
         }
+        shareableNodesCache.clear();
     }
 
     private NodeDefinition getDefinition(NodeState state)
@@ -398,6 +407,31 @@
     }
 
     /**
+     * Returns a shareble node with a given id and parent id.
+     * @param id
+     * @return
+     * @throws RepositoryException
+     */
+    public synchronized NodeImpl getNode(NodeId id, NodeId parentId)
+            throws ItemNotFoundException, AccessDeniedException, RepositoryException {
+        // check sanity of session
+        session.sanityCheck();
+        
+        // check shareable nodes
+        NodeImpl node = shareableNodesCache.retrieve(id, parentId);
+        if (node != null) {
+            return node;
+        }
+        
+        node = (NodeImpl) getItem(id);
+        if (!node.getParentId().equals(parentId)) {
+            node = new NodeImpl(node, parentId);
+            shareableNodesCache.cache(node);
+        }
+        return node;
+    }
+
+    /**
      * Returns the item instance for the given item state.
      * @param state the item state
      * @return the item instance for the given item <code>state</code>.
@@ -669,7 +703,11 @@
      */
     private ItemImpl retrieveItem(ItemId id) {
         synchronized (itemCache) {
-            return (ItemImpl) itemCache.get(id);
+            ItemImpl item = (ItemImpl) itemCache.get(id);
+            if (item == null && id.denotesNode()) {
+                item = shareableNodesCache.retrieve((NodeId) id);
+            }
+            return item;
         }
     }
 
@@ -750,6 +788,13 @@
             log.debug("created item " + item.getId());
         }
         // add instance to cache
+        if (item.isNode()) {
+            NodeImpl node = (NodeImpl) item;
+            if (node.isShareable()) {
+                shareableNodesCache.cache(node);
+                return;
+            }
+        }
         cacheItem(item);
     }
 
@@ -762,6 +807,9 @@
         }
         // remove instance from cache
         evictItem(id);
+        if (item.isNode()) {
+            shareableNodesCache.evict((NodeImpl) item);
+        }
     }
 
     /**
@@ -775,6 +823,9 @@
         item.removeLifeCycleListener(this);
         // remove instance from cache
         evictItem(id);
+        if (item.isNode()) {
+            shareableNodesCache.evict((NodeImpl) item);
+        }
     }
 
     //-------------------------------------------------------------< Dumpable >
@@ -845,6 +896,155 @@
         ItemImpl item = retrieveItem(discarded.getId());
         if (item != null) {
             item.stateDiscarded(discarded);
+        }
+    }
+    
+    /**
+     * Invoked by a <code>NodeImpl</code> when it is has become transient
+     * and has therefore replaced its state. Will inform all other nodes
+     * in the shareable set about this change.
+     */
+    public void becameTransient(NodeImpl node) {
+        NodeState state = (NodeState) node.getItemState();
+        
+        NodeImpl n = (NodeImpl) retrieveItem(node.getId());
+        if (n != null && n != node) {
+            n.stateReplaced(state);
+        }
+        shareableNodesCache.stateReplaced(node);
+    }
+
+    /**
+     * Invoked by a <code>NodeImpl</code> when it is has become transient
+     * and has therefore replaced its state. Will inform all other nodes
+     * in the shareable set about this change.
+     */
+    public void persisted(NodeImpl node) {
+        NodeState state = (NodeState) node.getItemState();
+        
+        NodeImpl n = (NodeImpl) retrieveItem(node.getId());
+        if (n != null && n != node) {
+            n.stateReplaced(state);
+        }
+        shareableNodesCache.stateReplaced(node);
+    }
+    
+    /**
+     * Cache of shareable nodes.
+     */
+    class ShareableNodesCache {
+        
+        /**
+         * This cache is based on a reference map, that maps an item id to a map,
+         * which again maps a (hard-ref) parent id to a (weak-ref) shareable node.
+         */
+        private final ReferenceMap cache;
+        
+        /**
+         * Create a new instance of this class.
+         */
+        public ShareableNodesCache() {
+            cache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.HARD);
+        }
+        
+        /**
+         * Clear cache.
+         * 
+         * @see ReferenceMap#clear()
+         */
+        public void clear() {
+            cache.clear();
+        }
+
+        /**
+         * Return the first available node that maps to the given id.
+         * 
+         * @param id node id
+         * @return node or <code>null</code>
+         */
+        public synchronized NodeImpl retrieve(NodeId id) {
+            ReferenceMap map = (ReferenceMap) cache.get(id);
+            if (map != null) {
+                Iterator iter = map.values().iterator();
+                while (iter.hasNext()) {
+                    NodeImpl node = (NodeImpl) iter.next();
+                    if (node != null) {
+                        return node;
+                    }
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Return the node with the given id and parent id.
+         * 
+         * @param id node id
+         * @param parentId parent id
+         * @return node or <code>null</code>
+         */
+        public synchronized NodeImpl retrieve(NodeId id, NodeId parentId) {
+            ReferenceMap map = (ReferenceMap) cache.get(id);
+            if (map != null) {
+                return (NodeImpl) map.get(parentId);
+            }
+            return null;
+        }
+        
+        /**
+         * Cache some node.
+         * 
+         * @param node node to cache
+         */
+        public synchronized void cache(NodeImpl node) {
+            ReferenceMap map = (ReferenceMap) cache.get(node.getId());
+            if (map == null) {
+                map = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.WEAK);
+                cache.put(node.getId(), map);
+            }
+            Object old = map.put(node.getParentId(), node);
+            if (old != null) {
+                log.warn("overwriting cached item: " + old);
+            }
+        }
+        
+        /**
+         * Evict some node from the cache.
+         * 
+         * @param node node to evict
+         */
+        public synchronized void evict(NodeImpl node) {
+            ReferenceMap map = (ReferenceMap) cache.get(node.getId());
+            if (map != null) {
+                map.remove(node.getParentId());
+            }
+        }
+        
+        /**
+         * Evict all nodes with a given node id from the cache.
+         * 
+         * @param id node id to evict
+         */
+        public synchronized void evictAll(NodeId id) {
+            cache.remove(id);
+        }
+
+        /**
+         * TODO SN: document
+         */
+        public synchronized void stateReplaced(NodeImpl node) {
+            NodeState state = (NodeState) node.getItemState();
+
+            ReferenceMap map = (ReferenceMap) cache.get(node.getId());
+            if (map != null) {
+                Iterator iter = map.values().iterator();
+                while (iter.hasNext()) {
+                    NodeImpl n = (NodeImpl) iter.next();
+                    if (n != null && n != node) {
+                        n.stateReplaced(state);
+                    }
+                }
+            }
         }
     }
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java Tue Mar 18 08:02:58 2008
@@ -110,6 +110,11 @@
 
     /** the definition of this node */
     protected NodeDefinition definition;
+    
+    /**
+     * Parent id, if this is a shareable node, <code>null</code> otherwise.
+     */
+    private NodeId parentId;
 
     // flag set in status passed to getOrCreateProperty if property was created
     protected static final short CREATED = 0;
@@ -145,6 +150,20 @@
     }
 
     /**
+     * Protected constructor. Used when creating a node that is a shared
+     * sibling of another node, and that has the same properties, children nodes,
+     * etc. as the other node.
+     */
+    protected NodeImpl(NodeImpl sharedSibling, NodeId parentId) {
+        super(sharedSibling.itemMgr, sharedSibling.session, 
+                sharedSibling.id, sharedSibling.state, null);
+        
+        this.definition = sharedSibling.definition;
+        this.primaryTypeName = sharedSibling.primaryTypeName;
+        this.parentId = parentId;
+    }
+    
+    /**
      * Returns the id of the property at <code>relPath</code> or <code>null</code>
      * if no property exists at <code>relPath</code>.
      * <p/>
@@ -263,6 +282,9 @@
                         stateMgr.createTransientNodeState((NodeState) state, ItemState.STATUS_EXISTING_MODIFIED);
                 // replace persistent with transient state
                 state = transientState;
+                if (isShareable()) {
+                    itemMgr.becameTransient(this);
+                }
             } catch (ItemStateException ise) {
                 String msg = "failed to create transient state";
                 log.debug(msg);
@@ -600,7 +622,7 @@
         // notify target of removal
         NodeId childId = entry.getId();
         NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId);
-        childNode.onRemove();
+        childNode.onRemove(getNodeId());
 
         // remove the child node entry
         if (!thisState.removeChildNodeEntry(nodeName, index)) {
@@ -621,10 +643,25 @@
         definition = newDef;
     }
 
-    protected void onRemove() throws RepositoryException {
+    protected void onRemove(NodeId parentId) throws RepositoryException {
         // modify the state of 'this', i.e. the target node
         NodeState thisState = (NodeState) getOrCreateTransientItemState();
 
+        // remove this node from its shared set
+        if (thisState.isShareable()) {
+            if (thisState.removeShare(parentId) > 0) {
+                // this state is still connected to some parents, so
+                // leave the child node entries and properties
+                
+                // set state of this instance to 'invalid'
+                status = STATUS_INVALIDATED;
+                // notify the listeners that this instance has been
+                // temporarily invalidated
+                notifyInvalidated();
+                return;
+            }
+        }
+        
         if (thisState.hasChildNodeEntries()) {
             // remove child nodes
             // use temp array to avoid ConcurrentModificationException
@@ -636,7 +673,7 @@
                 // recursively remove child node
                 NodeId childId = entry.getId();
                 NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId);
-                childNode.onRemove();
+                childNode.onRemove(thisState.getNodeId());
                 // remove the child node entry
                 thisState.removeChildNodeEntry(entry.getName(), entry.getIndex());
             }
@@ -949,6 +986,8 @@
             persistentState.setChildNodeEntries(transientState.getChildNodeEntries());
             // property entries
             persistentState.setPropertyNames(transientState.getPropertyNames());
+            // shared set
+            persistentState.setSharedSet(transientState.getSharedSet());
 
             // make state persistent
             stateMgr.store(persistentState);
@@ -960,6 +999,10 @@
         state = persistentState;
         // reset status
         status = STATUS_NORMAL;
+        
+        if (isShareable()) {
+            itemMgr.persisted(this);
+        }
     }
 
     protected void restoreTransient(NodeState transientState)
@@ -976,6 +1019,7 @@
         thisState.setDefinitionId(transientState.getDefinitionId());
         thisState.setChildNodeEntries(transientState.getChildNodeEntries());
         thisState.setPropertyNames(transientState.getPropertyNames());
+        thisState.setSharedSet(transientState.getSharedSet());
     }
 
     /**
@@ -1928,7 +1972,15 @@
             return "";
         }
 
-        return session.getJCRName(session.getHierarchyManager().getName(id));
+        HierarchyManager hierMgr = session.getHierarchyManager();
+        Name name;
+        
+        if (!isShareable()) {
+            name = hierMgr.getName(id);
+        } else {
+            name = hierMgr.getName(getNodeId(), getParentId());
+        }
+        return session.getJCRName(name);
     }
 
     /**
@@ -1949,14 +2001,17 @@
         // check state of this instance
         sanityCheck();
 
-        // check if root node
-        NodeId parentId = state.getParentId();
+        // check if shareable node
+        NodeId parentId = this.parentId;
         if (parentId == null) {
-            String msg = "root node doesn't have a parent";
-            log.debug(msg);
-            throw new ItemNotFoundException(msg);
+            // check if root node
+            parentId = state.getParentId();
+            if (parentId == null) {
+                String msg = "root node doesn't have a parent";
+                log.debug(msg);
+                throw new ItemNotFoundException(msg);
+            }
         }
-
         return (Node) itemMgr.getItem(parentId);
     }
 
@@ -2904,7 +2959,7 @@
         // check state of this instance
         sanityCheck();
 
-        NodeId parentId = state.getParentId();
+        NodeId parentId = getParentId();
         if (parentId == null) {
             // the root node cannot have same-name siblings; always return 1
             return 1;
@@ -2922,6 +2977,166 @@
             log.error(msg, ise);
             throw new RepositoryException(msg, ise);
         }
+    }
+    
+    //-------------------------------------------------------< shareable nodes >
+    
+    /**
+     * Returns an iterator over all nodes that are in the shared set of this
+     * node. If this node is not shared then the returned iterator contains
+     * only this node.
+     *  
+     * @return a <code>NodeIterator</code>
+     * @throws RepositoryException if an error occurs.
+     * @since JCR 2.0
+     */
+    public NodeIterator getSharedSet() throws RepositoryException {
+        // check state of this instance
+        sanityCheck();
+        
+        ArrayList list = new ArrayList();
+        
+        if (!isShareable()) {
+            list.add(this);
+        } else {
+            NodeState state = (NodeState) this.state;
+            Iterator iter = state.getSharedSet().iterator();
+            while (iter.hasNext()) {
+                NodeId parentId = (NodeId) iter.next();
+                list.add(itemMgr.getNode(getNodeId(), parentId));
+            }
+        }
+        return new NodeIteratorAdapter(list);
+    }
+
+    /**
+     * A special kind of <code>remove()</code> that removes this node and every
+     * other node in the shared set of this node.
+     * <p/>
+     * This removal must be done atomically, i.e., if one of the nodes cannot be
+     * removed, the function throws the exception <code>remove()</code> would
+     * have thrown in that case, and none of the nodes are removed.
+     * <p/>
+     * If this node is not shared this method removes only this node.
+     *
+     * @throws VersionException
+     * @throws LockException
+     * @throws ConstraintViolationException
+     * @throws RepositoryException
+     * @see #removeShare()
+     * @see Item#remove()
+     * @since JCR 2.0
+     */
+    public void removeSharedSet() throws VersionException, LockException, 
+            ConstraintViolationException, RepositoryException {
+        
+        // check state of this instance
+        sanityCheck();
+
+        NodeIterator iter = getSharedSet();
+        while (iter.hasNext()) {
+            ((NodeImpl) iter.nextNode()).removeShare();
+        }
+    }
+
+    /**
+     * A special kind of <code>remove()</code> that removes this node, but does
+     * not remove any other node in the shared set of this node.
+     * <p/>
+     * All of the exceptions defined for <code>remove()</code> apply to this
+     * function. In addition, a <code>RepositoryException</code> is thrown if
+     * this node cannot be removed without removing another node in the shared
+     * set of this node.
+     * <p/>
+     * If this node is not shared this method removes only this node.
+     *
+     * @throws VersionException
+     * @throws LockException
+     * @throws ConstraintViolationException
+     * @throws RepositoryException
+     * @see #removeSharedSet()
+     * @see Item#remove()
+     * @since JCR 2.0
+     */
+    public void removeShare() throws VersionException, LockException, 
+            ConstraintViolationException, RepositoryException {
+        
+        // check state of this instance
+        sanityCheck();
+
+        // Standard remove() will remove just this node 
+        remove();
+    }
+    
+    /**
+     * Helper method, returning a flag that indicates whether this node is
+     * shareable.
+     * 
+     * @return <code>true</code> if this node is shareable;
+     *         <code>false</code> otherwise.
+     * @see NodeState#isShareable()
+     */
+    protected boolean isShareable() {
+       return ((NodeState) state).isShareable(); 
+    }
+    
+    /**
+     * Helper method, returning the parent id this shareable node is attached
+     * to.
+     * 
+     * @return parent id
+     */
+    public NodeId getParentId() {
+        if (parentId != null) {
+            return parentId;
+        }
+        return state.getParentId();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * Overridden to return a different path for shareable nodes.
+     * 
+     * TODO SN: copies functionality in that is already available in
+     *          HierarchyManagerImpl, namely composing a path by
+     *          concatenating the parent path + this node's name and index:
+     *          rather use hierarchy manager to do this
+     */
+    public Path getPrimaryPath() throws RepositoryException {
+        if (!isShareable()) {
+            return super.getPrimaryPath();
+        }
+        
+        NodeId parentId = getParentId();
+        NodeImpl parentNode = (NodeImpl) getParent();
+        Path parentPath = parentNode.getPrimaryPath();
+        PathBuilder builder = new PathBuilder(parentPath);
+
+        NodeState.ChildNodeEntry entry = ((NodeState) parentNode.getItemState()).
+                getChildNodeEntry(getNodeId());
+        if (entry == null) {
+            String msg = "failed to build path of " + state.getId() + ": "
+                    + parentId + " has no child entry for "
+                    + id;
+            log.debug(msg);
+            throw new ItemNotFoundException(msg);
+        }
+        // add to path
+        if (entry.getIndex() == 1) {
+            builder.addLast(entry.getName());
+        } else {
+            builder.addLast(entry.getName(), entry.getIndex());
+        }
+        return builder.getPath();
+    }
+    
+    /**
+     * Invoked when another node in the same shared set has replaced the
+     * node state.
+     */
+    protected void stateReplaced(NodeState state) {
+        this.state = state;
     }
 
     //------------------------------< versioning support: public Node methods >

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java Tue Mar 18 08:02:58 2008
@@ -1624,6 +1624,13 @@
             // do rename
             destParentNode.renameChildNode(srcName.getName(), index, targetId, destName.getName());
         } else {
+            // check shareable case
+            if (((NodeState) targetNode.getItemState()).isShareable()) {
+                String msg = "Moving a shareable node is not supported.";
+                log.debug(msg);
+                throw new UnsupportedRepositoryOperationException(msg);
+            }
+            
             // do move:
             // 1. remove child node entry from old parent
             NodeState srcParentState =

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java Tue Mar 18 08:02:58 2008
@@ -404,6 +404,79 @@
             }
         }
     }
+    
+    /**
+     * Handles a clone inside the same workspace, which is supported with
+     * shareable nodes.
+     * 
+     * @see {@link #clone()}
+     * 
+     * @param srcAbsPath source path
+     * @param destAbsPath destination path
+     * @return the path of the node at its new position
+     * @throws ConstraintViolationException
+     * @throws AccessDeniedException
+     * @throws VersionException
+     * @throws PathNotFoundException
+     * @throws ItemExistsException
+     * @throws LockException
+     * @throws RepositoryException
+     */
+    private String internalClone(String srcAbsPath, String destAbsPath)
+            throws ConstraintViolationException, AccessDeniedException,
+                   VersionException, PathNotFoundException, ItemExistsException,
+                   LockException, RepositoryException {
+        
+        Path srcPath;
+        try {
+            srcPath = session.getQPath(srcAbsPath).getNormalizedPath();
+        } catch (NameException e) {
+            String msg = "invalid path: " + srcAbsPath;
+            log.debug(msg);
+            throw new RepositoryException(msg, e);
+        }
+        if (!srcPath.isAbsolute()) {
+            throw new RepositoryException("not an absolute path: " + srcAbsPath);
+        }
+
+        Path destPath;
+        try {
+            destPath = session.getQPath(destAbsPath).getNormalizedPath();
+        } catch (NameException e) {
+            String msg = "invalid path: " + destAbsPath;
+            log.debug(msg);
+            throw new RepositoryException(msg, e);
+        }
+        if (!destPath.isAbsolute()) {
+            throw new RepositoryException("not an absolute path: " + destAbsPath);
+        }
+
+        BatchedItemOperations ops = new BatchedItemOperations(
+                stateMgr, rep.getNodeTypeRegistry(), session.getLockManager(),
+                session, hierMgr);
+
+        try {
+            ops.edit();
+        } catch (IllegalStateException e) {
+            String msg = "unable to start edit operation";
+            log.debug(msg);
+            throw new RepositoryException(msg, e);
+        }
+
+        boolean succeeded = false;
+
+        try {
+            ItemId id = ops.clone(srcPath, destPath);
+            ops.update();
+            succeeded = true;
+            return session.getJCRPath(hierMgr.getPath(id));
+        } finally {
+            if (!succeeded) {
+                // update operation failed, cancel all modifications
+                ops.cancel();
+            }
+        }
+    }
 
     /**
      * Return the lock manager for this workspace. If not already done, creates
@@ -1016,10 +1089,8 @@
 
         // check workspace name
         if (getName().equals(srcWorkspace)) {
-            // same as current workspace
-            String msg = srcWorkspace + ": illegal workspace (same as current)";
-            log.debug(msg);
-            throw new RepositoryException(msg);
+            // same as current workspace: is allowed for mix:shareable nodes
+            return internalClone(srcAbsPath, destAbsPath);
         }
 
         // check authorization for specified workspace

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java Tue Mar 18 08:02:58 2008
@@ -162,7 +162,9 @@
                 if (n.hasOverlayedState()) {
                     NodeId oldParentId = n.getOverlayedState().getParentId();
                     NodeId newParentId = n.getParentId();
-                    if (newParentId != null && !oldParentId.equals(newParentId)) {
+                    if (newParentId != null && !oldParentId.equals(newParentId) &&
+                            !n.isShareable()) {
+                        
                         // node moved
                         // generate node removed & node added event
                         NodeState oldParent;

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/BundleBinding.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/BundleBinding.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/BundleBinding.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/BundleBinding.java Tue Mar 18 08:02:58 2008
@@ -131,6 +131,19 @@
         if (version >= VERSION_1) {
             bundle.setModCount(readModCount(in));
         }
+        
+        // read shared set, since version 2.0
+        Set sharedSet = new HashSet();
+        if (version >= VERSION_2) {
+            // shared set (list of parent uuids)
+            NodeId parentId = readID(in);
+            while (parentId != null) {
+                sharedSet.add(parentId);
+                parentId = readID(in);
+            }
+        }
+        bundle.setSharedSet(sharedSet);
+        
         return bundle;
     }
 
@@ -286,6 +299,13 @@
 
         // write mod count
         writeModCount(out, bundle.getModCount());
+        
+        // write shared set
+        iter = bundle.getSharedSet().iterator();
+        while (iter.hasNext()) {
+            writeID(out, (NodeId) iter.next());
+        }
+        writeID(out, null);
 
         // set size of bundle
         bundle.setSize(out.size() - size);

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/ItemStateBinding.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/ItemStateBinding.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/ItemStateBinding.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/ItemStateBinding.java Tue Mar 18 08:02:58 2008
@@ -49,9 +49,14 @@
     public static final int VERSION_1 = 1;
 
     /**
+     * serialization version 2
+     */
+    public static final int VERSION_2 = 2;
+
+    /**
      * current version
      */
-    public static final int VERSION_CURRENT = VERSION_1;
+    public static final int VERSION_CURRENT = VERSION_2;
 
     /**
      * the namespace index
@@ -220,6 +225,13 @@
         if (version >= VERSION_1) {
             state.setModCount(readModCount(in));
         }
+        if (version >= VERSION_2) {
+            // shared set (list of parent uuids)
+            count = in.readInt();   // count
+            for (int i = 0; i < count; i++) {
+                state.addShare(readID(in));
+            }
+        }
         return state;
     }
 
@@ -261,6 +273,13 @@
             writeID(out, entry.getId());  // uuid
         }
         writeModCount(out, state.getModCount());
+        
+        // shared set (list of parent uuids)
+        c = state.getSharedSet();
+        out.writeInt(c.size()); // count
+        for (Iterator iter = c.iterator(); iter.hasNext();) {
+            writeID(out, (NodeId) iter.next());
+        }
     }
 
     /**

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/NodePropBundle.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/NodePropBundle.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/NodePropBundle.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/NodePropBundle.java Tue Mar 18 08:02:58 2008
@@ -109,6 +109,12 @@
      * the size
      */
     private long size = 0;
+    
+    /**
+     * Shared set, consisting of the parent ids of this shareable node. This
+     * entry is <code>null</code> if this node is not shareable.
+     */
+    private Set sharedSet;
 
     /**
      * Creates a "new" bundle with the given id
@@ -152,6 +158,7 @@
             NodeState.ChildNodeEntry cne = (NodeState.ChildNodeEntry) iter.next();
             addChildNodeEntry(cne.getName(), cne.getId());
         }
+        sharedSet = state.getSharedSet();
     }
 
     /**
@@ -182,7 +189,10 @@
         if (isReferenceable) {
             state.addPropertyName(NameConstants.JCR_UUID);
         }
-
+        iter = sharedSet.iterator();
+        while (iter.hasNext()) {
+            state.addShare((NodeId) iter.next());
+        }
         return state;
     }
 
@@ -422,6 +432,22 @@
         if (pe != null) {
             pe.destroy(binding.getBlobStore());
         }
+    }
+    
+    /**
+     * Sets the shared set of this bundle.
+     * @return the shared set of this bundle.
+     */
+    public Set getSharedSet() {
+        return sharedSet;
+    }
+
+    /**
+     * Sets the shared set.
+     * @param sharedSet shared set
+     */
+    public void setSharedSet(Set sharedSet) {
+        this.sharedSet = sharedSet;
     }
 
     /**

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java Tue Mar 18 08:02:58 2008
@@ -35,6 +35,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Set;
@@ -96,6 +97,17 @@
      * different <code>NodeState</code> instances.
      */
     private boolean sharedPropertyNames = false;
+    
+    /**
+     * Shared set, consisting of the parent ids of this shareable node. This
+     * entry is {@link Collections.EMPTY_SET} if this node is not shareable.
+     */
+    private Set sharedSet = Collections.EMPTY_SET; 
+    
+    /**
+     * Flag indicating whether we are using a read-write shared set.
+     */
+    private boolean sharedSetRW; 
 
     /**
      * Listener.
@@ -153,6 +165,7 @@
             if (syncModCount) {
                 setModCount(state.getModCount());
             }
+            sharedSet = nodeState.sharedSet;
         }
     }
 
@@ -557,6 +570,99 @@
      */
     public synchronized void setNodeTypeName(Name nodeTypeName) {
         this.nodeTypeName = nodeTypeName;
+    }
+    
+    /**
+     * Return a flag indicating whether this state is shareable, i.e. whether
+     * there is at least one member inside its shared set.
+     */
+    public synchronized boolean isShareable() {
+        return sharedSet != Collections.EMPTY_SET;
+    }
+    
+    /**
+     * Add a parent to the shared set.
+     * 
+     * @param parentId parent id to add to the shared set
+     * @return <code>true</code> if the parent was successfully added;
+     *         <code>false</code> otherwise
+     */
+    public synchronized boolean addShare(NodeId parentId) {
+        // check first before making changes
+        if (sharedSet.contains(parentId)) {
+            return false;
+        }
+        if (!sharedSetRW) {
+            sharedSet = new LinkedHashSet();
+            sharedSetRW = true;
+        }
+        return sharedSet.add(parentId);
+    }
+    
+    /**
+     * Return a flag whether the given parent id appears in the shared set.
+     * 
+     * @param parentId parent id
+     * @return <code>true</code> if the parent id appears in the shared set;
+     *         <code>false</code> otherwise.
+     */
+    public synchronized boolean containsShare(NodeId parentId) {
+        return sharedSet.contains(parentId);
+    }
+    
+    /**
+     * Return the shared set as an unmodifiable collection.
+     * 
+     * @return unmodifiable collection
+     */
+    public Set getSharedSet() {
+        if (sharedSet != Collections.EMPTY_SET) {
+            return Collections.unmodifiableSet(sharedSet);
+        }
+        return Collections.EMPTY_SET;
+    }
+    
+    /**
+     * Set the shared set of this state to the shared set of another state.
+     * This state will get a deep copy of the shared set given.
+     * 
+     * @param set shared set
+     */
+    public synchronized void setSharedSet(Set set) {
+        if (set != Collections.EMPTY_SET) {
+            sharedSet = new LinkedHashSet(set);
+            sharedSetRW = true;
+        } else {
+            sharedSet = Collections.EMPTY_SET;            
+        }
+    }
+
+    /**
+     * Remove a parent from the shared set. Returns the number of
+     * elements in the shared set. If this number is <code>0</code>,
+     * the shared set is empty, i.e. there are no more parent items
+     * referencing this item and the state is free floating.
+     * 
+     * @param parentId parent id to remove from the shared set
+     * @return the number of elements left in the shared set
+     */
+    public synchronized int removeShare(NodeId parentId) {
+        // check first before making changes
+        if (sharedSet.contains(parentId)) {
+            if (!sharedSetRW) {
+                sharedSet = new LinkedHashSet(sharedSet);
+                sharedSetRW = true;
+            }
+            sharedSet.remove(parentId);
+            if (parentId.equals(this.parentId)) {
+                if (!sharedSet.isEmpty()) {
+                    this.parentId = (NodeId) sharedSet.iterator().next();
+                } else {
+                    this.parentId = null;
+                }
+            }
+        }
+        return sharedSet.size();
     }
 
     //---------------------------------------------------------< diff methods >

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java Tue Mar 18 08:02:58 2008
@@ -37,6 +37,7 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 import javax.jcr.InvalidItemStateException;
 import javax.jcr.ItemNotFoundException;
@@ -426,8 +427,30 @@
                 }
 
                 if (depth < 1) {
-                    // not a descendant
-                    continue;
+                    // TODO SN: move this to HierarchyManager
+                    // if state is shareable, it has actually more than one parent
+                    if (state.isNode()) {
+                        NodeState ns = (NodeState) state;
+                        if (ns.isShareable()) {
+                            Set sharedSet = ns.getSharedSet();
+                            if (ns.hasOverlayedState()) {
+                                sharedSet = ((NodeState) ns.getOverlayedState()).getSharedSet();
+                            }
+                            Iterator sharedParentIds = sharedSet.iterator();
+                            while (sharedParentIds.hasNext()) {
+                                NodeId sharedParentId = (NodeId) sharedParentIds.next();
+                                int depth2 = hierMgr.getRelativeDepth(parentId, sharedParentId);
+                                if (depth2 >= 0) {
+                                    depth = depth2 + 1;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    if (depth < 1) {
+                        // not a descendant
+                        continue;
+                    }
                 }
 
                 // ensure capacity

Modified: jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.xml
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.xml?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.xml (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.xml Tue Mar 18 08:02:58 2008
@@ -335,6 +335,11 @@
         </propertyDefinition>
         <propertyDefinition name="jcr:mergeFailed" requiredType="Reference" autoCreated="false" mandatory="false" onParentVersion="ABORT" protected="true" multiple="true" />
     </nodeType>
+    <nodeType name="mix:shareable" isMixin="true" hasOrderableChildNodes="false" primaryItemName="">
+        <supertypes>
+            <supertype>mix:referenceable</supertype>
+        </supertypes>
+    </nodeType>
 
     <!-- internal node types -->
     <nodeType name="rep:root" isMixin="false" hasOrderableChildNodes="true" primaryItemName="">

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java?rev=638398&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java Tue Mar 18 08:02:58 2008
@@ -0,0 +1,434 @@
+package org.apache.jackrabbit.core;
+
+import java.util.ArrayList;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Workspace;
+import javax.jcr.nodetype.ConstraintViolationException;
+
+import org.apache.jackrabbit.core.nodetype.InvalidNodeTypeDefException;
+import org.apache.jackrabbit.core.nodetype.NodeDef;
+import org.apache.jackrabbit.core.nodetype.NodeDefImpl;
+import org.apache.jackrabbit.core.nodetype.NodeTypeDef;
+import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
+import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NameFactory;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+public class ShareableNodeTest extends AbstractJCRTest {
+
+    private NameFactory factory;
+    private Name testShareable;
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+        
+        factory = NameFactoryImpl.getInstance();
+        testShareable = factory.create("http://www.apache.org/jackrabbit/test", "shareable");
+
+        checkNodeTypes();
+    }
+    
+    private void checkNodeTypes() 
+            throws RepositoryException, InvalidNodeTypeDefException {
+        
+        NodeTypeRegistry ntreg = ((NodeTypeManagerImpl) superuser.getWorkspace().
+                getNodeTypeManager()).getNodeTypeRegistry(); 
+        if (!ntreg.isRegistered(testShareable)) {
+            NodeDefImpl nd = new NodeDefImpl();
+            nd.setAllowsSameNameSiblings(false);
+            nd.setDeclaringNodeType(testShareable);
+            nd.setDefaultPrimaryType(null);
+            nd.setMandatory(false);
+            nd.setName(factory.create("", "*"));
+            nd.setProtected(false);
+            nd.setRequiredPrimaryTypes(new Name[]{NameConstants.NT_BASE});
+            
+            NodeTypeDef ntd = new NodeTypeDef();
+            ntd.setName(testShareable);
+            ntd.setSupertypes(new Name[]{factory.create(Name.NS_NT_URI, "base")});
+            ntd.setOrderableChildNodes(false);
+            ntd.setChildNodeDefs(new NodeDef[] { nd });
+            
+            ntreg.registerNodeType(ntd);
+        }
+    }
+    
+    public void testAddChild() throws Exception {
+        // setup parent nodes and first child 
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node b1 = a1.addNode("b1");
+        testRootNode.save();
+        
+        // add mixin
+        b1.addMixin("mix:shareable");
+        b1.save();
+        
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(), 
+                a2.getPath() + "/b2", true);
+
+        ArrayList list = new ArrayList();
+
+        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
+        while (iter.hasNext()) {
+            list.add(iter.nextNode());
+        }
+        assertEquals(list.size(), 2);
+        b1 = (Node) list.get(0);
+        Node b2 = (Node) list.get(1);
+        
+        b1.addNode("c");
+        assertTrue(b2.isModified());
+        assertTrue(b2.hasNode("c"));
+        b1.save();
+    }
+    
+    public void testAddMixin() throws Exception {
+        // setup parent node and first child 
+        Node a = testRootNode.addNode("a");
+        Node b = a.addNode("b");
+        testRootNode.save();
+        
+        b.addMixin("mix:shareable");
+        b.save();
+    }
+    
+    public void testClone() throws Exception {
+        // setup parent nodes and first child 
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node b1 = a1.addNode("b1");
+        testRootNode.save();
+        
+        // add mixin
+        b1.addMixin("mix:shareable");
+        b1.save();
+        
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(), 
+                a2.getPath() + "/b2", true);
+    }
+    
+    public void testCloneToSameParent() throws Exception {
+        // setup parent nodes and first child 
+        Node a = testRootNode.addNode("a");
+        Node b1 = a.addNode("b1");
+        testRootNode.save();
+        
+        // add mixin
+        b1.addMixin("mix:shareable");
+        b1.save();
+        
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        
+        try {
+            workspace.clone(workspace.getName(), b1.getPath(), 
+                    a.getPath() + "/b2", true);
+            fail("Cloning inside same parent should fail.");
+        } catch (UnsupportedRepositoryOperationException e) {
+            // expected
+        }
+    }
+
+    public void testGetIndex() throws Exception {
+        // setup parent nodes and first child 
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node b1 = a1.addNode("b1");
+        a2.addNode("b");
+        testRootNode.save();
+        
+        // add mixin
+        b1.addMixin("mix:shareable");
+        b1.save();
+        
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(), 
+                a2.getPath() + "/b", true);
+
+        ArrayList list = new ArrayList();
+
+        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
+        while (iter.hasNext()) {
+            list.add(iter.nextNode());
+        }
+        
+        assertEquals(list.size(), 2);
+        b1 = (Node) list.get(0);
+        Node b2 = (Node) list.get(1);
+        assertEquals(b1.getIndex(), 1);
+        assertEquals(b2.getIndex(), 2);
+    }
+    
+    public void testGetName() throws Exception {
+        // setup parent nodes and first child 
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node b1 = a1.addNode("b1");
+        testRootNode.save();
+        
+        // add mixin
+        b1.addMixin("mix:shareable");
+        b1.save();
+        
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(), 
+                a2.getPath() + "/b2", true);
+
+        ArrayList list = new ArrayList();
+
+        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
+        while (iter.hasNext()) {
+            list.add(iter.nextNode());
+        }
+        
+        assertEquals(list.size(), 2);
+        b1 = (Node) list.get(0);
+        Node b2 = (Node) list.get(1);
+        assertEquals(b1.getName(), "b1");
+        assertEquals(b2.getName(), "b2");
+    }
+    
+    public void testGetPath() throws Exception {
+        // setup parent nodes and first child 
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node b1 = a1.addNode("b1");
+        testRootNode.save();
+        
+        // add mixin
+        b1.addMixin("mix:shareable");
+        b1.save();
+        
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(), 
+                a2.getPath() + "/b2", true);
+
+        ArrayList list = new ArrayList();
+
+        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
+        while (iter.hasNext()) {
+            list.add(iter.nextNode());
+        }
+        
+        assertEquals(list.size(), 2);
+        b1 = (Node) list.get(0);
+        Node b2 = (Node) list.get(1);
+        assertEquals(b1.getPath(), "/testroot/a1/b1");
+        assertEquals(b2.getPath(), "/testroot/a2/b2");
+    }
+
+    public void testIsSame() throws Exception {
+        // setup parent nodes and first child 
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node b1 = a1.addNode("b1");
+        testRootNode.save();
+        
+        // add mixin
+        b1.addMixin("mix:shareable");
+        b1.save();
+        
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(), 
+                a2.getPath() + "/b2", true);
+
+        ArrayList list = new ArrayList();
+
+        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
+        while (iter.hasNext()) {
+            list.add(iter.nextNode());
+        }
+        
+        assertEquals(list.size(), 2);
+        b1 = (Node) list.get(0);
+        Node b2 = (Node) list.get(1);
+        assertTrue(b1.isSame(b2));
+        assertTrue(b2.isSame(b1));
+    }
+    
+    public void testRemoveShare() throws Exception {
+        // setup parent nodes and first child 
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node b1 = a1.addNode("b1");
+        testRootNode.save();
+        
+        // add mixin
+        b1.addMixin("mix:shareable");
+        b1.save();
+        
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(), 
+                a2.getPath() + "/b2", true);
+
+        ArrayList list = new ArrayList();
+
+        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
+        while (iter.hasNext()) {
+            list.add(iter.nextNode());
+        }
+        
+        assertEquals(list.size(), 2);
+        b1 = (Node) list.get(0);
+        Node b2 = (Node) list.get(1);
+        assertTrue(b1.isSame(b2));
+        assertTrue(b2.isSame(b1));
+        
+        ((NodeImpl) b1).removeShare();
+        a1.save();
+    }
+
+    public void testRemoveSharedSet() throws Exception {
+        // setup parent nodes and first child 
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node b1 = a1.addNode("b1");
+        testRootNode.save();
+        
+        // add mixin
+        b1.addMixin("mix:shareable");
+        b1.save();
+        
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(), 
+                a2.getPath() + "/b2", true);
+
+        ((NodeImpl) b1).removeSharedSet();
+        testRootNode.save();
+    }
+    
+    public void testRemoveSharedSetSaveOneParentOnly() throws Exception {
+        // setup parent nodes and first child 
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node b1 = a1.addNode("b1");
+        testRootNode.save();
+        
+        // add mixin
+        b1.addMixin("mix:shareable");
+        b1.save();
+        
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(), 
+                a2.getPath() + "/b2", true);
+
+        ((NodeImpl) b1).removeSharedSet();
+        
+        try {
+            a1.save();
+            fail("Removing a shared set requires saving all parents.");
+        } catch (ConstraintViolationException e) {
+            // expected 
+        }
+    }
+
+    public void testIterateSharedSet() throws Exception {
+        // setup parent nodes and first child 
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node b1 = a1.addNode("b1");
+        testRootNode.save();
+        
+        // add mixin
+        b1.addMixin("mix:shareable");
+        b1.save();
+        
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(), 
+                a2.getPath() + "/b2", true);
+        
+        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
+        int items = 0;
+        while (iter.hasNext()) {
+            iter.nextNode();
+            items++;
+        }
+        assertEquals(items, 2);
+    }
+
+    public void testMoveShareableNode() throws Exception {
+        // setup parent nodes and first childs 
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node b = a1.addNode("b");
+        testRootNode.save();
+        
+        // add mixin
+        b.addMixin("mix:shareable");
+        b.save();
+
+        // move
+        Workspace workspace = b.getSession().getWorkspace();
+
+        try {
+            workspace.move(b.getPath(), a2.getPath() + "/b");
+            fail("Moving a mix:shareable should fail.");
+        } catch (UnsupportedRepositoryOperationException e) {
+            // expected
+        }
+    }
+    
+    public void testTransientMoveShareableNode() throws Exception {
+        // setup parent nodes and first childs 
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node b = a1.addNode("b");
+        testRootNode.save();
+        
+        // add mixin
+        b.addMixin("mix:shareable");
+        b.save();
+
+        // move
+        Session session = superuser;
+        
+        try {
+            session.move(b.getPath(), a2.getPath() + "/b");
+            session.save();
+            fail("Moving a mix:shareable should fail.");
+        } catch (UnsupportedRepositoryOperationException e) {
+            // expected
+        }
+    }
+
+    public void testRemoveMixin() throws Exception {
+        // setup parent node and first child 
+        Node a = testRootNode.addNode("a");
+        Node b = a.addNode("b");
+        testRootNode.save();
+        
+        // add mixin
+        b.addMixin("mix:shareable");
+        b.save();
+        
+        // remove mixin
+        try {
+            b.removeMixin("mix:shareable");
+            b.save();
+            fail("Removing mix:shareable should fail.");
+        } catch (UnsupportedRepositoryOperationException e) {
+            // expected
+        }
+    }
+}

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestAll.java?rev=638398&r1=638397&r2=638398&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestAll.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestAll.java Tue Mar 18 08:02:58 2008
@@ -34,6 +34,7 @@
 
         suite.addTestSuite(CachingHierarchyManagerTest.class);
         suite.addTestSuite(NamespaceRegistryImplTest.class);
+        suite.addTestSuite(ShareableNodeTest.class);
         suite.addTestSuite(TransientRepositoryTest.class);
         suite.addTestSuite(XATest.class);
 



Mime
View raw message