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");