Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/RepositoryTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/RepositoryTest.java (revision 80166ab001a4a9a20a0f85e45cbf822c70a7eb09) +++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/RepositoryTest.java (revision ) @@ -279,7 +279,6 @@ assertTrue(node.isSame(node2)); } - @Ignore("OAK-343") // FIXME: OAK-343 @Test public void getNodeByUUID() throws RepositoryException { Node node = getNode("/foo").addNode("boo"); @@ -291,6 +290,57 @@ assertEquals(uuid, node.getIdentifier()); Node nAgain = node.getSession().getNodeByUUID(uuid); + assertTrue(nAgain.isSame(node)); + assertTrue(nAgain.isSame(node.getSession().getNodeByIdentifier(uuid))); + + Node childNode = node.addNode("boohoo"); + childNode.addMixin(JcrConstants.MIX_REFERENCEABLE); + + assertTrue(childNode.isNodeType(JcrConstants.MIX_REFERENCEABLE)); + String childUuid = childNode.getUUID(); + assertNotNull(childUuid); + assertEquals(childUuid, childNode.getIdentifier()); + + nAgain = childNode.getSession().getNodeByUUID(childUuid); + assertTrue(nAgain.isSame(childNode)); + assertTrue(nAgain.isSame(childNode.getSession().getNodeByIdentifier(childUuid))); + + node.getSession().move("/foo/boo", "/foo/boohoo"); + node = getNode("/foo/boohoo"); + nAgain = node.getSession().getNodeByUUID(uuid); + assertTrue(nAgain.isSame(node)); + assertTrue(nAgain.isSame(node.getSession().getNodeByIdentifier(uuid))); + } + + @Test + public void getRootChildByUUID() throws RepositoryException { + Node node = getNode("/").addNode("boo"); + node.addMixin(JcrConstants.MIX_REFERENCEABLE); + + assertTrue(node.isNodeType(JcrConstants.MIX_REFERENCEABLE)); + String uuid = node.getUUID(); + assertNotNull(uuid); + assertEquals(uuid, node.getIdentifier()); + + Node nAgain = node.getSession().getNodeByUUID(uuid); + assertTrue(nAgain.isSame(node)); + assertTrue(nAgain.isSame(node.getSession().getNodeByIdentifier(uuid))); + + Node childNode = node.addNode("boohoo"); + childNode.addMixin(JcrConstants.MIX_REFERENCEABLE); + + assertTrue(childNode.isNodeType(JcrConstants.MIX_REFERENCEABLE)); + String childUuid = childNode.getUUID(); + assertNotNull(childUuid); + assertEquals(childUuid, childNode.getIdentifier()); + + nAgain = childNode.getSession().getNodeByUUID(childUuid); + assertTrue(nAgain.isSame(childNode)); + assertTrue(nAgain.isSame(childNode.getSession().getNodeByIdentifier(childUuid))); + + node.getSession().move("/boo", "/boohoo"); + node = getNode("/boohoo"); + nAgain = node.getSession().getNodeByUUID(uuid); assertTrue(nAgain.isSame(node)); assertTrue(nAgain.isSame(node.getSession().getNodeByIdentifier(uuid))); } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java (revision 80166ab001a4a9a20a0f85e45cbf822c70a7eb09) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java (revision ) @@ -23,6 +23,9 @@ import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Collections; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; import javax.annotation.Nonnull; import javax.security.auth.Subject; @@ -33,13 +36,21 @@ import org.apache.jackrabbit.oak.api.Root; import org.apache.jackrabbit.oak.api.QueryEngine; import org.apache.jackrabbit.oak.api.TreeLocation; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.namepath.NamePathMapper; import org.apache.jackrabbit.oak.plugins.commit.DefaultConflictHandler; +import org.apache.jackrabbit.oak.query.index.IndexRowImpl; import org.apache.jackrabbit.oak.query.QueryEngineImpl; import org.apache.jackrabbit.oak.spi.commit.ConflictHandler; import org.apache.jackrabbit.oak.spi.observation.ChangeExtractor; import org.apache.jackrabbit.oak.spi.query.CompositeQueryIndexProvider; import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider; +import org.apache.jackrabbit.oak.spi.query.Cursor; +import org.apache.jackrabbit.oak.spi.query.Cursors; +import org.apache.jackrabbit.oak.spi.query.Filter; +import org.apache.jackrabbit.oak.spi.query.IndexRow; +import org.apache.jackrabbit.oak.spi.query.QueryIndex; import org.apache.jackrabbit.oak.spi.security.authorization.AccessControlConfiguration; import org.apache.jackrabbit.oak.spi.security.authorization.CompiledPermissions; import org.apache.jackrabbit.oak.spi.security.authorization.OpenAccessControlConfiguration; @@ -49,6 +60,8 @@ import org.apache.jackrabbit.oak.spi.state.NodeStateDiff; import org.apache.jackrabbit.oak.spi.state.NodeStore; import org.apache.jackrabbit.oak.spi.state.NodeStoreBranch; +import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; +import org.apache.jackrabbit.oak.spi.state.EmptyNodeStateDiff; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -106,7 +119,7 @@ this.store = checkNotNull(store); this.subject = checkNotNull(subject); this.accConfiguration = checkNotNull(accConfiguration); - this.indexProvider = indexProvider; + this.indexProvider = new TransientUUIDIndexProviderWrapper(indexProvider); refresh(); } @@ -115,7 +128,7 @@ this.store = checkNotNull(store); this.subject = new Subject(true, Collections.singleton(SystemPrincipal.INSTANCE), Collections.emptySet(), Collections.emptySet()); this.accConfiguration = new OpenAccessControlConfiguration(); - this.indexProvider = new CompositeQueryIndexProvider(); + this.indexProvider = new TransientUUIDIndexProviderWrapper(new CompositeQueryIndexProvider()); refresh(); } @@ -363,4 +376,112 @@ rootTree.getNodeBuilder().reset(branch.getRoot()); } + private final class TransientUUIDIndexProviderWrapper implements QueryIndexProvider { + + private final class TransientUUIDIndex implements QueryIndex { + + private String uuid; + private String path; + + @Override + public double getCost(Filter filter, NodeState root) { + + Filter.PropertyRestriction restriction = filter.getPropertyRestriction("jcr:uuid"); + if (restriction == null + || restriction.isLike + || !restriction.firstIncluding + || !restriction.lastIncluding + || !restriction.first.equals(restriction.last) ) { + return Double.POSITIVE_INFINITY; + } + uuid = restriction.first.toString(); + rootTree.getNodeState().compareAgainstBaseState(getBaseState(), new EmptyNodeStateDiff() { + + @Override + public void childNodeAdded(String name, NodeState after) { + TreeImpl child = rootTree.getChild(name); + testNodeState(child.getNodeState(), rootTree.getPath().concat(name)); + } + + @Override + public void childNodeChanged(String name, NodeState before, NodeState after) { + if (after.getChildNodeCount() > before.getChildNodeCount()) { + Iterable childNodeEntries = after.getChildNodeEntries(); + Iterator iterator = childNodeEntries.iterator(); + while (iterator.hasNext() && path == null) { + ChildNodeEntry next = iterator.next(); + if (!before.hasChildNode(next.getName())) { + testNodeState( + next.getNodeState(), + rootTree.getPath().concat(name).concat("/").concat(next.getName())); + } + } + } + } + + private void testNodeState(NodeState nodeState, String currentPath) { + PropertyState propertyState = nodeState.getProperty("jcr:uuid"); + if (propertyState != null && uuid.equals(propertyState.getValue(Type.STRING))) { + path = currentPath; + } + else { + Iterable childNodeEntries = nodeState.getChildNodeEntries(); + Iterator iterator = childNodeEntries.iterator(); + while (iterator.hasNext() && path == null) { + ChildNodeEntry childNodeEntry = iterator.next(); + testNodeState(childNodeEntry.getNodeState(), currentPath + "/" + childNodeEntry.getName()); + } + } + } + }); + return path == null ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY; + } + + @Override + public Cursor query(Filter filter, final NodeState root) { + return new Cursors.AbstractCursor() { + boolean fetched = false; + @Override + public IndexRow next() { + if (!this.fetched) { + fetched = true; + return new IndexRowImpl(path); + } + return null; + } + + @Override + public boolean hasNext() { + return !this.fetched; + } + }; + } + + @Override + public String getPlan(Filter filter, NodeState root) { + //TODO ? + return null; + } + + @Override + public String getIndexName() { + return "TransientUUID"; + } + } + + private final QueryIndexProvider baseProvider; + + public TransientUUIDIndexProviderWrapper(QueryIndexProvider baseProvider) { + this.baseProvider = baseProvider; + } + + @Nonnull + @Override + public List getQueryIndexes(NodeState nodeState) { + ArrayList indexes = new ArrayList( + Collections.singletonList(new TransientUUIDIndex())); + indexes.addAll(this.baseProvider.getQueryIndexes(nodeState)); + return indexes; + } + } }