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 2008/04/04 10:16:55 UTC

svn commit: r644638 - in /jackrabbit/trunk: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ jackrabbit-spi-commons/src/mai...

Author: dpfister
Date: Fri Apr  4 01:16:48 2008
New Revision: 644638

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

Added:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/PropertyWrapper.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/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/LazyItemIterator.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/xml/AbstractSAXEventGenerator.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/NameConstants.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=644638&r1=644637&r2=644638&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 Fri Apr  4 01:16:48 2008
@@ -203,7 +203,7 @@
     }
 
     //-------------------------------------------< 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:
@@ -213,11 +213,11 @@
      * <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
@@ -236,7 +236,7 @@
      * @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) 
+    public NodeId clone(Path srcPath, Path destPath)
             throws ConstraintViolationException, AccessDeniedException,
                    VersionException, PathNotFoundException, ItemExistsException,
                    LockException, RepositoryException, IllegalStateException {
@@ -257,36 +257,73 @@
             log.debug(msg);
             throw new RepositoryException(msg);
         }
-        
+
+        return clone(srcState, destParentState, destName.getName());
+    }
+
+    /**
+     * Implementation of {@link #clone(Path, Path)} that has already determined
+     * the affected <code>NodeState</code>s.
+     *
+     * @param srcState source state
+     * @param destParentState destination parent state
+     * @param destName destination name
+     * @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.
+     * @see #clone(Path, Path)
+     */
+    public NodeId clone(NodeState srcState, NodeState destParentState, Name destName)
+            throws ConstraintViolationException, AccessDeniedException,
+                   VersionException, PathNotFoundException, ItemExistsException,
+                   LockException, RepositoryException, IllegalStateException {
+
+
         // 2. check access rights, lock status, node type constraints, etc.
-        checkAddNode(destParentState, destName.getName(),
+        checkAddNode(destParentState, destName,
                 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. detect share cycle
         NodeId srcId = srcState.getNodeId();
         NodeId destParentId = destParentState.getNodeId();
-        if (destParentId.equals(srcId) || 
+        if (destParentId.equals(srcId) ||
                 hierMgr.isAncestor(srcId, destParentId)) {
             String msg = "This would create a share cycle.";
             log.debug(msg);
             throw new RepositoryException(msg);
         }
-        
+
         // 5. 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());
+        destParentState.addChildNodeEntry(destName, srcState.getNodeId());
 
         // store states
         stateMgr.store(srcState);
@@ -294,7 +331,7 @@
         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.
@@ -365,7 +402,7 @@
 
         // check precondition
         checkInEditMode();
-        
+
         // 1. check paths & retrieve state
 
         NodeState srcState = getNodeState(srcStateMgr, srcHierMgr, srcPath);
@@ -1925,10 +1962,10 @@
         }
         return vh;
     }
-    
+
     /**
      * Check that the updatable item state manager is in edit mode.
-     * 
+     *
      * @throws IllegalStateException if it isn't
      */
     private void checkInEditMode() throws IllegalStateException {
@@ -1936,7 +1973,7 @@
             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
@@ -1958,7 +1995,7 @@
         mixins.toArray(types);
         // primary type
         types[types.length - 1] = primary;
-        
+
         try {
             return ntReg.getEffectiveNodeType(types).includesNodeType(NameConstants.MIX_REFERENCEABLE);
         } catch (NodeTypeConflictException ntce) {

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=644638&r1=644637&r2=644638&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 Fri Apr  4 01:16:48 2008
@@ -142,7 +142,6 @@
                 addLifeCycleListener(listeners[i]);
             }
         }
-        notifyCreated();
     }
 
     /**
@@ -215,7 +214,7 @@
      * Notify the listeners that this instance has been discarded
      * (i.e. it has been temporarily rendered 'invalid').
      */
-    private void notifyCreated() {
+    protected void notifyCreated() {
         // copy listeners to array to avoid ConcurrentModificationException
         ItemLifeCycleListener[] la =
                 (ItemLifeCycleListener[]) listeners.values().toArray(

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=644638&r1=644637&r2=644638&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 Fri Apr  4 01:16:48 2008
@@ -92,12 +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.
      *
@@ -120,7 +120,7 @@
         // 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();
     }
@@ -411,8 +411,12 @@
     }
 
     /**
-     * Returns a shareble node with a given id and parent id.
-     * @param id
+     * Returns a node with a given id and parent id. If the indicated node is
+     * shareable, there might be multiple nodes associated with the same id,
+     * but only one node with the given parent id.
+     *
+     * @param id node id
+     * @param
      * @return
      * @throws RepositoryException
      */
@@ -420,17 +424,24 @@
             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);
+            // verify that parent actually appears in the shared set
+            if (!node.hasSharedParent(parentId)) {
+                String msg = "Node with id '" + id
+                        + "' does not have shared parent with id: " + parentId;
+                throw new ItemNotFoundException(msg);
+            }
+
+            node = new NodeImpl(node, parentId, new ItemLifeCycleListener[] { this });
+            node.notifyCreated();
         }
         return node;
     }
