Return-Path: Delivered-To: apmail-jackrabbit-commits-archive@www.apache.org Received: (qmail 70870 invoked from network); 18 Mar 2008 15:03:56 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 18 Mar 2008 15:03:56 -0000 Received: (qmail 99750 invoked by uid 500); 18 Mar 2008 15:03:53 -0000 Delivered-To: apmail-jackrabbit-commits-archive@jackrabbit.apache.org Received: (qmail 99723 invoked by uid 500); 18 Mar 2008 15:03:53 -0000 Mailing-List: contact commits-help@jackrabbit.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@jackrabbit.apache.org Delivered-To: mailing list commits@jackrabbit.apache.org Received: (qmail 99714 invoked by uid 99); 18 Mar 2008 15:03:53 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 18 Mar 2008 08:03:53 -0700 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO eris.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 18 Mar 2008 15:03:15 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 3947A1A9832; Tue, 18 Mar 2008 08:03:26 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit 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 -0000 To: commits@jackrabbit.apache.org From: dpfister@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20080318150326.3947A1A9832@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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 srcAbsPath in to the new + * location at destAbsPath. This operation is only supported: + *
    + *
  • If the source element has the mixin mix:shareable (or some + * derived node type)
  • + *
  • If the parent node of destAbsPath has not already a shareable + * node in the same shared set as the node at srcPath.
  • + *
+ * + * @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 destAbsPath is + * versionable and checked-in, or is non-versionable but its nearest versionable ancestor is + * checked-in. This exception will also be thrown if removeExisting is true, + * 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 srcAbsPath in + * srcWorkspace or the parent of destAbsPath in this workspace does not exist. + * @throws ItemExistsException if a property already exists at + * destAbsPath or a node already exist there, and same name + * siblings are not allowed or if removeExisting is false and a + * UUID conflict occurs. + * @throws LockException if a lock prevents the clone. + * @throws RepositoryException if the last element of destAbsPath + * 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 srcPath to the new location at * destPath. 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 shareable, i.e. + * whether the mixin type mix:shareable is either + * directly assigned or indirectly inherited. + * + * @param state node state to check + * @return true if the specified node is shareable, 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 @@ /** * ItemImpl implements the Item 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 mix:shareable + * or (some derived node type) has been added or removed: + *
    + *
  • If the mixin mix:shareable (or some derived node type), + * then initialize the shared set inside the state.
  • + *
  • If the mixin mix:shareable (or some derived node type) + * has been removed, throw.
  • + *
+ */ + 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 ItemManager */ private final Map itemCache; - + + /** + * Shareable node cache. + */ + private final ShareableNodesCache shareableNodesCache; + /** * Creates a new per-session instance ItemManager 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 state. @@ -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 NodeImpl 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 NodeImpl 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 null + */ + 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 null + */ + 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, null 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 relPath or null * if no property exists at relPath. *

@@ -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 NodeIterator + * @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 remove() that removes this node and every + * other node in the shared set of this node. + *

+ * This removal must be done atomically, i.e., if one of the nodes cannot be + * removed, the function throws the exception remove() would + * have thrown in that case, and none of the nodes are removed. + *

+ * 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 remove() that removes this node, but does + * not remove any other node in the shared set of this node. + *

+ * All of the exceptions defined for remove() apply to this + * function. In addition, a RepositoryException is thrown if + * this node cannot be removed without removing another node in the shared + * set of this node. + *

+ * 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 true if this node is shareable; + * false 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 null 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 NodeState 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 true if the parent was successfully added; + * false 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 true if the parent id appears in the shared set; + * false 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 0, + * 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 @@ + + + mix:referenceable + + 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);