Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ChangeLogBasedHierarchyMgr.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ChangeLogBasedHierarchyMgr.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ChangeLogBasedHierarchyMgr.java (working copy)
@@ -29,6 +29,7 @@
import org.apache.jackrabbit.core.state.NodeReferences;
import org.apache.jackrabbit.core.state.NodeReferencesId;
import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.Name;
import javax.jcr.ItemNotFoundException;
import javax.jcr.RepositoryException;
@@ -68,10 +69,10 @@
* Same as {@link #getPath(ItemId)}} except that the old path is
* returned in case of a moved/removed item.
*
- * @param id
- * @return
- * @throws ItemNotFoundException
- * @throws RepositoryException
+ * @param id the id of the node for which to retrieve the path.
+ * @return the path of the item.
+ * @throws ItemNotFoundException if an item state cannot be found.
+ * @throws RepositoryException if another error occurs.
*/
public Path getZombiePath(ItemId id)
throws ItemNotFoundException, RepositoryException {
@@ -79,6 +80,21 @@
}
/**
+ * Same as {@link #getName(NodeId, NodeId)} except that the old path
+ * is returned in case of moved/removed item.
+ *
+ * @param id the id of the node for which to retrieve the name.
+ * @param parentId the id of the parent node.
+ * @return the name of the node.
+ * @throws ItemNotFoundException if an item state cannot be found.
+ * @throws RepositoryException if another error occurs.
+ */
+ public Name getZombieName(NodeId id, NodeId parentId)
+ throws ItemNotFoundException, RepositoryException {
+ return zombieHierMgr.getName(id, parentId);
+ }
+
+ /**
* Implements an ItemStateManager that is overlayed by a ChangeLog.
*/
private static class ChangeLogItemStateManager implements ItemStateManager {
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java (working copy)
@@ -165,6 +165,17 @@
}
/**
+ * Returns a flag indicating whether the child node of this event is a
+ * shareable node. Only applies to node added/removed events.
+ *
+ * @return true for a shareable child node, false
+ * otherwise.
+ */
+ public boolean isShareableChildNode() {
+ return eventState.isShareableNode();
+ }
+
+ /**
* Return a flag indicating whether this is an externally generated event.
*
* @return true if this is an external event;
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventState.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventState.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventState.java (working copy)
@@ -113,6 +113,12 @@
private final boolean external;
/**
+ * If set to true, indicates that the child node of a node
+ * added or removed event is a shareable node.
+ */
+ private boolean shareableNode = false;
+
+ /**
* Creates a new EventState instance.
*
* @param type the type of this event.
@@ -563,6 +569,27 @@
}
/**
+ * Returns a flag indicating whether the child node of this event is a
+ * shareable node. Only applies to node added/removed events.
+ *
+ * @return true for a shareable child node, false
+ * otherwise.
+ */
+ boolean isShareableNode() {
+ return shareableNode;
+ }
+
+ /**
+ * Sets a new value for the {@link #shareableNode} flag.
+ *
+ * @param shareableNode whether the child node is shareable.
+ * @see #isShareableNode()
+ */
+ void setShareableNode(boolean shareableNode) {
+ this.shareableNode = shareableNode;
+ }
+
+ /**
* Returns a String representation of this EventState.
*
* @return a String representation of this EventState.
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java (working copy)
@@ -173,6 +173,8 @@
// 4) child node removed
// 5) node moved
// 6) node reordered
+ // 7) shareable node added
+ // 8) shareable node removed
// cases 1) and 2) are detected with added and deleted states
// on the PropertyState itself.
// cases 3) and 4) are detected with added and deleted states
@@ -183,6 +185,10 @@
// the node that actually got moved.
// in case 6) only one node state changes. the state of the
// parent node.
+ // in case 7) parent of added shareable node has new child node
+ // entry.
+ // in case 8) parent of removed shareable node has removed child
+ // node entry.
NodeState n = (NodeState) state;
if (n.hasOverlayedState()) {
@@ -326,6 +332,9 @@
session));
}
}
+
+ // create events if n is shareable
+ createShareableNodeEvents(n, changes, hmgr, stateMgr);
} else {
// property changed
Path path = getPath(state.getId(), hmgr);
@@ -359,6 +368,9 @@
nodeType.getQName(),
mixins,
session));
+
+ // create events if n is shareable
+ createShareableNodeEvents(n, changes, hmgr, stateMgr);
} else {
// property removed
// only create an event if node still exists
@@ -400,6 +412,9 @@
nodeType.getQName(),
mixins,
session));
+
+ // create events if n is shareable
+ createShareableNodeEvents(n, changes, hmgr, stateMgr);
} else {
// property created / set
NodeState n = (NodeState) changes.get(state.getParentId());
@@ -518,6 +533,67 @@
//----------------------------< internal >----------------------------------
+ private void createShareableNodeEvents(NodeState n,
+ ChangeLog changes,
+ ChangeLogBasedHierarchyMgr hmgr,
+ ItemStateManager stateMgr)
+ throws ItemStateException {
+ if (n.isShareable()) {
+ // check if a share was added or removed
+ for (Iterator added = n.getAddedShares().iterator(); added.hasNext(); ) {
+ NodeId parentId = (NodeId) added.next();
+ // ignore primary parent id
+ if (n.getParentId().equals(parentId)) {
+ continue;
+ }
+ NodeState parent = (NodeState) changes.get(parentId);
+ if (parent == null) {
+ // happens when mix:shareable is added to an existing node
+ // usually the parent node state is in the change log
+ // when a node is added to a shared set -> new child node
+ // entry on parent node state.
+ parent = (NodeState) stateMgr.getItemState(parentId);
+ }
+ Name ntName = getNodeType(parent, session).getQName();
+ EventState es = EventState.childNodeAdded(parentId,
+ getPath(parentId, hmgr),
+ n.getNodeId(),
+ getNameElement(n.getNodeId(), parentId, hmgr),
+ ntName,
+ parent.getMixinTypeNames(),
+ session);
+ es.setShareableNode(true);
+ events.add(es);
+ }
+ for (Iterator removed = n.getRemovedShares().iterator(); removed.hasNext(); ) {
+ NodeId parentId = (NodeId) removed.next();
+ // if this shareable node is removed, only create events for
+ // parent ids that are not primary
+ if (n.getParentId().equals(parentId)) {
+ continue;
+ }
+ NodeState parent = (NodeState) changes.get(parentId);
+ if (parent == null) {
+ // happens when mix:shareable is removed from an existing
+ // node. Usually the parent node state is in the change log
+ // when a node is removed to a shared set -> removed child
+ // node entry on parent node state.
+ parent = (NodeState) stateMgr.getItemState(parentId);
+ }
+ Name ntName = getNodeType(parent, session).getQName();
+ EventState es = EventState.childNodeRemoved(parentId,
+ getZombiePath(parentId, hmgr),
+ n.getNodeId(),
+ getZombieNameElement(n.getNodeId(), parentId, hmgr),
+ ntName,
+ parent.getMixinTypeNames(),
+ session);
+ es.setShareableNode(true);
+ events.add(es);
+ }
+ }
+ }
+
/**
* Resolves the node type name in node into a {@link javax.jcr.nodetype.NodeType}
* object using the {@link javax.jcr.nodetype.NodeTypeManager} of session.
@@ -579,6 +655,61 @@
}
/**
+ * Returns the name element for the node with the given nodeId
+ * and its parent with parentId. This method is only useful
+ * if nodeId denotes a shareable node.
+ *
+ * @param nodeId the node id of a shareable node.
+ * @param parentId the id of the parent node.
+ * @param hmgr the hierarchy manager.
+ * @return the name element for the node.
+ * @throws ItemStateException if an error occurs while resolving the name.
+ */
+ private Path.Element getNameElement(NodeId nodeId,
+ NodeId parentId,
+ HierarchyManager hmgr)
+ throws ItemStateException {
+ try {
+ Name name = hmgr.getName(nodeId, parentId);
+ PathBuilder builder = new PathBuilder();
+ builder.addFirst(name);
+ return builder.getPath().getNameElement();
+ } catch (RepositoryException e) {
+ String msg = "Unable to get name for node with id: " + nodeId;
+ throw new ItemStateException(msg, e);
+ }
+ }
+
+ /**
+ * Returns the zombie (i.e. the old) name element for the node with
+ * the given nodeId and its parent with parentId.
+ * This method is only useful if nodeId denotes a shareable
+ * node.
+ *
+ * @param nodeId the node id of a shareable node.
+ * @param parentId the id of the parent node.
+ * @param hmgr the hierarchy manager.
+ * @return the name element for the node.
+ * @throws ItemStateException if an error occurs while resolving the name.
+ */
+ private Path.Element getZombieNameElement(NodeId nodeId,
+ NodeId parentId,
+ ChangeLogBasedHierarchyMgr hmgr)
+ throws ItemStateException {
+ try {
+ Name name = hmgr.getZombieName(nodeId, parentId);
+ PathBuilder builder = new PathBuilder();
+ builder.addFirst(name);
+ return builder.getPath().getNameElement();
+ } catch (RepositoryException e) {
+ // should never happen actually
+ String msg = "Unable to resolve zombie name for item: " + nodeId;
+ log.error(msg);
+ throw new ItemStateException(msg, e);
+ }
+ }
+
+ /**
* Resolves the zombie (i.e. the old) path of the Item with id
* itemId.
*
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingIndexReader.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingIndexReader.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingIndexReader.java (working copy)
@@ -56,6 +56,12 @@
private static long currentTick;
/**
+ * BitSet where bits that correspond to document numbers are set for
+ * shareable nodes.
+ */
+ private final BitSet shareableNodes;
+
+ /**
* Cache of nodes parent relation. If an entry in the array is not null,
* that means the node with the document number = array-index has the node
* with DocId as parent.
@@ -105,6 +111,16 @@
super(delegatee);
this.cache = cache;
this.parents = new DocId[delegatee.maxDoc()];
+ this.shareableNodes = new BitSet();
+ TermDocs tDocs = delegatee.termDocs(
+ new Term(FieldNames.SHAREABLE_NODE, ""));
+ try {
+ while (tDocs.next()) {
+ shareableNodes.set(tDocs.doc());
+ }
+ } finally {
+ tDocs.close();
+ }
this.cacheInitializer = new CacheInitializer(delegatee);
if (initCache) {
cacheInitializer.run();
@@ -144,32 +160,34 @@
if (parent == null) {
Document doc = document(n, FieldSelectors.UUID_AND_PARENT);
- String parentUUID = doc.get(FieldNames.PARENT);
- if (parentUUID == null || parentUUID.length() == 0) {
+ String[] parentUUIDs = doc.getValues(FieldNames.PARENT);
+ if (parentUUIDs.length == 0 || parentUUIDs[0].length() == 0) {
+ // root node
parent = DocId.NULL;
} else {
- // only create a DocId from document number if there is no
- // existing DocId
- if (!existing) {
- Term id = new Term(FieldNames.UUID, parentUUID);
- TermDocs docs = termDocs(id);
- try {
- while (docs.next()) {
- if (!deleted.get(docs.doc())) {
- parent = DocId.create(docs.doc());
- break;
+ if (shareableNodes.get(n)) {
+ parent = DocId.create(parentUUIDs);
+ } else {
+ if (!existing) {
+ Term id = new Term(FieldNames.UUID, parentUUIDs[0]);
+ TermDocs docs = termDocs(id);
+ try {
+ while (docs.next()) {
+ if (!deleted.get(docs.doc())) {
+ parent = DocId.create(docs.doc());
+ break;
+ }
}
+ } finally {
+ docs.close();
}
- } finally {
- docs.close();
}
+ // if still null, then parent is not in this index, or existing
+ // DocId was invalid. thus, only allowed to create DocId from uuid
+ if (parent == null) {
+ parent = DocId.create(parentUUIDs[0]);
+ }
}
-
- // if still null, then parent is not in this index, or existing
- // DocId was invalid. thus, only allowed to create DocId from uuid
- if (parent == null) {
- parent = DocId.create(parentUUID);
- }
}
// finally put to cache
@@ -379,8 +397,12 @@
public void collect(Term term, TermDocs tDocs) throws IOException {
UUID uuid = UUID.fromString(term.text());
while (tDocs.next()) {
- NodeInfo info = new NodeInfo(tDocs.doc(), uuid);
- docs.put(new Integer(info.docId), info);
+ int doc = tDocs.doc();
+ // skip shareable nodes
+ if (!shareableNodes.get(doc)) {
+ NodeInfo info = new NodeInfo(doc, uuid);
+ docs.put(new Integer(doc), info);
+ }
}
}
});
@@ -392,9 +414,13 @@
while (tDocs.next()) {
Integer docId = new Integer(tDocs.doc());
NodeInfo info = (NodeInfo) docs.get(docId);
- info.parent = uuid;
- docs.remove(docId);
- docs.put(info.uuid, info);
+ if (info == null) {
+ // shareable node, see above
+ } else {
+ info.parent = uuid;
+ docs.remove(docId);
+ docs.put(info.uuid, info);
+ }
}
}
});
@@ -413,6 +439,9 @@
} else if (info.parent != null) {
foreignParents++;
parents[info.docId] = DocId.create(info.parent);
+ } else if (shareableNodes.get(info.docId)) {
+ Document doc = reader.document(info.docId, FieldSelectors.UUID_AND_PARENT);
+ parents[info.docId] = DocId.create(doc.getValues(FieldNames.PARENT));
} else {
// no parent -> root node
parents[info.docId] = DocId.NULL;
@@ -491,7 +520,7 @@
void collect(Term term, TermDocs tDocs) throws IOException;
}
- private static class NodeInfo {
+ private final static class NodeInfo {
final int docId;
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingMultiIndexReader.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingMultiIndexReader.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingMultiIndexReader.java (working copy)
@@ -83,17 +83,11 @@
}
/**
- * Returns the document number of the parent of n or
- * -1 if n does not have a parent (n
- * is the root node).
- *
- * @param n the document number.
- * @return the document number of n's parent.
- * @throws IOException if an error occurs while reading from the index.
+ * {@inheritDoc}
*/
- public int getParent(int n) throws IOException {
+ public int[] getParents(int n, int[] docNumbers) throws IOException {
DocId id = getParentDocId(n);
- return id.getDocumentNumber(this);
+ return id.getDocumentNumbers(this, docNumbers);
}
/**
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildAxisQuery.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildAxisQuery.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildAxisQuery.java (working copy)
@@ -701,9 +701,20 @@
long time = System.currentTimeMillis();
Hits childrenHits = new AdaptingHits();
Hits nameHits = new ScorerHits(nameTestScorer);
+ int[] docs = new int[1];
for (int h = nameHits.next(); h > -1; h = nameHits.next()) {
- if (docIds.contains(new Integer(hResolver.getParent(h)))) {
- childrenHits.set(h);
+ docs = hResolver.getParents(h, docs);
+ if (docs.length == 1) {
+ // optimize single value
+ if (docIds.contains(new Integer(docs[0]))) {
+ childrenHits.set(h);
+ }
+ } else {
+ for (int i = 0; i < docs.length; i++) {
+ if (docIds.contains(new Integer(docs[i]))) {
+ childrenHits.set(h);
+ }
+ }
}
}
time = System.currentTimeMillis() - time;
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java (working copy)
@@ -26,6 +26,7 @@
import org.apache.lucene.search.Weight;
import org.apache.lucene.search.Sort;
import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.ItemManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -269,6 +270,8 @@
private NodeTraversingQueryHits currentTraversal;
+ private ItemManager itemMgr = session.getItemManager();
+
{
fetchNextTraversal();
}
@@ -419,6 +422,16 @@
private int[] ancestorDocs = new int[2];
/**
+ * Reusable array that holds document numbers of parents.
+ */
+ private int[] pDocs = new int[1];
+
+ /**
+ * Reusable array that holds a single document number.
+ */
+ private final int[] singleDoc = new int[1];
+
+ /**
* Creates a new DescendantSelfAxisScorer.
*
* @param similarity the Similarity instance to use.
@@ -476,12 +489,7 @@
boolean match = subScorer.skipTo(target);
if (match) {
collectContextHits();
- if (isValid(subScorer.doc())) {
- return true;
- } else {
- // find next valid
- return next();
- }
+ return isValid(subScorer.doc()) || next();
} else {
return false;
}
@@ -532,25 +540,46 @@
}
// check if doc is a descendant of one of the context nodes
- int parentDoc = hResolver.getParent(doc);
+ pDocs = hResolver.getParents(doc, pDocs);
+ if (pDocs.length == 0) {
+ return false;
+ }
+
int ancestorCount = 0;
- ancestorDocs[ancestorCount++] = parentDoc;
+ // can only remember one parent doc per level
+ ancestorDocs[ancestorCount++] = pDocs[0];
// traverse
- while (parentDoc != -1 && (!contextHits.get(parentDoc) || ancestorCount < minLevels)) {
- parentDoc = hResolver.getParent(parentDoc);
- // resize array if needed
- if (ancestorCount == ancestorDocs.length) {
- // double the size of the new array
- int[] copy = new int[ancestorDocs.length * 2];
- System.arraycopy(ancestorDocs, 0, copy, 0, ancestorDocs.length);
- ancestorDocs = copy;
+ while (pDocs.length != 0) {
+ boolean valid = false;
+ for (int i = 0; i < pDocs.length; i++) {
+ if (ancestorCount >= minLevels && contextHits.get(pDocs[i])) {
+ valid = true;
+ break;
+ }
}
- ancestorDocs[ancestorCount++] = parentDoc;
+ if (valid) {
+ break;
+ } else {
+ // load next level
+ pDocs = getParents(pDocs, singleDoc);
+ // resize array if needed
+ if (ancestorCount == ancestorDocs.length) {
+ // double the size of the new array
+ int[] copy = new int[ancestorDocs.length * 2];
+ System.arraycopy(ancestorDocs, 0, copy, 0, ancestorDocs.length);
+ ancestorDocs = copy;
+ }
+ if (pDocs.length != 0) {
+ // can only remember one parent doc per level
+ ancestorDocs[ancestorCount++] = pDocs[0];
+ }
+ }
}
- if (parentDoc != -1) {
- // since current parentDoc is a descendant of one of the context
+
+ if (pDocs.length > 0) {
+ // since current parentDocs are descendants of one of the context
// docs we can promote all ancestorDocs to the context hits
for (int i = 0; i < ancestorCount; i++) {
contextHits.set(ancestorDocs[i]);
@@ -559,5 +588,31 @@
}
return false;
}
+
+ /**
+ * Returns the parent document numbers for the given docs.
+ *
+ * @param docs the current document numbers, for which to get the
+ * parents.
+ * @param pDocs an array of document numbers for reuse as return value.
+ * @return the parent document number for the given docs.
+ * @throws IOException if an error occurs while reading from the index.
+ */
+ private int[] getParents(int[] docs, int[] pDocs) throws IOException {
+ // optimize single doc
+ if (docs.length == 1) {
+ return hResolver.getParents(docs[0], pDocs);
+ } else {
+ pDocs = new int[0];
+ for (int i = 0; i < docs.length; i++) {
+ int[] p = hResolver.getParents(docs[i], new int[0]);
+ int[] tmp = new int[p.length + pDocs.length];
+ System.arraycopy(pDocs, 0, tmp, 0, pDocs.length);
+ System.arraycopy(p, 0, tmp, pDocs.length, p.length);
+ pDocs = tmp;
+ }
+ return pDocs;
+ }
+ }
}
}
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DocId.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DocId.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DocId.java (working copy)
@@ -27,6 +27,8 @@
*/
abstract class DocId {
+ static final int[] EMPTY = new int[0];
+
/**
* Indicates a null DocId. Will be returned if the root node is asked for
* its parent.
@@ -34,12 +36,14 @@
static final DocId NULL = new DocId() {
/**
- * Always returns -1.
+ * Always returns an empty array.
* @param reader the index reader.
- * @return always -1.
+ * @param docNumbers a int array for reuse as return value.
+ * @return always an empty array.
*/
- final int getDocumentNumber(MultiIndexReader reader) {
- return -1;
+ final int[] getDocumentNumbers(MultiIndexReader reader,
+ int[] docNumbers) {
+ return EMPTY;
}
/**
@@ -62,15 +66,22 @@
};
/**
- * Returns the document number of this DocId. If this id is
- * invalid -1 is returned.
+ * Returns the document numbers of this DocId. An empty array
+ * is returned if this id is invalid.
*
- * @param reader the IndexReader to resolve this DocId.
- * @return the document number of this DocId or -1
- * if it is invalid (e.g. does not exist).
+ * @param reader the IndexReader to resolve this DocId.
+ * @param docNumbers an array for reuse. An implementation should use the
+ * passed array as a container for the return value,
+ * unless the length of the returned array is different
+ * from docNumbers. In which case an
+ * implementation will create a new array with an
+ * appropriate size.
+ * @return the document numbers of this DocId or
+ * empty if it is invalid (e.g. does not exist).
* @throws IOException if an error occurs while reading from the index.
*/
- abstract int getDocumentNumber(MultiIndexReader reader) throws IOException;
+ abstract int[] getDocumentNumbers(MultiIndexReader reader, int[] docNumbers)
+ throws IOException;
/**
* Applies an offset to this DocId. The returned DocId
@@ -123,6 +134,16 @@
return new UUIDDocId(uuid);
}
+ /**
+ * Creates a DocId that references multiple UUIDs.
+ *
+ * @param uuids the UUIDs of the referenced nodes.
+ * @return a DocId based on multiple node UUIDs.
+ */
+ static DocId create(String[] uuids) {
+ return new MultiUUIDDocId(uuids);
+ }
+
//--------------------------< internal >------------------------------------
/**
@@ -147,8 +168,13 @@
/**
* @inheritDoc
*/
- int getDocumentNumber(MultiIndexReader reader) {
- return docNumber;
+ int[] getDocumentNumbers(MultiIndexReader reader, int[] docNumbers) {
+ if (docNumbers.length == 1) {
+ docNumbers[0] = docNumber;
+ return docNumbers;
+ } else {
+ return new int[]{docNumber};
+ }
}
/**
@@ -208,7 +234,8 @@
/**
* @inheritDoc
*/
- int getDocumentNumber(MultiIndexReader reader) throws IOException {
+ int[] getDocumentNumbers(MultiIndexReader reader, int[] docNumbers)
+ throws IOException {
int realDoc = -1;
ForeignSegmentDocId segDocId = doc;
if (segDocId != null) {
@@ -222,12 +249,18 @@
doc = segDocId;
}
}
- return realDoc;
+
+ if (docNumbers.length == 1) {
+ docNumbers[0] = realDoc;
+ return docNumbers;
+ } else {
+ return new int[]{realDoc};
+ }
}
/**
* This implementation will return this. Document number is
- * not known until resolved in {@link #getDocumentNumber(MultiIndexReader)}.
+ * not known until resolved in {@link #getDocumentNumbers(MultiIndexReader,int[])}.
*
* @inheritDoc
*/
@@ -254,4 +287,76 @@
return "UUIDDocId(" + new UUID(msb, lsb) + ")";
}
}
+
+ /**
+ * A DocId based on multiple UUIDDocIds.
+ */
+ private static final class MultiUUIDDocId extends DocId {
+
+ /**
+ * The internal uuid based doc ids.
+ */
+ private final UUIDDocId[] docIds;
+
+ /**
+ * @param uuids the uuids of the referenced nodes.
+ * @throws IllegalArgumentException if one of the uuids is malformed.
+ */
+ MultiUUIDDocId(String[] uuids) {
+ this.docIds = new UUIDDocId[uuids.length];
+ for (int i = 0; i < uuids.length; i++) {
+ docIds[i] = new UUIDDocId(UUID.fromString(uuids[i]));
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ int[] getDocumentNumbers(MultiIndexReader reader, int[] docNumbers)
+ throws IOException {
+ int[] tmp = new int[1];
+ docNumbers = new int[docIds.length];
+ for (int i = 0; i < docNumbers.length; i++) {
+ docNumbers[i] = docIds[i].getDocumentNumbers(reader, tmp)[0];
+ }
+ return docNumbers;
+ }
+
+ /**
+ * This implementation will return this. Document number is
+ * not known until resolved in {@link #getDocumentNumbers(MultiIndexReader,int[])}.
+ *
+ * @inheritDoc
+ */
+ DocId applyOffset(int offset) {
+ return this;
+ }
+
+ /**
+ * Always returns true.
+ *
+ * @param deleted the deleted documents.
+ * @return always true.
+ */
+ boolean isValid(BitSet deleted) {
+ return true;
+ }
+
+ /**
+ * Returns a String representation for this DocId.
+ *
+ * @return a String representation for this DocId.
+ */
+ public String toString() {
+ StringBuffer sb = new StringBuffer("MultiUUIDDocId(");
+ String separator = "";
+ for (int i = 0; i < docIds.length; i++) {
+ sb.append(separator);
+ separator = ", ";
+ sb.append(new UUID(docIds[i].msb, docIds[i].lsb));
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+ }
}
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldNames.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldNames.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldNames.java (working copy)
@@ -107,6 +107,11 @@
public static final String REINDEXING_REQUIRED = "_:REINDEXING_REQUIRED".intern();
/**
+ * Name of the field that marks shareable nodes.
+ */
+ public static final String SHAREABLE_NODE = "_:SHAREABLE_NODE".intern();
+
+ /**
* Returns a named length for use as a term in the index. The named length
* is of the form: propertyName + '[' +
* {@link LongField#longToString(long)}.
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ForeignSegmentDocId.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ForeignSegmentDocId.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ForeignSegmentDocId.java (working copy)
@@ -26,6 +26,11 @@
final class ForeignSegmentDocId extends DocId {
/**
+ * Empty array of {@link ForeignSegmentDocId}s.
+ */
+ static final ForeignSegmentDocId[] EMPTY_ARRAY = new ForeignSegmentDocId[0];
+
+ /**
* The document number.
*/
private final int docNumber;
@@ -64,13 +69,23 @@
/**
* @inheritDoc
*/
- int getDocumentNumber(MultiIndexReader reader) throws IOException {
- return reader.getDocumentNumber(this);
+ int[] getDocumentNumbers(MultiIndexReader reader, int[] docNumbers) throws IOException {
+ int doc = reader.getDocumentNumber(this);
+ if (doc == -1) {
+ return EMPTY;
+ } else {
+ if (docNumbers.length == 1) {
+ docNumbers[0] = doc;
+ return docNumbers;
+ } else {
+ return new int[]{doc};
+ }
+ }
}
/**
* This implementation will return this. Document number is
- * not known until resolved in {@link #getDocumentNumber(MultiIndexReader)}.
+ * not known until resolved in {@link DocId#getDocumentNumbers(MultiIndexReader,int[])}.
*
* {@inheritDoc}
*/
@@ -82,7 +97,7 @@
* Always returns true because this calls is in context of the
* index segment where this DocId lives. Within this segment this DocId is
* always valid. Whether the target of this DocId is valid can only be
- * checked in the method {@link #getDocumentNumber(MultiIndexReader)}.
+ * checked in the method {@link DocId#getDocumentNumbers(MultiIndexReader,int[])}.
*
* @param deleted the deleted documents in the segment where this DocId
* lives.
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/HierarchyResolver.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/HierarchyResolver.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/HierarchyResolver.java (working copy)
@@ -25,14 +25,20 @@
public interface HierarchyResolver {
/**
- * Returns the document number of the parent of n or
- * -1 if n does not have a parent (n
- * is the root node).
+ * Returns the document number of the parent of n or an empty
+ * array if n does not have a parent (n is the
+ * root node).
*
- * @param n the document number.
+ * @param n the document number.
+ * @param docNumbers an array for reuse. An implementation should use the
+ * passed array as a container for the return value,
+ * unless the length of the returned array is different
+ * from docNumbers. In which case an
+ * implementation will create a new array with an
+ * appropriate size.
* @return the document number of n's parent.
- * @throws java.io.IOException if an error occurs while reading from the index.
+ * @throws java.io.IOException if an error occurs while reading from the
+ * index.
*/
- int getParent(int n) throws IOException;
-
+ int[] getParents(int n, int[] docNumbers) throws IOException;
}
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexMigration.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexMigration.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexMigration.java (working copy)
@@ -85,15 +85,12 @@
}
} finally {
reader.release();
+ index.releaseWriterAndReaders();
}
// if we get here then the index must be migrated
log.debug("Index requires migration {}", indexDir);
- // make sure readers are closed, otherwise the directory
- // cannot be deleted
- index.releaseWriterAndReaders();
-
String migrationName = index.getName() + "_v2.3";
if (directoryManager.hasDirectory(migrationName)) {
directoryManager.delete(migrationName);
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitIndexReader.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitIndexReader.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitIndexReader.java (working copy)
@@ -82,8 +82,8 @@
/**
* {@inheritDoc}
*/
- public int getParent(int n) throws IOException {
- return resolver.getParent(n);
+ public int[] getParents(int n, int[] docNumbers) throws IOException {
+ return resolver.getParents(n, docNumbers);
}
//-------------------------< MultiIndexReader >-----------------------------
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIndexer.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIndexer.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIndexer.java (working copy)
@@ -186,28 +186,24 @@
// UUID
doc.add(new Field(
FieldNames.UUID, node.getNodeId().getUUID().toString(),
- Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO));
+ Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
try {
// parent UUID
if (node.getParentId() == null) {
// root node
- doc.add(new Field(FieldNames.PARENT, "", Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO));
+ doc.add(new Field(FieldNames.PARENT, "", Field.Store.YES,
+ Field.Index.NOT_ANALYZED_NO_NORMS));
addNodeName(doc, "", "");
+ } else if (node.getSharedSet().isEmpty()) {
+ addParentChildRelation(doc, node.getParentId());
} else {
- doc.add(new Field(
- FieldNames.PARENT, node.getParentId().toString(),
- Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO));
- NodeState parent = (NodeState) stateProvider.getItemState(node.getParentId());
- ChildNodeEntry child = parent.getChildNodeEntry(node.getNodeId());
- if (child == null) {
- // this can only happen when jackrabbit
- // is running in a cluster.
- throw new RepositoryException(
- "Missing child node entry for node with id: "
- + node.getNodeId());
+ // shareable node
+ for (Iterator it = node.getSharedSet().iterator(); it.hasNext(); ) {
+ addParentChildRelation(doc, (NodeId) it.next());
}
- Name name = child.getName();
- addNodeName(doc, name.getNamespaceURI(), name.getLocalName());
+ // mark shareable nodes
+ doc.add(new Field(FieldNames.SHAREABLE_NODE, "",
+ Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS));
}
} catch (NoSuchItemStateException e) {
throwRepositoryException(e);
@@ -890,4 +886,32 @@
doc.add(new Field(FieldNames.LOCAL_NAME, localName, Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS));
}
}
+
+ /**
+ * Adds a parent child relation to the given doc.
+ *
+ * @param doc the document.
+ * @param parentId the id of the parent node.
+ * @throws ItemStateException if the parent node cannot be read.
+ * @throws RepositoryException if the parent node does not have a child node
+ * entry for the current node.
+ */
+ protected void addParentChildRelation(Document doc,
+ NodeId parentId)
+ throws ItemStateException, RepositoryException {
+ doc.add(new Field(
+ FieldNames.PARENT, parentId.toString(),
+ Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO));
+ NodeState parent = (NodeState) stateProvider.getItemState(parentId);
+ ChildNodeEntry child = parent.getChildNodeEntry(node.getNodeId());
+ if (child == null) {
+ // this can only happen when jackrabbit
+ // is running in a cluster.
+ throw new RepositoryException(
+ "Missing child node entry for node with id: "
+ + node.getNodeId());
+ }
+ Name name = child.getName();
+ addNodeName(doc, name.getNamespaceURI(), name.getLocalName());
+ }
}
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java (working copy)
@@ -312,14 +312,25 @@
final IOException[] ex = new IOException[1];
contextScorer.score(new HitCollector() {
+
+ private int[] docs = new int[1];
+
public void collect(int doc, float score) {
try {
- doc = hResolver.getParent(doc);
- if (doc != -1) {
- hits.set(doc);
+ docs = hResolver.getParents(doc, docs);
+ if (docs.length == 1) {
+ // optimize single value
+ hits.set(docs[0]);
if (score != DEFAULT_SCORE.floatValue()) {
- scores.put(new Integer(doc), new Float(score));
+ scores.put(new Integer(docs[0]), new Float(score));
}
+ } else {
+ for (int i = 0; i < docs.length; i++) {
+ hits.set(docs[i]);
+ if (score != DEFAULT_SCORE.floatValue()) {
+ scores.put(new Integer(docs[i]), new Float(score));
+ }
+ }
}
} catch (IOException e) {
ex[0] = e;
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java (working copy)
@@ -1410,11 +1410,11 @@
/**
* @inheritDoc
*/
- public int getParent(int n) throws IOException {
+ public int[] getParents(int n, int[] docNumbers) throws IOException {
int i = readerIndex(n);
DocId id = subReaders[i].getParentDocId(n - starts[i]);
id = id.applyOffset(starts[i]);
- return id.getDocumentNumber(this);
+ return id.getDocumentNumbers(this, docNumbers);
}
//-------------------------< MultiIndexReader >-------------------------
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SearchManager.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SearchManager.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SearchManager.java (working copy)
@@ -387,8 +387,18 @@
if (e.isExternal()) {
removedNodes.add(e.getChildId());
}
+ if (e.isShareableChildNode()) {
+ // simply re-index shareable nodes
+ removedNodes.add(e.getChildId());
+ }
} else if (type == Event.NODE_REMOVED) {
removedNodes.add(e.getChildId());
+ if (e.isShareableChildNode()) {
+ // check if there is a node remaining in the shared set
+ if (itemMgr.hasItemState(e.getChildId())) {
+ addedNodes.put(e.getChildId(), e);
+ }
+ }
} else {
propEvents.add(e);
}
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java (revision 761214)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java (working copy)
@@ -797,6 +797,36 @@
}
}
+ /**
+ * Returns a set of shares that were added.
+ *
+ * @return the set of shares that were added. Set of {@link NodeId}s.
+ */
+ public synchronized Set getAddedShares() {
+ if (!hasOverlayedState() || !isShareable()) {
+ return Collections.EMPTY_SET;
+ }
+ NodeState other = (NodeState) getOverlayedState();
+ HashSet set = new HashSet(sharedSet);
+ set.removeAll(other.sharedSet);
+ return set;
+ }
+
+ /**
+ * Returns a set of shares that were removed.
+ *
+ * @return the set of shares that were removed. Set of {@link NodeId}s.
+ */
+ public synchronized Set getRemovedShares() {
+ if (!hasOverlayedState() || !isShareable()) {
+ return Collections.EMPTY_SET;
+ }
+ NodeState other = (NodeState) getOverlayedState();
+ HashSet set = new HashSet(other.sharedSet);
+ set.removeAll(sharedSet);
+ return set;
+ }
+
//--------------------------------------------------< ItemState overrides >
/**
Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/ShareableNodesTest.java
===================================================================
--- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/ShareableNodesTest.java (revision 0)
+++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/ShareableNodesTest.java (revision 0)
@@ -0,0 +1,91 @@
+/*
+ * 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.observation;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Node;
+import javax.jcr.Workspace;
+import javax.jcr.observation.Event;
+
+import org.apache.jackrabbit.test.api.observation.EventResult;
+import org.apache.jackrabbit.core.NodeImpl;
+
+/**
+ * ShareableNodesTest...
+ */
+public class ShareableNodesTest extends AbstractObservationTest {
+
+ public void testAddShareableMixin() throws RepositoryException {
+ Node n1 = testRootNode.addNode(nodeName1);
+ testRootNode.save();
+
+ EventResult result = new EventResult(log);
+ addEventListener(result);
+
+ n1.addMixin(mixShareable);
+ testRootNode.save();
+
+ Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
+ for (int i = 0; i < events.length; i++) {
+ assertFalse("must not contain node added event", events[i].getType() == Event.NODE_ADDED);
+ assertFalse("must not contain node removed event", events[i].getType() == Event.NODE_REMOVED);
+ }
+ }
+
+ public void testAddShare() throws RepositoryException {
+ Node n1 = testRootNode.addNode(nodeName1);
+ Node n2 = testRootNode.addNode(nodeName2);
+ Node s = n1.addNode(nodeName3);
+ s.addMixin(mixShareable);
+ testRootNode.save();
+
+ EventResult result = new EventResult(log);
+ addEventListener(result);
+
+ Workspace wsp = superuser.getWorkspace();
+ wsp.clone(wsp.getName(), s.getPath(), n2.getPath() + "/" + s.getName(), false);
+
+ checkNodeAdded(result.getEvents(DEFAULT_WAIT_TIMEOUT),
+ new String[]{nodeName2 + "/" + nodeName3},
+ new String[0]);
+ }
+
+ public void testRemoveShare() throws RepositoryException {
+ Node n1 = testRootNode.addNode(nodeName1);
+ Node n2 = testRootNode.addNode(nodeName2);
+ Node s = n1.addNode(nodeName3);
+ s.addMixin(mixShareable);
+ testRootNode.save();
+
+ Workspace wsp = superuser.getWorkspace();
+ wsp.clone(wsp.getName(), s.getPath(), n2.getPath() + "/" + s.getName(), false);
+
+ EventResult result = new EventResult(log);
+ addEventListener(result);
+
+ removeFromSharedSet(n2.getNode(nodeName3));
+ testRootNode.save();
+
+ checkNodeRemoved(result.getEvents(DEFAULT_WAIT_TIMEOUT),
+ new String[]{nodeName2 + "/" + nodeName3},
+ new String[0]);
+ }
+
+ protected void removeFromSharedSet(Node node) throws RepositoryException {
+ ((NodeImpl) node).removeShare();
+ }
+}
Property changes on: jackrabbit-core\src\test\java\org\apache\jackrabbit\core\observation\ShareableNodesTest.java
___________________________________________________________________
Added: svn:eol-style
+ native
Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/TestAll.java
===================================================================
--- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/TestAll.java (revision 761214)
+++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/TestAll.java (working copy)
@@ -42,6 +42,7 @@
suite.addTestSuite(MoveInPlaceTest.class);
suite.addTestSuite(EventJournalTest.class);
suite.addTestSuite(UserDataTest.class);
+ suite.addTestSuite(ShareableNodesTest.class);
return suite;
}
Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ShareableNodeTest.java
===================================================================
--- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ShareableNodeTest.java (revision 0)
+++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ShareableNodeTest.java (revision 0)
@@ -0,0 +1,157 @@
+/*
+ * 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.query;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Node;
+import javax.jcr.Workspace;
+import javax.jcr.NodeIterator;
+
+import org.apache.jackrabbit.core.NodeImpl;
+
+/**
+ * ShareableNodeTest performs query tests with shareable nodes.
+ */
+public class ShareableNodeTest extends AbstractQueryTest {
+
+ public void testPathConstraint() throws RepositoryException {
+ Node n1 = testRootNode.addNode(nodeName1);
+ Node n2 = testRootNode.addNode(nodeName2);
+ Node s = n1.addNode(nodeName3);
+ s.setProperty(propertyName1, "value");
+ s.addMixin(mixShareable);
+ Node n4 = s.addNode(nodeName4);
+ n4.setProperty(propertyName2, "value");
+ testRootNode.save();
+
+ Workspace wsp = superuser.getWorkspace();
+ wsp.clone(wsp.getName(), s.getPath(), n2.getPath() + "/" + s.getName(), false);
+
+ String stmt = testPath + "/" + nodeName1 + "/*[@" + propertyName1 + "='value']";
+ NodeIterator nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 1, nodes.getSize());
+ // spec does not say which path must be returned -> use isSame()
+ assertTrue("wrong node", s.isSame(nodes.nextNode()));
+
+ stmt = testPath + "/" + nodeName2 + "/*[@" + propertyName1 + "='value']";
+ nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 1, nodes.getSize());
+ // spec does not say which path must be returned -> use isSame()
+ assertTrue("wrong node", s.isSame(nodes.nextNode()));
+
+ stmt = testPath + "//*[@" + propertyName1 + "='value']";
+ nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 1, nodes.getSize());
+ // spec does not say which path must be returned -> use isSame()
+ assertTrue("wrong node", s.isSame(nodes.nextNode()));
+
+ stmt = testPath + "//*[@" + propertyName2 + "='value']";
+ nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 1, nodes.getSize());
+ // spec does not say which path must be returned -> use isSame()
+ assertTrue("wrong node", n4.isSame(nodes.nextNode()));
+
+ // remove a node from the shared set
+ ((NodeImpl) s).removeShare();
+ testRootNode.save();
+
+ s = n2.getNode(nodeName3);
+
+ stmt = testPath + "/" + nodeName1 + "/*[@" + propertyName1 + "='value']";
+ nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 0, nodes.getSize());
+
+ stmt = testPath + "/" + nodeName2 + "/*[@" + propertyName1 + "='value']";
+ nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 1, nodes.getSize());
+ // spec does not say which path must be returned -> use isSame()
+ assertTrue("wrong node", s.isSame(nodes.nextNode()));
+
+ stmt = testPath + "//*[@" + propertyName1 + "='value']";
+ nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 1, nodes.getSize());
+ // spec does not say which path must be returned -> use isSame()
+ assertTrue("wrong node", s.isSame(nodes.nextNode()));
+
+ // remove remaining node from the shared set
+ ((NodeImpl) s).removeShare();
+ testRootNode.save();
+
+ stmt = testPath + "/" + nodeName1 + "/*[@" + propertyName1 + "='value']";
+ nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 0, nodes.getSize());
+
+ stmt = testPath + "/" + nodeName2 + "/*[@" + propertyName1 + "='value']";
+ nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 0, nodes.getSize());
+
+ stmt = testPath + "//*[@" + propertyName1 + "='value']";
+ nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 0, nodes.getSize());
+ }
+
+ public void testName() throws RepositoryException {
+ Node n1 = testRootNode.addNode(nodeName1);
+ Node n2 = testRootNode.addNode(nodeName2);
+ Node s = n1.addNode(nodeName3);
+ s.addMixin(mixShareable);
+ testRootNode.save();
+
+ Workspace wsp = superuser.getWorkspace();
+ wsp.clone(wsp.getName(), s.getPath(), n2.getPath() + "/" + nodeName4, false);
+
+ String stmt = testPath + "//" + nodeName3;
+ NodeIterator nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 1, nodes.getSize());
+ // spec does not say which path must be returned -> use isSame()
+ assertTrue("wrong node", s.isSame(nodes.nextNode()));
+
+ stmt = testPath + "//" + nodeName4;
+ nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 1, nodes.getSize());
+ // spec does not say which path must be returned -> use isSame()
+ assertTrue("wrong node", s.isSame(nodes.nextNode()));
+
+ // remove a node from the shared set
+ ((NodeImpl) s).removeShare();
+ testRootNode.save();
+
+ s = n2.getNode(nodeName4);
+
+ stmt = testPath + "//" + nodeName3;
+ nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 0, nodes.getSize());
+
+ stmt = testPath + "//" + nodeName4;
+ nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 1, nodes.getSize());
+ // spec does not say which path must be returned -> use isSame()
+ assertTrue("wrong node", s.isSame(nodes.nextNode()));
+
+ // remove remaining node from the shared set
+ ((NodeImpl) s).removeShare();
+ testRootNode.save();
+
+ stmt = testPath + "//" + nodeName3;
+ nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 0, nodes.getSize());
+
+ stmt = testPath + "//" + nodeName4;
+ nodes = executeQuery(stmt).getNodes();
+ assertEquals("wrong result size", 0, nodes.getSize());
+ }
+}
Property changes on: jackrabbit-core\src\test\java\org\apache\jackrabbit\core\query\ShareableNodeTest.java
___________________________________________________________________
Added: svn:eol-style
+ native
Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java
===================================================================
--- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java (revision 761214)
+++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java (working copy)
@@ -59,6 +59,7 @@
suite.addTestSuite(IndexingAggregateTest.class);
suite.addTestSuite(IndexFormatVersionTest.class);
suite.addTestSuite(IndexingRuleTest.class);
+ suite.addTestSuite(ShareableNodeTest.class);
return suite;
}
Index: jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/AbstractJCRTest.java
===================================================================
--- jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/AbstractJCRTest.java (revision 761214)
+++ jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/AbstractJCRTest.java (working copy)
@@ -185,6 +185,11 @@
protected String mixLockable;
/**
+ * JCR Name mix:shareable using the namespace resolver of the current session.
+ */
+ protected String mixShareable;
+
+ /**
* JCR Name nt:query using the namespace resolver of the current session.
*/
protected String ntQuery;
@@ -327,6 +332,7 @@
mixReferenceable = superuser.getNamespacePrefix(NS_MIX_URI) + ":referenceable";
mixVersionable = superuser.getNamespacePrefix(NS_MIX_URI) + ":versionable";
mixLockable = superuser.getNamespacePrefix(NS_MIX_URI) + ":lockable";
+ mixShareable = superuser.getNamespacePrefix(NS_MIX_URI) + ":shareable";
ntQuery = superuser.getNamespacePrefix(NS_NT_URI) + ":query";
// setup custom namespaces