@@ -522,7 +533,7 @@
             }
         }
 
-        return new LazyItemIterator(this, childIds);
+        return new LazyItemIterator(this, childIds, parentId);
     }
 
     /**
@@ -624,18 +635,21 @@
         // we want to be informed on life cycle changes of the new node object
         // in order to maintain item cache consistency
         ItemLifeCycleListener[] listeners = new ItemLifeCycleListener[]{this};
+        NodeImpl node = null;
 
         // check special nodes
         if (state.getNodeTypeName().equals(NameConstants.NT_VERSION)) {
-            return createVersionInstance(id, state, def, listeners);
+            node = createVersionInstance(id, state, def, listeners);
 
         } else if (state.getNodeTypeName().equals(NameConstants.NT_VERSIONHISTORY)) {
-            return createVersionHistoryInstance(id, state, def, listeners);
+            node = createVersionHistoryInstance(id, state, def, listeners);
 
         } else {
             // create node object
-            return new NodeImpl(this, session, id, state, def, listeners);
+            node = new NodeImpl(this, session, id, state, def, listeners);
         }
+        node.notifyCreated();
+        return node;
     }
 
     NodeImpl createNodeInstance(NodeState state) throws RepositoryException {
@@ -651,8 +665,10 @@
         // in order to maintain item cache consistency
         ItemLifeCycleListener[] listeners = new ItemLifeCycleListener[]{this};
         // create property object
-        return new PropertyImpl(
+        PropertyImpl property = new PropertyImpl(
                 this, session, state.getPropertyId(), state, def, listeners);
+        property.notifyCreated();
+        return property;
     }
 
     PropertyImpl createPropertyInstance(PropertyState state)
@@ -737,13 +753,15 @@
      * Removes a cache entry for a specific item.
      *
      * @param id id of the item to remove from the cache
+     * @return <code>true</code> if the item was contained in this cache,
+     *         <code>false</code> otherwise.
      */
-    private void evictItem(ItemId id) {
+    private boolean evictItem(ItemId id) {
         if (log.isDebugEnabled()) {
             log.debug("removing item " + id + " from cache");
         }
         synchronized (itemCache) {
-            itemCache.remove(id);
+            return itemCache.remove(id) != null;
         }
     }
 
@@ -901,15 +919,17 @@
             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.
+     * Invoked by a shareable <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.
+     *
+     * @param node node that has changed its underlying state
      */
     public void becameTransient(NodeImpl node) {
         NodeState state = (NodeState) node.getItemState();
-        
+
         NodeImpl n = (NodeImpl) retrieveItem(node.getId());
         if (n != null && n != node) {
             n.stateReplaced(state);
@@ -918,41 +938,49 @@
     }
 
     /**
-     * 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.
+     * Invoked by a shareable <code>NodeImpl</code> when it is has become
+     * persistent and has therefore replaced its state. Will inform all other
+     * nodes in the shareable set about this change.
+     *
+     * @param node node that has changed its underlying state
      */
     public void persisted(NodeImpl node) {
+        // item has possibly become shareable on this call: move it
+        // from the main cache to the cache of shareable nodes
+        if (evictItem(node.getNodeId())) {
+            shareableNodesCache.cache(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() {
@@ -961,7 +989,7 @@
 
         /**
          * Return the first available node that maps to the given id.
-         * 
+         *
          * @param id node id
          * @return node or <code>null</code>
          */
@@ -981,7 +1009,7 @@
 
         /**
          * Return the node with the given id and parent id.
-         * 
+         *
          * @param id node id
          * @param parentId parent id
          * @return node or <code>null</code>
@@ -993,10 +1021,10 @@
             }
             return null;
         }
-        
+
         /**
          * Cache some node.
-         * 
+         *
          * @param node node to cache
          */
         public synchronized void cache(NodeImpl node) {
@@ -1010,10 +1038,10 @@
                 log.warn("overwriting cached item: " + old);
             }
         }
-        
+
         /**
          * Evict some node from the cache.
-         * 
+         *
          * @param node node to evict
          */
         public synchronized void evict(NodeImpl node) {
@@ -1022,10 +1050,10 @@
                 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) {
@@ -1035,7 +1063,7 @@
         /**
          * Replace the state of all nodes that are in the same shared set
          * as the given node.
-         * 
+         *
          * @param node node in shared set.
          */
         public synchronized void stateReplaced(NodeImpl node) {

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java?rev=644638&r1=644637&r2=644638&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java Fri Apr  4 01:16:48 2008
@@ -55,6 +55,9 @@
     /** the list of item ids */
     private final List idList;
 
+    /** parent node id (when returning children nodes) or <code>null</code> */
+    private final NodeId parentId;
+
     /** the position of the next item */
     private int pos;
 
@@ -68,8 +71,22 @@
      * @param idList  list of item id's
      */
     public LazyItemIterator(ItemManager itemMgr, List idList) {
+        this(itemMgr, idList, null);
+    }
+
+    /**
+     * Creates a new <code>LazyItemIterator</code> instance, additionally taking
+     * a parent id as parameter. This version should be invoked to strictly return
+     * children nodes of a node.
+     *
+     * @param itemMgr item manager
+     * @param idList  list of item id's
+     * @param parentId parent id.
+     */
+    public LazyItemIterator(ItemManager itemMgr, List idList, NodeId parentId) {
         this.itemMgr = itemMgr;
         this.idList = new ArrayList(idList);
+        this.parentId = parentId;
         // prefetch first item
         pos = 0;
         prefetchNext();
@@ -87,7 +104,11 @@
         while (next == null && pos < idList.size()) {
             ItemId id = (ItemId) idList.get(pos);
             try {
-                next = itemMgr.getItem(id);
+                if (parentId != null) {
+                    next = itemMgr.getNode((NodeId) id, parentId);
+                } else {
+                    next = itemMgr.getItem(id);
+                }
             } catch (ItemNotFoundException e) {
                 log.debug("ignoring nonexistent item " + id);
                 // remove invalid id

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=644638&r1=644637&r2=644638&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 Fri Apr  4 01:16:48 2008
@@ -110,11 +110,11 @@
 
     /** the definition of this node */
     protected NodeDefinition definition;
-    
+
     /**
-     * Parent id, if this is a shareable node, <code>null</code> otherwise.
+     * Primary parent id, if this is a shareable node, <code>null</code> otherwise.
      */
-    private NodeId parentId;
+    private NodeId primaryParentId;
 
     // flag set in status passed to getOrCreateProperty if property was created
     protected static final short CREATED = 0;
@@ -147,6 +147,9 @@
                     + state.getNodeTypeName() + "' of node " + safeGetJCRPath());
             primaryTypeName = NameConstants.NT_UNSTRUCTURED;
         }
+        if (isShareable()) {
+            this.primaryParentId = state.getParentId();
+        }
     }
 
     /**
@@ -154,15 +157,17 @@
      * 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);
-        
+    protected NodeImpl(NodeImpl sharedSibling, NodeId parentId,
+                       ItemLifeCycleListener[] listeners) {
+
+        super(sharedSibling.itemMgr, sharedSibling.session,
+                sharedSibling.id, sharedSibling.state, listeners);
+
         this.definition = sharedSibling.definition;
         this.primaryTypeName = sharedSibling.primaryTypeName;
-        this.parentId = parentId;
+        this.primaryParentId = parentId;
     }
-    
+
     /**
      * Returns the id of the property at <code>relPath</code> or <code>null</code>
      * if no property exists at <code>relPath</code>.
@@ -621,7 +626,7 @@
 
         // notify target of removal
         NodeId childId = entry.getId();
-        NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId);
+        NodeImpl childNode = itemMgr.getNode(childId, getNodeId());
         childNode.onRemove(getNodeId());
 
         // remove the child node entry
@@ -652,7 +657,7 @@
             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
@@ -661,7 +666,7 @@
                 return;
             }
         }
-        
+
         if (thisState.hasChildNodeEntries()) {
             // remove child nodes
             // use temp array to avoid ConcurrentModificationException
@@ -672,7 +677,8 @@
                         (NodeState.ChildNodeEntry) tmp.get(i);
                 // recursively remove child node
                 NodeId childId = entry.getId();
-                NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId);
+                //NodeImpl childNode = (NodeImpl) itemMgr.getItem(childId);
+                NodeImpl childNode = itemMgr.getNode(childId, getNodeId());
                 childNode.onRemove(thisState.getNodeId());
                 // remove the child node entry
                 thisState.removeChildNodeEntry(entry.getName(), entry.getIndex());
@@ -693,7 +699,7 @@
 
         // finally remove this node
         thisState.setParentId(null);
-        itemMgr.getItem(id).setRemoved();
+        setRemoved();
     }
 
     protected NodeImpl internalAddNode(String relPath, NodeTypeImpl nodeType)
@@ -999,8 +1005,12 @@
         state = persistentState;
         // reset status
         status = STATUS_NORMAL;
-        
+
         if (isShareable()) {
+            // if node has become shareable, set its primary parent id
+            if (primaryParentId == null) {
+                primaryParentId = state.getParentId();
+            }
             itemMgr.persisted(this);
         }
     }
@@ -1455,7 +1465,7 @@
             throw new ItemNotFoundException();
         }
         try {
-            return (NodeImpl) itemMgr.getItem(cne.getId());
+            return itemMgr.getNode(cne.getId(), getNodeId());
         } catch (AccessDeniedException ade) {
             throw new ItemNotFoundException();
         }
@@ -1974,7 +1984,7 @@
 
         HierarchyManager hierMgr = session.getHierarchyManager();
         Name name;
-        
+
         if (!isShareable()) {
             name = hierMgr.getName(id);
         } else {
@@ -2002,7 +2012,7 @@
         sanityCheck();
 
         // check if shareable node
-        NodeId parentId = this.parentId;
+        NodeId parentId = this.primaryParentId;
         if (parentId == null) {
             // check if root node
             parentId = state.getParentId();
@@ -2525,7 +2535,10 @@
             throw new PathNotFoundException(relPath);
         }
         try {
-            return (Node) itemMgr.getItem(id);
+            if (((NodeState) state).hasChildNodeEntry(id)) {
+                return itemMgr.getNode(id, getNodeId());
+            }
+            return (NodeImpl) itemMgr.getItem(id);
         } catch (AccessDeniedException ade) {
             throw new PathNotFoundException(relPath);
         } catch (ItemNotFoundException infe) {
@@ -2978,14 +2991,14 @@
             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
@@ -2993,9 +3006,9 @@
     public NodeIterator getSharedSet() throws RepositoryException {
         // check state of this instance
         sanityCheck();
-        
+
         ArrayList list = new ArrayList();
-        
+
         if (!isShareable()) {
             list.add(this);
         } else {
@@ -3027,9 +3040,9 @@
      * @see Item#remove()
      * @since JCR 2.0
      */
-    public void removeSharedSet() throws VersionException, LockException, 
+    public void removeSharedSet() throws VersionException, LockException,
             ConstraintViolationException, RepositoryException {
-        
+
         // check state of this instance
         sanityCheck();
 
@@ -3058,46 +3071,60 @@
      * @see Item#remove()
      * @since JCR 2.0
      */
-    public void removeShare() throws VersionException, LockException, 
+    public void removeShare() throws VersionException, LockException,
             ConstraintViolationException, RepositoryException {
-        
+
         // check state of this instance
         sanityCheck();
 
-        // Standard remove() will remove just this node 
+        // 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(); 
+       return ((NodeState) state).isShareable();
     }
-    
+
     /**
-     * Helper method, returning the parent id this shareable node is attached
-     * to.
-     * 
+     * Helper method, returning the parent id this node is attached to. If this
+     * node is shareable, it returns the primary parent id (which remains
+     * fixed). Otherwise returns the underlying state's parent id.
+     *
      * @return parent id
      */
-    public NodeId getParentId() {
-        if (parentId != null) {
-            return parentId;
+    protected NodeId getParentId() {
+        if (primaryParentId != null) {
+            return primaryParentId;
         }
         return state.getParentId();
     }
 
     /**
+     * Helper method, returning a flag indicating whether this node has
+     * the given shared parent.
+     *
+     * @param parentId parent id
+     * @return <code>true</code> if the node has the given shared parent;
+     *         <code>false</code> otherwise.
+     */
+    protected boolean hasSharedParent(NodeId parentId) {
+        return ((NodeState) state).containsShare(parentId);
+    }
+
+
+    /**
      * {@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:
@@ -3107,7 +3134,7 @@
         if (!isShareable()) {
             return super.getPrimaryPath();
         }
-        
+
         NodeId parentId = getParentId();
         NodeImpl parentNode = (NodeImpl) getParent();
         Path parentPath = parentNode.getPrimaryPath();
@@ -3130,7 +3157,7 @@
         }
         return builder.getPath();
     }
-    
+
     /**
      * Invoked when another node in the same shared set has replaced the
      * node state.

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AbstractSAXEventGenerator.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AbstractSAXEventGenerator.java?rev=644638&r1=644637&r2=644638&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AbstractSAXEventGenerator.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AbstractSAXEventGenerator.java Fri Apr  4 01:16:48 2008
@@ -17,8 +17,10 @@
 package org.apache.jackrabbit.core.xml;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
@@ -28,6 +30,7 @@
 import org.apache.jackrabbit.spi.commons.namespace.SessionNamespaceResolver;
 import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.value.StringValue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.xml.sax.ContentHandler;
@@ -41,6 +44,8 @@
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.NamespaceException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
 
 /**
  * <code>AbstractSAXEventGenerator</code> serves as the base class for
@@ -113,6 +118,21 @@
     protected final String jcrXMLCharacters;
 
     /**
+     * The mix:shareable node type.
+     */
+    private final String mixShareable;
+
+    /**
+     * The nt:share node type.
+     */
+    private final String ntShare;
+
+    /**
+     * Shareable nodes serialized so far (set of UUIDs).
+     */
+    private Set shareableNodeIds;
+
+    /**
      * Constructor
      *
      * @param node           the node state which should be serialized
@@ -148,6 +168,8 @@
             jcrRoot = resolver.getJCRName(NameConstants.JCR_ROOT);
             jcrXMLText = resolver.getJCRName(NameConstants.JCR_XMLTEXT);
             jcrXMLCharacters = resolver.getJCRName(NameConstants.JCR_XMLCHARACTERS);
+            mixShareable = resolver.getJCRName(NameConstants.MIX_SHAREABLE);
+            ntShare = resolver.getJCRName(NameConstants.NT_SHARE);
         } catch (NamespaceException e) {
             // should never get here...
             String msg = "internal error: failed to resolve namespace mappings";
@@ -269,6 +291,7 @@
      */
     protected void process(Node node, int level)
             throws RepositoryException, SAXException {
+
         // enter node
         entering(node, level);
 
@@ -276,12 +299,7 @@
         enteringProperties(node, level);
 
         // Collect all properties (and sort them, see JCR-1084)
-        SortedMap properties = new TreeMap();
-        PropertyIterator propIter = node.getProperties();
-        while (propIter.hasNext()) {
-            Property property = propIter.nextProperty();
-            properties.put(property.getName(), property);
-        }
+        SortedMap properties = collectProperties(node);
 
         // serialize jcr:primaryType, jcr:mixinTypes & jcr:uuid first:
         if (properties.containsKey(jcrPrimaryType)) {
@@ -325,6 +343,54 @@
 
         // leaving node
         leaving(node, level);
+    }
+
+    /**
+     * Collect all properties of a node and return them as sorted map. Returns
+     * a smaller set if the node is shareable and has already been serialized.
+     *
+     * @param node node
+     * @return properties as sorted map
+     */
+    protected SortedMap collectProperties(Node node) throws RepositoryException {
+        SortedMap properties = new TreeMap();
+
+        // if node is shareable and has already been serialized, change its
+        // type to nt:share and process only the properties jcr:primaryType
+        // and jcr:uuid
+        if (node.isNodeType(mixShareable)) {
+            if (shareableNodeIds == null) {
+                shareableNodeIds = new HashSet();
+            }
+            if (!shareableNodeIds.add(node.getUUID())) {
+                if (node.hasProperty(jcrPrimaryType)) {
+                    Property property = node.getProperty(jcrPrimaryType);
+                    property = new PropertyWrapper(property) {
+                        public Value getValue() throws ValueFormatException,
+                                RepositoryException {
+                            return new StringValue(ntShare);
+                        }
+                    };
+                    properties.put(property.getName(), property);
+                } else {
+                    throw new RepositoryException(
+                            "Missing jcr:primaryType property: " + node.getPath());
+                }
+                if (node.hasProperty(jcrUUID)) {
+                    Property property = node.getProperty(jcrUUID);
+                    properties.put(property.getName(), property);
+                }
+                return properties;
+            }
+        }
+
+        // standard behaviour: return all properties
+        PropertyIterator propIter = node.getProperties();
+        while (propIter.hasNext()) {
+            Property property = propIter.nextProperty();
+            properties.put(property.getName(), property);
+        }
+        return properties;
     }
 
     /**

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/PropertyWrapper.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/PropertyWrapper.java?rev=644638&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/PropertyWrapper.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/PropertyWrapper.java Fri Apr  4 01:16:48 2008
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.xml;
+
+import java.io.InputStream;
+import java.util.Calendar;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.Item;
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.ItemVisitor;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.ReferentialIntegrityException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.PropertyDefinition;
+import javax.jcr.version.VersionException;
+
+/**
+ * Provides a wrapper to an existing <code>Property</code> implementation.
+ * Methods default to calling through to the wrapped property object.
+ */
+class PropertyWrapper implements Property {
+
+    /** Wrapped property */
+    private final Property property;
+
+    /**
+     * Constructs a property adaptor wrapping the given property.
+     *
+     * @param property property to wrap
+     */
+    public PropertyWrapper(Property property) {
+        this.property = property;
+    }
+
+    /** {@inheritDoc} */
+    public boolean getBoolean() throws ValueFormatException,
+            RepositoryException {
+        return property.getBoolean();
+    }
+
+    /** {@inheritDoc} */
+    public Calendar getDate() throws ValueFormatException, RepositoryException {
+        return property.getDate();
+    }
+
+    /** {@inheritDoc} */
+    public PropertyDefinition getDefinition() throws RepositoryException {
+        return property.getDefinition();
+    }
+
+    /** {@inheritDoc} */
+    public double getDouble() throws ValueFormatException, RepositoryException {
+        return property.getDouble();
+    }
+
+    /** {@inheritDoc} */
+    public long getLength() throws ValueFormatException, RepositoryException {
+        return property.getLength();
+    }
+
+    /** {@inheritDoc} */
+    public long[] getLengths() throws ValueFormatException, RepositoryException {
+        return property.getLengths();
+    }
+
+    /** {@inheritDoc} */
+    public long getLong() throws ValueFormatException, RepositoryException {
+        return property.getLong();
+    }
+
+    /** {@inheritDoc} */
+    public Node getNode() throws ValueFormatException, RepositoryException {
+        return property.getNode();
+    }
+
+    /** {@inheritDoc} */
+    public InputStream getStream() throws ValueFormatException,
+            RepositoryException {
+        return property.getStream();
+    }
+
+    /** {@inheritDoc} */
+    public String getString() throws ValueFormatException, RepositoryException {
+        return property.getString();
+    }
+
+    /** {@inheritDoc} */
+    public int getType() throws RepositoryException {
+        return property.getType();
+    }
+
+    /** {@inheritDoc} */
+    public Value getValue() throws ValueFormatException, RepositoryException {
+        return property.getValue();
+    }
+
+    /** {@inheritDoc} */
+    public Value[] getValues() throws ValueFormatException, RepositoryException {
+        return property.getValues();
+    }
+
+    /** {@inheritDoc} */
+    public void setValue(Value value) throws ValueFormatException,
+            VersionException, LockException, ConstraintViolationException,
+            RepositoryException {
+        property.setValue(value);
+    }
+
+    /** {@inheritDoc} */
+    public void setValue(Value[] values) throws ValueFormatException,
+            VersionException, LockException, ConstraintViolationException,
+            RepositoryException {
+        property.setValue(values);
+    }
+
+    /** {@inheritDoc} */
+    public void setValue(String value) throws ValueFormatException,
+            VersionException, LockException, ConstraintViolationException,
+            RepositoryException {
+        property.setValue(value);
+    }
+
+    /** {@inheritDoc} */
+    public void setValue(String[] values) throws ValueFormatException,
+            VersionException, LockException, ConstraintViolationException,
+            RepositoryException {
+        property.setValue(values);
+    }
+
+    /** {@inheritDoc} */
+    public void setValue(InputStream value) throws ValueFormatException,
+            VersionException, LockException, ConstraintViolationException,
+            RepositoryException {
+        property.setValue(value);
+    }
+
+    /** {@inheritDoc} */
+    public void setValue(long value) throws ValueFormatException,
+            VersionException, LockException, ConstraintViolationException,
+            RepositoryException {
+        property.setValue(value);
+    }
+
+    /** {@inheritDoc} */
+    public void setValue(double value) throws ValueFormatException,
+            VersionException, LockException, ConstraintViolationException,
+            RepositoryException {
+        property.setValue(value);
+    }
+
+    /** {@inheritDoc} */
+    public void setValue(Calendar value) throws ValueFormatException,
+            VersionException, LockException, ConstraintViolationException,
+            RepositoryException {
+        property.setValue(value);
+    }
+
+    /** {@inheritDoc} */
+    public void setValue(boolean value) throws ValueFormatException,
+            VersionException, LockException, ConstraintViolationException,
+            RepositoryException {
+        property.setValue(value);
+    }
+
+    /** {@inheritDoc} */
+    public void setValue(Node value) throws ValueFormatException,
+            VersionException, LockException, ConstraintViolationException,
+            RepositoryException {
+        property.setValue(value);
+    }
+
+    /** {@inheritDoc} */
+    public void accept(ItemVisitor visitor) throws RepositoryException {
+        property.accept(visitor);
+    }
+
+    /** {@inheritDoc} */
+    public Item getAncestor(int depth) throws ItemNotFoundException,
+            AccessDeniedException, RepositoryException {
+        return property.getAncestor(depth);
+    }
+
+    /** {@inheritDoc} */
+    public int getDepth() throws RepositoryException {
+        return property.getDepth();
+    }
+
+    /** {@inheritDoc} */
+    public String getName() throws RepositoryException {
+        return property.getName();
+    }
+
+    /** {@inheritDoc} */
+    public Node getParent() throws ItemNotFoundException,
+            AccessDeniedException, RepositoryException {
+        return property.getParent();
+    }
+
+    /** {@inheritDoc} */
+    public String getPath() throws RepositoryException {
+        return property.getPath();
+    }
+
+    /** {@inheritDoc} */
+    public Session getSession() throws RepositoryException {
+        return property.getSession();
+    }
+
+    /** {@inheritDoc} */
+    public boolean isModified() {
+        return property.isModified();
+    }
+
+    /** {@inheritDoc} */
+    public boolean isNew() {
+        return property.isNew();
+    }
+
+    public boolean isNode() {
+        return property.isNode();
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSame(Item otherItem) throws RepositoryException {
+        return property.isSame(otherItem);
+    }
+
+    /** {@inheritDoc} */
+    public void refresh(boolean keepChanges) throws InvalidItemStateException,
+            RepositoryException {
+        refresh(keepChanges);
+    }
+
+    /** {@inheritDoc} */
+    public void remove() throws VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        property.remove();
+    }
+
+    /** {@inheritDoc} */
+    public void save() throws AccessDeniedException, ItemExistsException,
+            ConstraintViolationException, InvalidItemStateException,
+            ReferentialIntegrityException, VersionException, LockException,
+            NoSuchNodeTypeException, RepositoryException {
+        property.save();
+    }
+}

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java?rev=644638&r1=644637&r2=644638&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java Fri Apr  4 01:16:48 2008
@@ -159,6 +159,12 @@
                 refTracker.mappedUUID(nodeInfo.getId().getUUID(), node.getNodeId().getUUID());
             }
         } else if (uuidBehavior == ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW) {
+            // if existing node is shareable, then instead of failing, create
+            // new node and share with existing
+            if (conflicting.isShareable()) {
+                itemOps.clone(conflicting, parent, nodeInfo.getName());
+                return null;
+            }
             String msg = "a node with uuid " + nodeInfo.getId()
                     + " already exists!";
             log.debug(msg);
@@ -468,6 +474,13 @@
                         NodeState conflicting = itemOps.getNodeState(id);
                         // resolve uuid conflict
                         node = resolveUUIDConflict(parent, conflicting, nodeInfo);
+                        if (node == null) {
+                            // no new node has been created, so skip this node
+                            parents.push(null); // push null onto stack for skipped node
+                            succeeded = true;
+                            log.debug("skipping existing node: " + nodeName);
+                            return;
+                        }
                     } catch (ItemNotFoundException e) {
                         // create new with given uuid
                         NodeDef def = itemOps.findApplicableNodeDefinition(
@@ -493,7 +506,6 @@
             }
 
             // process properties
-
             Iterator iter = propInfos.iterator();
             while (iter.hasNext()) {
                 PropInfo pi = (PropInfo) iter.next();

Modified: 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=644638&r1=644637&r2=644638&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java Fri Apr  4 01:16:48 2008
@@ -16,8 +16,14 @@
  */
 package org.apache.jackrabbit.core;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 
+import javax.jcr.ImportUUIDBehavior;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
@@ -133,9 +139,100 @@
     }
 
     /**
+     * Verify that the shareable node returned by Node.getNode() has the right
+     * name.
+     */
+    public void testGetNode() 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", false);
+
+        // a1.getNode("b1") should return b1
+        b1 = a1.getNode("b1");
+        assertEquals("b1", b1.getName());
+
+        // a2.getNode("b2") should return b2
+        Node b2 = a2.getNode("b2");
+        assertEquals("b2", b2.getName());
+    }
+
+    /**
+     * Verify that the shareable nodes returned by Node.getNodes() have
+     * the right name.
+     */
+    public void testGetNodes() 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", false);
+
+        // a1.getNodes() should return b1
+        Node[] children = toArray(a1.getNodes());
+        assertEquals(1, children.length);
+        assertEquals("b1", children[0].getName());
+
+        // a2.getNodes() should return b2
+        children = toArray(a2.getNodes());
+        assertEquals(1, children.length);
+        assertEquals("b2", children[0].getName());
+    }
+
+    /**
+     * Verify that the shareable nodes returned by Node.getNodes(String) have
+     * the right name.
+     */
+    public void testGetNodesByPattern() 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", false);
+
+        // a1.getNodes(*) should return b1
+        Node[] children = toArray(a1.getNodes("*"));
+        assertEquals(1, children.length);
+        assertEquals("b1", children[0].getName());
+
+        // a2.getNodes(*) should return b2
+        children = toArray(a2.getNodes("*"));
+        assertEquals(1, children.length);
+        assertEquals("b2", children[0].getName());
+    }
+
+    /**
      * Checks new API Node.getSharedSet() (6.13.1)
      */
-    public void testIterateSharedSet() throws Exception {
+    public void testGetSharedSet() throws Exception {
         // setup parent nodes and first child
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
@@ -388,6 +485,60 @@
     }
 
     /**
+     * Verify import and export (6.13.14).
+     */
+    public void testImportExport() throws Exception {
+        // setup parent nodes and first child
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node a3 = testRootNode.addNode("a3");
+        Node b1 = a1.addNode("b1");
+        testRootNode.save();
+
+        // add mixin
+        b1.addMixin("mix:shareable");
+        b1.save();
+
+        // clone
+        Session session = b1.getSession();
+        Workspace workspace = session.getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(),
+                a2.getPath() + "/b2", false);
+
+        // add child c to shareable nodes b1 & b2
+        b1.addNode("c");
+        b1.save();
+
+        // create temp file
+        File tmpFile = File.createTempFile("test", null);
+        tmpFile.deleteOnExit();
+
+        // export system view of /a1/b1
+        OutputStream out = new FileOutputStream(tmpFile);
+        try {
+            session.exportSystemView(b1.getPath(), out, false, false);
+        } finally {
+            out.close();
+        }
+
+        // and import again underneath /a3
+        InputStream in = new FileInputStream(tmpFile);
+        try {
+            workspace.importXML(a3.getPath(), in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
+        } finally {
+            in.close();
+        }
+
+        // verify there's another element in the shared set
+        Node[] shared = getSharedSet(b1);
+        assertEquals(3, shared.length);
+
+        // verify child c has not been duplicated
+        Node[] children = toArray(b1.getNodes());
+        assertEquals(1, children.length);
+    }
+
+    /**
      * Verifies that observation events are sent only once (6.13.15).
      */
     public void testObservation() throws Exception {
@@ -554,6 +705,40 @@
     }
 
     /**
+     * Clone a mix:shareable node to the same workspace multiple times, remove
+     * all parents and save.
+     */
+    public void testCloneMultipleTimes() throws Exception {
+        int count = 10;
+        Node[] parents = new Node[count];
+
+        // setup parent nodes and first child
+        for (int i = 0; i < parents.length; i++) {
+            parents[i] = testRootNode.addNode("a" + (i + 1));
+        }
+        Node b = parents[0].addNode("b");
+        testRootNode.save();
+
+        // add mixin
+        b.addMixin("mix:shareable");
+        b.save();
+
+        Workspace workspace = b.getSession().getWorkspace();
+
+        // clone to all other nodes
+        for (int i = 1; i < parents.length; i++) {
+            workspace.clone(workspace.getName(), b.getPath(),
+                    parents[i].getPath() + "/b", false);
+        }
+
+        // remove all parents and save
+        for (int i = 0; i < parents.length; i++) {
+            parents[i].remove();
+        }
+        testRootNode.save();
+    }
+
+    /**
      * Verifies that Node.isSame returns <code>true</code> for shareable nodes
      * in the same shared set (6.13.21)
      */
@@ -736,9 +921,18 @@
      * @return array of nodes in shared set
      */
     private Node[] getSharedSet(Node n) throws RepositoryException {
+        return toArray(((NodeImpl) n).getSharedSet());
+    }
+
+    /**
+     * Return an array of nodes given a <code>NodeIterator</code>.
+     *
+     * @param iter node iterator
+     * @return node array
+     */
+    private static Node[] toArray(NodeIterator iter) {
         ArrayList list = new ArrayList();
 
-        NodeIterator iter = ((NodeImpl) n).getSharedSet();
         while (iter.hasNext()) {
             list.add(iter.nextNode());
         }

Modified: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/NameConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/NameConstants.java?rev=644638&r1=644637&r2=644638&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/NameConstants.java (original)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/NameConstants.java Fri Apr  4 01:16:48 2008
@@ -348,6 +348,11 @@
     public static final Name NT_QUERY = FACTORY.create(Name.NS_NT_URI, "query");
 
     /**
+     * nt:share
+     */
+    public static final Name NT_SHARE = FACTORY.create(Name.NS_NT_URI, "share");
+
+    /**
      * mix:referenceable
      */
     public static final Name MIX_REFERENCEABLE = FACTORY.create(Name.NS_MIX_URI, "referenceable");