Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrChildNodeIteratorTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrChildNodeIteratorTest.java (revision 0) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrChildNodeIteratorTest.java (revision 0) @@ -0,0 +1,112 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * Unless otherwise indicated, all code in JBoss DNA is licensed + * to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.jcr; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsSame.sameInstance; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.stub; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import org.jboss.dna.graph.property.basic.BasicName; +import org.jboss.dna.jcr.SessionCache.ChildNode; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockitoAnnotations; +import org.mockito.MockitoAnnotations.Mock; + +/** + * + */ +public class JcrChildNodeIteratorTest { + + private List children; + private List childNodes; + private NodeIterator iter; + @Mock + private SessionCache cache; + + @Before + public void beforeEach() throws Exception { + MockitoAnnotations.initMocks(this); + children = new ArrayList(); + childNodes = new ArrayList(); + for (int i = 0; i != 10; ++i) { + UUID uuid = UUID.randomUUID(); + ChildNode child = new ChildNode(uuid, new BasicName("", "name"), i + 1); + AbstractJcrNode node = mock(AbstractJcrNode.class); + stub(cache.findJcrNode(uuid)).toReturn(node); + children.add(child); + childNodes.add(node); + } + iter = new JcrChildNodeIterator(cache, children, children.size()); + } + + @Test + public void shouldProperlyDetermineHasNext() { + Iterator nodeIter = childNodes.iterator(); + long position = 0L; + assertThat(iter.getPosition(), is(position)); + while (iter.hasNext()) { + assertThat(nodeIter.hasNext(), is(true)); + Node actual = (Node)iter.next(); + Node expected = nodeIter.next(); + assertThat(iter.getPosition(), is(++position)); + assertThat(iter.getPosition(), is(position)); // call twice + assertThat(actual, is(sameInstance(expected))); + } + assertThat(iter.hasNext(), is(false)); + assertThat(nodeIter.hasNext(), is(false)); + } + + @Test + public void shouldStartWithPositionOfZero() { + assertThat(iter.getPosition(), is(0L)); + } + + @Test + public void shouldHaveCorrectSize() { + assertThat(iter.getSize(), is((long)childNodes.size())); + } + + @Test( expected = UnsupportedOperationException.class ) + public void shouldNotAllowRemove() { + iter.remove(); + } + + @Test( expected = NoSuchElementException.class ) + public void shouldFailWhenNextIsCalled() { + while (iter.hasNext()) { + iter.next(); + } + iter.next(); + } + +} Index: dna-jcr/src/test/java/org/jboss/dna/jcr/SessionCacheTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/SessionCacheTest.java (revision 0) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/SessionCacheTest.java (revision 0) @@ -0,0 +1,480 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * Unless otherwise indicated, all code in JBoss DNA is licensed + * to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.jcr; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.hamcrest.core.IsSame.sameInstance; +import static org.junit.Assert.assertThat; +import static org.junit.matchers.JUnitMatchers.hasItems; +import static org.mockito.Mockito.stub; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import javax.jcr.nodetype.NodeType; +import org.jboss.dna.common.statistic.Stopwatch; +import org.jboss.dna.common.util.StringUtil; +import org.jboss.dna.graph.ExecutionContext; +import org.jboss.dna.graph.Graph; +import org.jboss.dna.graph.Node; +import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource; +import org.jboss.dna.graph.property.Name; +import org.jboss.dna.graph.property.Path; +import org.jboss.dna.graph.property.Property; +import org.jboss.dna.jcr.SessionCache.Children; +import org.jboss.dna.jcr.SessionCache.NodeInfo; +import org.jboss.dna.jcr.SessionCache.PropertyInfo; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockitoAnnotations; +import org.mockito.MockitoAnnotations.Mock; +import org.xml.sax.SAXException; + +/** + * + */ +public class SessionCacheTest { + + private String workspaceName; + private ExecutionContext context; + private JcrNodeTypeManager nodeTypes; + private Stopwatch sw; + private Graph store; + private SessionCache cache; + private Map storesByName; + private Map cachesByName; + + @Mock + private JcrSession session; + + @Before + public void beforeEach() throws Exception { + MockitoAnnotations.initMocks(this); + sw = new Stopwatch(); + storesByName = new HashMap(); + cachesByName = new HashMap(); + + context = new ExecutionContext(); + context.getNamespaceRegistry().register("vehix", "http://example.com/vehicles"); + + workspaceName = "theWorkspace"; + + stub(session.getExecutionContext()).toReturn(context); + stub(session.namespaces()).toReturn(context.getNamespaceRegistry()); + + // Load up all the node types ... + JcrNodeTypeSource nodeTypeSource = new JcrBuiltinNodeTypeSource(session); + nodeTypeSource = new DnaBuiltinNodeTypeSource(session, nodeTypeSource); + nodeTypeSource = new VehixNodeTypeSource(session, nodeTypeSource); + nodeTypes = new JcrNodeTypeManager(session, nodeTypeSource); + stub(session.nodeTypeManager()).toReturn(nodeTypes); + + InMemoryRepositorySource source = new InMemoryRepositorySource(); + source.setName("store"); + source.setDefaultWorkspaceName(workspaceName); + store = Graph.create(source, context); + + // Import the "cars.xml" file into the repository + store.importXmlFrom(new File("src/test/resources/vehicles.xml")).into("/"); + + cache = new SessionCache(session, workspaceName, context, nodeTypes, store); + } + + /** + * Define the node types for the "vehix" namespace. + */ + public static class VehixNodeTypeSource extends AbstractJcrNodeTypeSource { + private final List primaryNodeTypes; + private final List mixinNodeTypes; + + public VehixNodeTypeSource( JcrSession session, + JcrNodeTypeSource predecessor ) { + super(predecessor); + this.primaryNodeTypes = new ArrayList(); + this.mixinNodeTypes = new ArrayList(); + Name carName = session.getExecutionContext().getValueFactories().getNameFactory().create("vehix:car"); + Name aircraftName = session.getExecutionContext().getValueFactories().getNameFactory().create("vehix:aircraft"); + JcrNodeType unstructured = findType(JcrNtLexicon.UNSTRUCTURED); + + // Add in the "vehix:car" node type (which extends "nt:unstructured") ... + JcrNodeType car = new JcrNodeType(session, carName, Arrays.asList(new NodeType[] {unstructured}), + NO_PRIMARY_ITEM_NAME, NO_CHILD_NODES, NO_PROPERTIES, NOT_MIXIN, + ORDERABLE_CHILD_NODES); + + // Add in the "vehix:aircraft" node type (which extends "nt:unstructured") ... + JcrNodeType aircraft = new JcrNodeType(session, aircraftName, Arrays.asList(new NodeType[] {unstructured}), + NO_PRIMARY_ITEM_NAME, NO_CHILD_NODES, NO_PROPERTIES, NOT_MIXIN, + ORDERABLE_CHILD_NODES); + + primaryNodeTypes.addAll(Arrays.asList(new JcrNodeType[] {car, aircraft,})); + } + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.jcr.AbstractJcrNodeTypeSource#getDeclaredMixinNodeTypes() + */ + @Override + public Collection getDeclaredMixinNodeTypes() { + return mixinNodeTypes; + } + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.jcr.AbstractJcrNodeTypeSource#getDeclaredPrimaryNodeTypes() + */ + @Override + public Collection getDeclaredPrimaryNodeTypes() { + return primaryNodeTypes; + } + + } + + protected Graph createFrom( String repositoryName, + String workspaceName, + File file ) throws IOException, SAXException { + InMemoryRepositorySource source = new InMemoryRepositorySource(); + source.setName(repositoryName); + source.setDefaultWorkspaceName(workspaceName); + Graph graph = Graph.create(source, context); + + if (file != null) { + graph.importXmlFrom(file).into("/"); + } + return graph; + } + + protected Graph getGraph( String name ) throws IOException, SAXException { + Graph graph = storesByName.get(name); + if (graph == null) { + graph = createFrom(name, name + " workspace", new File("src/test/resources/" + name + ".xml")); + storesByName.put(name, graph); + } + return graph; + } + + protected SessionCache getCache( String name ) throws IOException, SAXException { + SessionCache cache = cachesByName.get(name); + if (cache == null) { + cache = new SessionCache(session, name + " workspace", context, nodeTypes, getGraph(name)); + cachesByName.put(name, cache); + } + return cache; + } + + protected Name name( String name ) { + return context.getValueFactories().getNameFactory().create(name); + } + + protected Path path( String path ) { + return context.getValueFactories().getPathFactory().create(path); + } + + protected Path.Segment segment( String segment ) { + return context.getValueFactories().getPathFactory().createSegment(segment); + } + + protected String pad( String name ) { + return StringUtil.justifyLeft(name, 22, ' '); + } + + protected void assertSameProperties( NodeInfo nodeInfo, + Node dnaNode ) { + UUID uuid = dnaNode.getLocation().getUuid(); + assertThat(nodeInfo.getUuid(), is(uuid)); + assertThat(nodeInfo.getOriginalLocation().getUuid(), is(uuid)); + Set propertyNames = nodeInfo.getProperties().keySet(); + for (Name propertyName : propertyNames) { + PropertyInfo info = nodeInfo.getProperty(propertyName); + assertThat(info.getNodeUuid(), is(uuid)); + assertThat(info.getPropertyId().getNodeId(), is(uuid)); + assertThat(info.getPropertyId().getPropertyName(), is(propertyName)); + assertThat(info.getProperty().getName(), is(propertyName)); + Property actual = dnaNode.getProperty(propertyName); + if (actual != null) { + assertThat(info.getProperty().size(), is(actual.size())); + assertThat(info.getProperty().getValuesAsArray(), is(actual.getValuesAsArray())); + } else { + assertThat(propertyName, is(JcrLexicon.PRIMARY_TYPE)); + } + } + } + + @Test + public void shouldCreateWithValidParameters() { + assertThat(cache, is(notNullValue())); + } + + @Test + public void shouldFindNodeInfoForRootByUuid() throws Exception { + Node root = store.getNodeAt("/"); + UUID rootUuid = root.getLocation().getUuid(); + NodeInfo rootInfo = cache.findNodeInfo(rootUuid); + assertThat(rootInfo, is(notNullValue())); + assertThat(rootInfo.getUuid(), is(rootUuid)); + assertThat(rootInfo.getDefinitionId().getNodeTypeName(), is(name("dna:root"))); + assertThat(rootInfo.getDefinitionId().getChildDefinitionName(), is(name("*"))); + assertThat(rootInfo.getPrimaryTypeName(), is(name("dna:root"))); + assertThat(rootInfo.getOriginalLocation().getPath().isRoot(), is(true)); + assertThat(rootInfo.getOriginalLocation().getUuid(), is(rootUuid)); + Set rootProperties = rootInfo.getProperties().keySet(); + assertThat(rootProperties, hasItems(name("jcr:primaryType"))); + assertSameProperties(rootInfo, root); + + PropertyInfo info = rootInfo.getProperty(JcrLexicon.PRIMARY_TYPE); + assertThat(info.getProperty().getFirstValue(), is((Object)name("dna:root"))); + } + + @Test + public void shouldRepeatedlyFindRootNodeInfoByUuid() throws Exception { + Node root = store.getNodeAt("/"); + UUID rootUuid = root.getLocation().getUuid(); + for (int i = 0; i != 20; ++i) { + NodeInfo rootInfo = cache.findNodeInfo(rootUuid); + assertThat(rootInfo, is(notNullValue())); + } + } + + @Test + public void shouldRepeatedlyFindRootNodeInfoByPath() throws Exception { + String sourceName = "cars"; + Graph store = getGraph(sourceName); + SessionCache cache = getCache(sourceName); + Node root = store.getNodeAt("/"); + UUID rootUuid = root.getLocation().getUuid(); + NodeInfo nodeInfo = cache.findNodeInfoForRoot(); + assertThat(nodeInfo.getUuid(), is(rootUuid)); + for (int i = 0; i != 20; ++i) { + sw.start(); + assertThat(nodeInfo, is(sameInstance(cache.findNodeInfoForRoot()))); + sw.stop(); + assertThat(nodeInfo, is(sameInstance(cache.findNodeInfo(rootUuid)))); + } + System.out.println(pad(sourceName) + " ==> " + sw.getSimpleStatistics()); + } + + @Test + public void shouldRepeatedlyFindRootNodeInfoByUuidFromVehiclesSource() throws Exception { + String sourceName = "vehicles"; + Graph store = getGraph(sourceName); + SessionCache cache = getCache(sourceName); + sw.start(); + Node root = store.getNodeAt("/"); + sw.stop(); + UUID rootUuid = root.getLocation().getUuid(); + for (int i = 0; i != 20; ++i) { + sw.start(); + NodeInfo rootInfo = cache.findNodeInfo(rootUuid); + sw.stop(); + assertThat(rootInfo, is(notNullValue())); + } + System.out.println(pad(sourceName) + " ==> " + sw.getSimpleStatistics()); + } + + @Test + public void shouldRepeatedlyFindRootNodeInfoByUuidFromSourceWithPrimaryTypes() throws Exception { + String sourceName = "repositoryForTckTests"; + Graph store = getGraph(sourceName); + SessionCache cache = getCache(sourceName); + sw.start(); + Node root = store.getNodeAt("/"); + sw.stop(); + UUID rootUuid = root.getLocation().getUuid(); + for (int i = 0; i != 20; ++i) { + sw.start(); + NodeInfo rootInfo = cache.findNodeInfo(rootUuid); + sw.stop(); + assertThat(rootInfo, is(notNullValue())); + } + System.out.println(pad(sourceName) + " ==> " + sw.getSimpleStatistics()); + } + + @Test + public void shouldFindChildrenInNodeInfoForRoot() throws Exception { + NodeInfo root = cache.findNodeInfoForRoot(); + Children children = root.getChildren(); + assertThat(children, is(notNullValue())); + assertThat(children.size(), is(1)); + assertThat(children.getChild(segment("vehix:Vehicles")), is(notNullValue())); + } + + @Test + public void shouldFindNodeInfoForNonRootNodeByPath() throws Exception { + String sourceName = "vehicles"; + Graph store = getGraph(sourceName); + SessionCache cache = getCache(sourceName); + // Get the root ... + NodeInfo root = cache.findNodeInfoForRoot(); + + // Now try to load a node that is well-below the root ... + Path lr3Path = path("/vehix:Vehicles/vehix:Cars/vehix:Utility/vehix:Land Rover LR3"); + Node lr3Node = store.getNodeAt(lr3Path); + sw.start(); + NodeInfo lr3 = cache.findNodeInfo(root.getUuid(), lr3Path); + sw.stop(); + assertThat(lr3.getUuid(), is(lr3Node.getLocation().getUuid())); + assertSameProperties(lr3, lr3Node); + System.out.println(pad(sourceName) + " ==> " + sw.getSimpleStatistics()); + + // Verify that this loaded all the intermediate nodes, by walking up ... + NodeInfo info = lr3; + while (true) { + UUID parent = info.getParent(); + if (parent == null) { + // then we should be at the root ... + assertThat(info.getUuid(), is(root.getUuid())); + break; + } + // Otherwise, we're not at the root, so we should find the parent ... + info = cache.findNodeInfoInCache(parent); + assertThat(info, is(notNullValue())); + } + } + + @Test + public void shouldFindInfoForNodeUsingRelativePathFromRoot() throws Exception { + String sourceName = "vehicles"; + Graph store = getGraph(sourceName); + SessionCache cache = getCache(sourceName); + + // Verify that the node does exist in the source ... + Path lr3AbsolutePath = path("/vehix:Vehicles/vehix:Cars/vehix:Utility/vehix:Land Rover LR3"); + Node lr3Node = store.getNodeAt(lr3AbsolutePath); + + // Get the root ... + NodeInfo root = cache.findNodeInfoForRoot(); + + // Now try to load a node that is well-below the root ... + Path lr3Path = path("vehix:Vehicles/vehix:Cars/vehix:Utility/vehix:Land Rover LR3"); + assertThat(lr3Path.isAbsolute(), is(false)); + NodeInfo lr3 = cache.findNodeInfo(root.getUuid(), lr3Path); + assertThat(lr3.getUuid(), is(lr3Node.getLocation().getUuid())); + assertSameProperties(lr3, lr3Node); + + Path recoveredPath = cache.getPathFor(lr3); + assertThat(recoveredPath, is(lr3AbsolutePath)); + } + + @Test + public void shouldFindInfoForNodeUsingRelativePathFromNonRoot() throws Exception { + String sourceName = "vehicles"; + Graph store = getGraph(sourceName); + SessionCache cache = getCache(sourceName); + + // Verify that the node does exist in the source ... + Path carsAbsolutePath = path("/vehix:Vehicles/vehix:Cars"); + Node carsNode = store.getNodeAt(carsAbsolutePath); + Path lr3AbsolutePath = path("/vehix:Vehicles/vehix:Cars/vehix:Utility/vehix:Land Rover LR3"); + Node lr3Node = store.getNodeAt(lr3AbsolutePath); + Path b787AbsolutePath = path("/vehix:Vehicles/vehix:Aircraft/vehix:Commercial/vehix:Boeing 787"); + Node b787Node = store.getNodeAt(b787AbsolutePath); + + // Get the root ... + NodeInfo root = cache.findNodeInfoForRoot(); + + // Now try to load the cars node ... + Path carsPath = path("vehix:Vehicles/vehix:Cars"); + assertThat(carsPath.isAbsolute(), is(false)); + NodeInfo cars = cache.findNodeInfo(root.getUuid(), carsPath); + assertThat(cars.getUuid(), is(carsNode.getLocation().getUuid())); + assertSameProperties(cars, carsNode); + + // Now try to find the LR3 node relative to the car ... + Path lr3Path = path("vehix:Utility/vehix:Land Rover LR3"); + assertThat(lr3Path.isAbsolute(), is(false)); + NodeInfo lr3 = cache.findNodeInfo(cars.getUuid(), lr3Path); + assertThat(lr3.getUuid(), is(lr3Node.getLocation().getUuid())); + assertSameProperties(lr3, lr3Node); + + // Now try to find the "Boeing 787" node relative to the LR3 node ... + Path b787Path = path("../../../vehix:Aircraft/vehix:Commercial/vehix:Boeing 787"); + assertThat(b787Path.isAbsolute(), is(false)); + assertThat(b787Path.isNormalized(), is(true)); + NodeInfo b787 = cache.findNodeInfo(lr3.getUuid(), b787Path); + assertThat(b787.getUuid(), is(b787Node.getLocation().getUuid())); + assertSameProperties(b787, b787Node); + + assertThat(cache.getPathFor(cars), is(carsAbsolutePath)); + assertThat(cache.getPathFor(lr3), is(lr3AbsolutePath)); + assertThat(cache.getPathFor(b787), is(b787AbsolutePath)); + } + + @Test + public void shouldFindJcrNodeUsingAbsolutePaths() throws Exception { + String sourceName = "vehicles"; + Graph store = getGraph(sourceName); + SessionCache cache = getCache(sourceName); + + // Verify that the node does exist in the source ... + Path carsAbsolutePath = path("/vehix:Vehicles/vehix:Cars"); + Node carsNode = store.getNodeAt(carsAbsolutePath); + Path lr3AbsolutePath = path("/vehix:Vehicles/vehix:Cars/vehix:Utility/vehix:Land Rover LR3"); + Node lr3Node = store.getNodeAt(lr3AbsolutePath); + Path b787AbsolutePath = path("/vehix:Vehicles/vehix:Aircraft/vehix:Commercial/vehix:Boeing 787"); + Node b787Node = store.getNodeAt(b787AbsolutePath); + + // Get the root ... + NodeInfo root = cache.findNodeInfoForRoot(); + + // Now try to load the cars node ... + Path carsPath = path("vehix:Vehicles/vehix:Cars"); + assertThat(carsPath.isAbsolute(), is(false)); + AbstractJcrNode carJcrNode = cache.findJcrNode(root.getUuid(), carsAbsolutePath); + assertThat(carJcrNode.internalUuid(), is(carsNode.getLocation().getUuid())); + NodeInfo cars = cache.findNodeInfo(root.getUuid(), carsPath); + assertThat(cars.getUuid(), is(carsNode.getLocation().getUuid())); + assertSameProperties(cars, carsNode); + + // Now try to find the LR3 node relative to the car ... + Path lr3Path = path("vehix:Utility/vehix:Land Rover LR3"); + assertThat(lr3Path.isAbsolute(), is(false)); + AbstractJcrNode lr3JcrNode = cache.findJcrNode(root.getUuid(), lr3AbsolutePath); + assertThat(lr3JcrNode.internalUuid(), is(lr3Node.getLocation().getUuid())); + NodeInfo lr3 = cache.findNodeInfo(cars.getUuid(), lr3Path); + assertThat(lr3.getUuid(), is(lr3Node.getLocation().getUuid())); + assertSameProperties(lr3, lr3Node); + + // Now try to find the "Boeing 787" node relative to the LR3 node ... + Path b787Path = path("../../../vehix:Aircraft/vehix:Commercial/vehix:Boeing 787"); + assertThat(b787Path.isAbsolute(), is(false)); + assertThat(b787Path.isNormalized(), is(true)); + AbstractJcrNode b787JcrNode = cache.findJcrNode(root.getUuid(), b787AbsolutePath); + assertThat(b787JcrNode.internalUuid(), is(b787Node.getLocation().getUuid())); + NodeInfo b787 = cache.findNodeInfo(lr3.getUuid(), b787Path); + assertThat(b787.getUuid(), is(b787Node.getLocation().getUuid())); + assertSameProperties(b787, b787Node); + + assertThat(cache.getPathFor(cars), is(carsAbsolutePath)); + assertThat(cache.getPathFor(lr3), is(lr3AbsolutePath)); + assertThat(cache.getPathFor(b787), is(b787AbsolutePath)); + } +} Index: dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrPropertyTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrPropertyTest.java (revision 776) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrPropertyTest.java (working copy) @@ -25,6 +25,7 @@ import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.stub; import java.io.InputStream; import java.util.Calendar; @@ -34,17 +35,16 @@ import javax.jcr.ItemVisitor; import javax.jcr.Node; import javax.jcr.Property; -import javax.jcr.PropertyType; import javax.jcr.Repository; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.Workspace; -import javax.jcr.nodetype.NodeDefinition; -import javax.jcr.nodetype.PropertyDefinition; import org.jboss.dna.common.util.StringUtil; import org.jboss.dna.graph.ExecutionContext; -import org.jboss.dna.graph.Location; +import org.jboss.dna.graph.property.Name; import org.jboss.dna.graph.property.Path; +import org.jboss.dna.jcr.SessionCache.NodeInfo; +import org.jboss.dna.jcr.SessionCache.PropertyInfo; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; @@ -56,48 +56,54 @@ */ public class AbstractJcrPropertyTest { + private PropertyId propertyId; + private PropertyInfo info; + private ExecutionContext executionContext; + private JcrNode node; private AbstractJcrProperty prop; @Mock - private Workspace workspace; - @Mock - private Repository repository; - @Mock private JcrSession session; - private AbstractJcrNode node; @Mock - private NodeDefinition nodeDefinition; - @Mock - private PropertyDefinition propertyDefinition; - private ExecutionContext executionContext; - private org.jboss.dna.graph.property.Property dnaProperty; - private Location rootLocation; - private Location nodeLocation; + private SessionCache cache; @Before public void before() throws Exception { MockitoAnnotations.initMocks(this); executionContext = new ExecutionContext(); - dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, "text/plain"); - stub(propertyDefinition.getRequiredType()).toReturn(PropertyType.STRING); - stub(session.getWorkspace()).toReturn(workspace); - stub(session.getRepository()).toReturn(repository); stub(session.getExecutionContext()).toReturn(executionContext); - UUID rootUuid = UUID.randomUUID(); - Path rootPath = executionContext.getValueFactories().getPathFactory().createRootPath(); - rootLocation = Location.create(rootPath, rootUuid); - JcrRootNode rootNode = new JcrRootNode(session, rootLocation, nodeDefinition); - stub(session.getNode(rootUuid)).toReturn(rootNode); - UUID uuid = UUID.randomUUID(); - Path path = executionContext.getValueFactories().getPathFactory().create("/nodeName"); - nodeLocation = Location.create(path, uuid); - node = new JcrNode(session, rootUuid, nodeLocation, nodeDefinition); - stub(session.getNode(uuid)).toReturn(node); + node = new JcrNode(cache, uuid); + propertyId = new PropertyId(uuid, JcrLexicon.MIMETYPE); + prop = new MockAbstractJcrProperty(cache, propertyId); - prop = new MockAbstractJcrProperty(node, propertyDefinition, dnaProperty); + info = mock(PropertyInfo.class); + stub(info.getPropertyId()).toReturn(propertyId); + stub(info.getPropertyName()).toReturn(propertyId.getPropertyName()); + + stub(cache.session()).toReturn(session); + stub(cache.context()).toReturn(executionContext); + stub(cache.findJcrProperty(propertyId)).toReturn(prop); + stub(cache.findPropertyInfo(propertyId)).toReturn(info); + stub(cache.getPathFor(info)).toReturn(path("/a/b/c/jcr:mimeType")); + stub(cache.getPathFor(propertyId)).toReturn(path("/a/b/c/jcr:mimeType")); + stub(cache.getPathFor(uuid)).toReturn(path("/a/b/c")); + + NodeInfo nodeInfo = mock(NodeInfo.class); + stub(cache.findJcrNode(uuid)).toReturn(node); + stub(cache.findNodeInfo(uuid)).toReturn(nodeInfo); + stub(cache.getPathFor(uuid)).toReturn(path("/a/b/c")); + stub(cache.getPathFor(nodeInfo)).toReturn(path("/a/b/c")); } + protected Name name( String name ) { + return executionContext.getValueFactories().getNameFactory().create(name); + } + + protected Path path( String path ) { + return executionContext.getValueFactories().getPathFactory().create(path); + } + @Test public void shouldAllowVisitation() throws Exception { ItemVisitor visitor = Mockito.mock(ItemVisitor.class); @@ -123,18 +129,18 @@ } @Test( expected = ItemNotFoundException.class ) - public void shouldNotAllowAncestorDepthGreaterThanNodeDepth() throws Exception { - prop.getAncestor(3); + public void shouldNotAllowAncestorDepthGreaterThanPropertyDepth() throws Exception { + prop.getAncestor(prop.getDepth() + 1); } @Test public void shouldProvideDepth() throws Exception { - assertThat(prop.getDepth(), is(2)); + assertThat(prop.getDepth(), is(4)); } @Test public void shouldProvideExecutionContext() throws Exception { - assertThat(prop.getExecutionContext(), is(executionContext)); + assertThat(prop.context(), is(executionContext)); } @Test @@ -149,7 +155,7 @@ @Test public void shouldProvidePath() throws Exception { - assertThat(prop.getPath(), is("/nodeName/jcr:mimeType")); + assertThat(prop.getPath(), is("/a/b/c/jcr:mimeType")); } @Test @@ -163,43 +169,111 @@ } @Test - public void shouldIndicateSameAsNodeWithSameParentAndSamePropertyName() throws Exception { - org.jboss.dna.graph.property.Property otherDnaProperty = executionContext.getPropertyFactory() - .create(dnaProperty.getName()); + public void shouldIndicateSameAsPropertyWithSameNodeAndSamePropertyName() throws Exception { + Repository repository = mock(Repository.class); + Workspace workspace = mock(Workspace.class); + Workspace workspace2 = mock(Workspace.class); + stub(workspace.getName()).toReturn("workspace"); + stub(workspace2.getName()).toReturn("workspace"); + JcrSession session2 = mock(JcrSession.class); + SessionCache cache2 = mock(SessionCache.class); + stub(session2.getRepository()).toReturn(repository); + stub(session.getRepository()).toReturn(repository); + stub(session2.getWorkspace()).toReturn(workspace2); + stub(session.getWorkspace()).toReturn(workspace); + stub(cache2.session()).toReturn(session2); + stub(cache2.context()).toReturn(executionContext); + // Make the other node have the same UUID ... - JcrNode otherNode = new JcrNode(session, rootLocation.getUuid(), nodeLocation, nodeDefinition); + UUID uuid = node.internalUuid(); + NodeInfo nodeInfo = mock(NodeInfo.class); + PropertyInfo propertyInfo = mock(PropertyInfo.class); + AbstractJcrNode otherNode = new JcrNode(cache2, uuid); + stub(propertyInfo.getPropertyId()).toReturn(propertyId); + stub(propertyInfo.getPropertyName()).toReturn(propertyId.getPropertyName()); + stub(cache2.findJcrNode(uuid)).toReturn(otherNode); + stub(cache2.findNodeInfo(uuid)).toReturn(nodeInfo); + stub(cache2.getPathFor(uuid)).toReturn(path("/a/b/c")); + stub(cache2.getPathFor(nodeInfo)).toReturn(path("/a/b/c")); + stub(cache2.findPropertyInfo(propertyId)).toReturn(info); assertThat(node.isSame(otherNode), is(true)); - Property prop = new MockAbstractJcrProperty(node, propertyDefinition, otherDnaProperty); - Property otherProp = new MockAbstractJcrProperty(otherNode, propertyDefinition, otherDnaProperty); + + Property prop = new MockAbstractJcrProperty(cache, propertyId); + Property otherProp = new MockAbstractJcrProperty(cache2, propertyId); assertThat(prop.isSame(otherProp), is(true)); } @Test public void shouldIndicateDifferentThanNodeWithDifferentParent() throws Exception { - org.jboss.dna.graph.property.Property otherDnaProperty = executionContext.getPropertyFactory() - .create(dnaProperty.getName()); - UUID otherUuid = UUID.randomUUID(); - Path otherPath = executionContext.getValueFactories().getPathFactory().create("/nodeName"); - Location location = Location.create(otherPath, otherUuid); - JcrNode otherNode = new JcrNode(session, rootLocation.getUuid(), location, nodeDefinition); - stub(session.getNode(otherUuid)).toReturn(otherNode); + Repository repository = mock(Repository.class); + Workspace workspace = mock(Workspace.class); + Workspace workspace2 = mock(Workspace.class); + stub(workspace.getName()).toReturn("workspace"); + stub(workspace2.getName()).toReturn("workspace"); + JcrSession session2 = mock(JcrSession.class); + SessionCache cache2 = mock(SessionCache.class); + stub(session2.getRepository()).toReturn(repository); + stub(session.getRepository()).toReturn(repository); + stub(session2.getWorkspace()).toReturn(workspace2); + stub(session.getWorkspace()).toReturn(workspace); + stub(cache2.session()).toReturn(session2); + stub(cache2.context()).toReturn(executionContext); + // Make the other node have a different UUID ... + UUID uuid = UUID.randomUUID(); + PropertyId propertyId2 = new PropertyId(uuid, JcrLexicon.MIXIN_TYPES); + NodeInfo nodeInfo = mock(NodeInfo.class); + PropertyInfo propertyInfo = mock(PropertyInfo.class); + AbstractJcrNode otherNode = new JcrNode(cache2, uuid); + stub(propertyInfo.getPropertyId()).toReturn(propertyId2); + stub(propertyInfo.getPropertyName()).toReturn(propertyId2.getPropertyName()); + stub(cache2.findJcrNode(uuid)).toReturn(otherNode); + stub(cache2.findNodeInfo(uuid)).toReturn(nodeInfo); + stub(cache2.getPathFor(uuid)).toReturn(path("/a/b/c")); + stub(cache2.getPathFor(nodeInfo)).toReturn(path("/a/b/c")); + assertThat(node.isSame(otherNode), is(false)); - Property prop = new MockAbstractJcrProperty(node, propertyDefinition, otherDnaProperty); - Property otherProp = new MockAbstractJcrProperty(otherNode, propertyDefinition, otherDnaProperty); + + Property prop = new MockAbstractJcrProperty(cache, propertyId); + Property otherProp = new MockAbstractJcrProperty(cache2, propertyId2); assertThat(prop.isSame(otherProp), is(false)); } @Test public void shouldIndicateDifferentThanPropertyWithSameNodeWithDifferentPropertyName() throws Exception { - org.jboss.dna.graph.property.Property otherDnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.NAME); + Repository repository = mock(Repository.class); + Workspace workspace = mock(Workspace.class); + Workspace workspace2 = mock(Workspace.class); + stub(workspace.getName()).toReturn("workspace"); + stub(workspace2.getName()).toReturn("workspace"); + JcrSession session2 = mock(JcrSession.class); + SessionCache cache2 = mock(SessionCache.class); + stub(session2.getRepository()).toReturn(repository); + stub(session.getRepository()).toReturn(repository); + stub(session2.getWorkspace()).toReturn(workspace2); + stub(session.getWorkspace()).toReturn(workspace); + stub(cache2.session()).toReturn(session2); + stub(cache2.context()).toReturn(executionContext); + // Make the other node have the same UUID ... - JcrNode otherNode = new JcrNode(session, rootLocation.getUuid(), nodeLocation, nodeDefinition); + UUID uuid = node.internalUuid(); + NodeInfo nodeInfo = mock(NodeInfo.class); + PropertyInfo propertyInfo = mock(PropertyInfo.class); + PropertyId propertyId2 = new PropertyId(uuid, JcrLexicon.NAME); + AbstractJcrNode otherNode = new JcrNode(cache2, uuid); + stub(propertyInfo.getPropertyId()).toReturn(propertyId2); + stub(propertyInfo.getPropertyName()).toReturn(propertyId2.getPropertyName()); + stub(cache2.findJcrNode(uuid)).toReturn(otherNode); + stub(cache2.findNodeInfo(uuid)).toReturn(nodeInfo); + stub(cache2.getPathFor(uuid)).toReturn(path("/a/b/c")); + stub(cache2.getPathFor(nodeInfo)).toReturn(path("/a/b/c")); + stub(cache2.findPropertyInfo(propertyId2)).toReturn(propertyInfo); assertThat(node.isSame(otherNode), is(true)); - Property prop = new MockAbstractJcrProperty(node, propertyDefinition, dnaProperty); - Property otherProp = new MockAbstractJcrProperty(otherNode, propertyDefinition, otherDnaProperty); + + Property prop = new MockAbstractJcrProperty(cache, propertyId); + Property otherProp = new MockAbstractJcrProperty(cache2, propertyId2); assertThat(prop.isSame(otherProp), is(false)); } @@ -255,10 +329,9 @@ private class MockAbstractJcrProperty extends AbstractJcrProperty { - MockAbstractJcrProperty( AbstractJcrNode node, - PropertyDefinition propertyDefinition, - org.jboss.dna.graph.property.Property dnaProperty ) { - super(node, propertyDefinition, propertyDefinition.getRequiredType(), dnaProperty); + MockAbstractJcrProperty( SessionCache cache, + PropertyId propertyId ) { + super(cache, propertyId); } /** @@ -266,8 +339,9 @@ * * @see javax.jcr.Property#getNode() */ + @SuppressWarnings( "synthetic-access" ) public Node getNode() { - throw new UnsupportedOperationException(); // shouldn't be called + return node; } /** Index: dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java (revision 776) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java (working copy) @@ -25,13 +25,13 @@ import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.notNullValue; +import static org.hamcrest.core.IsSame.sameInstance; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.stub; import java.io.InputStream; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -39,20 +39,23 @@ import javax.jcr.ItemNotFoundException; import javax.jcr.ItemVisitor; import javax.jcr.Node; +import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.Workspace; -import javax.jcr.nodetype.NodeDefinition; import javax.jcr.version.Version; import org.jboss.dna.graph.ExecutionContext; -import org.jboss.dna.graph.Location; import org.jboss.dna.graph.property.Name; import org.jboss.dna.graph.property.Path; +import org.jboss.dna.jcr.SessionCache.Children; +import org.jboss.dna.jcr.SessionCache.NodeInfo; +import org.jboss.dna.jcr.SessionCache.PropertyInfo; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; @@ -64,36 +67,16 @@ */ public class AbstractJcrNodeTest { - static MockAbstractJcrNode createChild( JcrSession session, - String name, - int index, - List children, - AbstractJcrNode parent ) throws Exception { - MockAbstractJcrNode child = new MockAbstractJcrNode(session, name, parent); - String parentPath = parent.getPath(); - String childPath = parentPath + "/" + name + "[" + index + "]"; - Path path = session.getExecutionContext().getValueFactories().getPathFactory().create(childPath); - Location location = Location.create(path, UUID.randomUUID()); - children.add(location); - // Stub the session to return this node ... - String absolutePath = path.getString(session.getExecutionContext().getNamespaceRegistry()); - stub(session.getChild(parent, location)).toReturn(child); - stub(session.getNode(location.getUuid())).toReturn(child); - stub(session.getItem(absolutePath)).toReturn(child); - return child; - } - static class MockAbstractJcrNode extends AbstractJcrNode { - String name; - Node parent; + MockAbstractJcrNode( SessionCache cache, + UUID uuid ) { + super(cache, uuid); + } - MockAbstractJcrNode( JcrSession session, - String name, - Node parent ) { - super(session, Location.create(UUID.randomUUID()), mock(NodeDefinition.class)); - this.name = name; - this.parent = parent; + @Override + boolean isRoot() { + return false; } @Override @@ -105,45 +88,67 @@ return 0; } - public String getName() { - return name; + public String getName() throws RepositoryException { + return cache.getPathFor(nodeInfo()).getLastSegment().getString(namespaces()); } - public Node getParent() { - return parent; + public Node getParent() throws RepositoryException { + return cache.findJcrNode(nodeInfo().getParent()); } public String getPath() throws RepositoryException { - return (parent == null ? '/' + getName() : parent.getPath() + '/' + getName()); + return cache.getPathFor(nodeInfo()).getString(namespaces()); } } + private ExecutionContext context; + private UUID uuid; private AbstractJcrNode node; + private Children children; @Mock + private SessionCache cache; + @Mock private JcrSession session; @Mock - private Workspace workspace; + private JcrNodeTypeManager nodeTypes; @Mock - private Repository repository; - private List children; - private Map properties; - private ExecutionContext context; + private NodeInfo info; @Before public void before() throws Exception { MockitoAnnotations.initMocks(this); context = new ExecutionContext(); - stub(session.getExecutionContext()).toReturn(context); - children = new ArrayList(); - properties = new HashMap(); - node = new MockAbstractJcrNode(session, "node", null); - node.setProperties(properties); + context.getNamespaceRegistry().register("acme", "http://www.example.com"); + uuid = UUID.randomUUID(); + stub(cache.session()).toReturn(session); + stub(cache.context()).toReturn(context); + stub(cache.workspaceName()).toReturn("my workspace"); + stub(cache.findNodeInfo(uuid)).toReturn(info); + stub(cache.getPathFor(info)).toReturn(path("/a/b/c/d")); + stub(session.nodeTypeManager()).toReturn(nodeTypes); + node = new MockAbstractJcrNode(cache, uuid); + + // Create the children container for the node ... + children = new Children(uuid); + stub(info.getChildren()).toReturn(children); } protected Name name( String name ) { return context.getValueFactories().getNameFactory().create(name); } + protected Path relativePath( String relativePath ) { + return context.getValueFactories().getPathFactory().create(relativePath); + } + + protected Path path( String absolutePath ) { + return context.getValueFactories().getPathFactory().create(absolutePath); + } + + protected Value stringValueFor( Object value ) { + return new JcrValue(context.getValueFactories(), PropertyType.STRING, value); + } + @Test public void shouldAllowVisitation() throws Exception { ItemVisitor visitor = Mockito.mock(ItemVisitor.class); @@ -156,11 +161,6 @@ node.accept(null); } - @Test( expected = AssertionError.class ) - public void shouldNotAllowNoSession() throws Exception { - new MockAbstractJcrNode(null, null, null); - } - @Test( expected = ItemNotFoundException.class ) public void shouldNotAllowNegativeAncestorDepth() throws Exception { node.getAncestor(-1); @@ -172,20 +172,50 @@ } @Test - public void shouldProvideInternalUuid() throws Exception { - UUID uuid = UUID.randomUUID(); - node.setInternalUuid(uuid); - assertThat(node.internalUuid(), is(uuid)); + public void shouldReturnPropertyFromGetPropertyWithValidName() throws Exception { + PropertyId propertyId = new PropertyId(uuid, name("test")); + AbstractJcrProperty property = mock(AbstractJcrProperty.class); + stub(cache.findJcrProperty(propertyId)).toReturn(property); + assertThat(node.getProperty("test"), is((Property)property)); } @Test - public void shouldProvideNamedProperty() throws Exception { - Property property = Mockito.mock(Property.class); - stub(property.getName()).toReturn("test"); - properties.put(name(property.getName()), property); - assertThat(node.getProperty("test"), is(property)); + public void shouldReturnPropertyFromGetPropertyWithValidRelativePath() throws Exception { + PropertyId propertyId = new PropertyId(uuid, name("test")); + AbstractJcrProperty property = mock(AbstractJcrProperty.class); + stub(cache.findJcrProperty(propertyId)).toReturn(property); + assertThat(node.getProperty("test"), is((Property)property)); } + @Test( expected = PathNotFoundException.class ) + public void shouldFailToReturnPropertyFromGetPropertyWithNameOfPropertyThatDoesNotExist() throws Exception { + node.getProperty("nonExistantProperty"); + } + + @Test( expected = IllegalArgumentException.class ) + public void shouldFailToReturnPropertyFromGetPropertyWithAbsolutePath() throws Exception { + node.getProperty("/test"); + } + + @Test( expected = PathNotFoundException.class ) + public void shouldFailToReturnPropertyFromGetPropertyWithRelativePathToNonExistantItem() throws Exception { + node.getProperty("../bogus/path"); + } + + @Test + public void shouldReturnPropertyFromGetPropertyWithRelativePathToPropertyOnOtherNode() throws Exception { + AbstractJcrProperty property = mock(AbstractJcrProperty.class); + stub(cache.findJcrItem(uuid, relativePath("../good/path"))).toReturn(property); + assertThat(node.getProperty("../good/path"), is((Property)property)); + } + + @Test( expected = PathNotFoundException.class ) + public void shouldReturnPropertyFromGetPropertyWithRelativePathToOtherNode() throws Exception { + AbstractJcrNode otherNode = mock(AbstractJcrNode.class); + stub(cache.findJcrItem(uuid, relativePath("../good/path"))).toReturn(otherNode); + node.getProperty("../good/path"); + } + @Test( expected = UnsupportedOperationException.class ) public void shoudNotAllowAddMixin() throws Exception { node.addMixin(null); @@ -241,152 +271,210 @@ node.getLock(); } - @Test - public void shouldProvideNode() throws Exception { - Node child = createChild(session, "child", 1, children, node); - node.setChildren(children); - stub(session.getItem("/node/child")).toReturn(child); - assertThat(node.getNode("child"), is(child)); + @Test( expected = IllegalArgumentException.class ) + public void shouldNotAllowGetPropertyWithNullPath() throws Exception { + node.getProperty((String)null); } @Test( expected = IllegalArgumentException.class ) - public void shouldNotAllowGetNodeWithNoPath() throws Exception { - node.getNode(null); + public void shouldNotAllowGetPropertyWithEmptyPath() throws Exception { + node.getProperty(""); } + @Test + public void shouldReturnChildNodeFromGetNodeWithValidName() throws Exception { + AbstractJcrNode child = mock(AbstractJcrNode.class); + UUID childUuid = UUID.randomUUID(); + children.append(cache, name("child"), childUuid); + stub(cache.findJcrNode(childUuid)).toReturn(child); + assertThat(node.getNode("child"), is((Node)child)); + } + + @Test + public void shouldReturnNonChildNodeFromGetNodeWithValidRelativePath() throws Exception { + AbstractJcrNode otherNode = mock(AbstractJcrNode.class); + stub(cache.findJcrItem(uuid, path("../other/node"))).toReturn(otherNode); + assertThat(node.getNode("../other/node"), is((Node)otherNode)); + } + @Test( expected = PathNotFoundException.class ) - public void shouldNotProvideNodeIfPathNotFound() throws Exception { - node.getNode("bogus"); + public void shouldFailToReturnNodeFromGetNodeWithValidRelativePathToProperty() throws Exception { + AbstractJcrProperty property = mock(AbstractJcrProperty.class); + stub(cache.findJcrItem(uuid, path("../other/node"))).toReturn(property); + node.getNode("../other/node"); } @Test( expected = PathNotFoundException.class ) - public void shouldNotProvideNodeIfPathIsProperty() throws Exception { - Property property = Mockito.mock(Property.class); - stub(session.getItem("/property")).toReturn(property); - node.getNode("property"); + public void shouldFailToReturnNodeFromGetNodeWithValidRelativePathToNoNodeOrProperty() throws Exception { + node.getNode("../other/node"); } @Test - public void shouldProvideNodeIterator() throws Exception { - assertThat(node.getNodes(), notNullValue()); + public void shouldReturnSelfFromGetNodeWithRelativePathContainingOnlySelfReference() throws Exception { + assertThat(node.getNode("."), is((Node)node)); } @Test - public void shoudProvidePrimaryItem() throws Exception { - Property property = Mockito.mock(Property.class); - stub(property.getName()).toReturn("jcr:primaryItemName"); - stub(property.getString()).toReturn("primaryItem"); - properties.put(name(property.getName()), property); - Item primaryItem = Mockito.mock(Item.class); - stub(session.getItem("/node/primaryItem")).toReturn(primaryItem); - assertThat(node.getPrimaryItem(), is(primaryItem)); + public void shouldReturnParentFromGetNodeWithRelativePathContainingOnlyParentReference() throws Exception { + AbstractJcrNode parent = mock(AbstractJcrNode.class); + UUID parentUuid = UUID.randomUUID(); + stub(info.getParent()).toReturn(parentUuid); + stub(cache.findJcrNode(parentUuid)).toReturn(parent); + assertThat(node.getNode(".."), is((Node)parent)); } - @Test( expected = ItemNotFoundException.class ) - public void shoudNotProvidePrimaryItemIfUnavailable() throws Exception { - node.getPrimaryItem(); + @Test( expected = PathNotFoundException.class ) + public void shouldFailToReturnChildNodeFromGetNodeWithNameOfChildThatDoesNotExist() throws Exception { + node.getNode("something"); } + @Test( expected = IllegalArgumentException.class ) + public void shouldNotAllowGetNodeWithNoPath() throws Exception { + node.getNode(null); + } + @Test - public void shouldProvideProperty() throws Exception { - Property prop1 = Mockito.mock(Property.class); - stub(prop1.getName()).toReturn("prop1"); - properties.put(name(prop1.getName()), prop1); - assertThat(node.getProperty("prop1"), is(prop1)); - MockAbstractJcrNode child = createChild(session, "child", 1, children, node); - Map properties = new HashMap(); - child.setProperties(properties); - Property prop2 = Mockito.mock(Property.class); - stub(prop2.getName()).toReturn("prop2"); - stub(session.getItem("/node/child/prop2")).toReturn(prop2); - properties.put(name(prop2.getName()), prop2); - MockAbstractJcrNode prop3Node = createChild(session, "prop3", 1, children, child); - node.setChildren(children); - assertThat(node.getProperty("child/prop2"), is(prop2)); - // Ensure we return a property even when a child exists with the same name - Property prop3 = Mockito.mock(Property.class); - stub(prop3.getName()).toReturn("prop3"); - properties.put(name(prop3.getName()), prop3); - stub(session.getItem("/node/child/prop3")).toReturn(prop3Node); - assertThat(node.getProperty("child/prop3"), is(prop3)); + public void shouldProvideNodeIterator() throws Exception { + AbstractJcrNode child = mock(AbstractJcrNode.class); + UUID childUuid = UUID.randomUUID(); + children.append(cache, name("child"), childUuid); + stub(cache.findJcrNode(childUuid)).toReturn(child); + NodeIterator iter = node.getNodes(); + assertThat(iter, notNullValue()); + assertThat(iter.getSize(), is(1L)); + assertThat(iter.next(), is((Object)child)); } - @Test( expected = IllegalArgumentException.class ) - public void shouldNotAllowGetPropertyWithNullPath() throws Exception { - node.getProperty((String)null); + @Test + public void shoudReturnItemFromGetPrimaryItemIfItExists() throws Exception { + // Define the node type ... + Name primaryTypeName = name("thePrimaryType"); + JcrNodeType primaryType = mock(JcrNodeType.class); + stub(nodeTypes.getNodeType(primaryTypeName)).toReturn(primaryType); + stub(primaryType.getPrimaryItemName()).toReturn("thePrimaryItemName"); + // Define the node info to use this primary type ... + stub(info.getPrimaryTypeName()).toReturn(primaryTypeName); + // Now make an item with the appropriate name ... + AbstractJcrNode child = mock(AbstractJcrNode.class); + stub(cache.findJcrItem(uuid, path("thePrimaryItemName"))).toReturn(child); + // Now call the method ... + assertThat(node.getPrimaryItem(), is((Item)child)); } - @Test( expected = IllegalArgumentException.class ) - public void shouldNotAllowGetPropertyWithEmptyPath() throws Exception { - node.getProperty(""); + @Test( expected = ItemNotFoundException.class ) + public void shoudFailToReturnItemFromGetPrimaryItemIfPrimaryTypeDoesNotHavePrimaryItemName() throws Exception { + // Define the node type ... + Name primaryTypeName = name("thePrimaryType"); + JcrNodeType primaryType = mock(JcrNodeType.class); + stub(nodeTypes.getNodeType(primaryTypeName)).toReturn(primaryType); + stub(primaryType.getPrimaryItemName()).toReturn(null); + // Define the node info to use this primary type ... + stub(info.getPrimaryTypeName()).toReturn(primaryTypeName); + // Now call the method ... + node.getPrimaryItem(); } - @Test( expected = PathNotFoundException.class ) - public void shouldNotProvideChildPropertyIfNotAvailable() throws Exception { - node.getProperty("prop1"); + @Test( expected = ItemNotFoundException.class ) + public void shoudFailToReturnItemFromGetPrimaryItemIfThePrimaryTypeAsInvalidPrimaryItemName() throws Exception { + // Define the node type ... + Name primaryTypeName = name("thePrimaryType"); + JcrNodeType primaryType = mock(JcrNodeType.class); + stub(nodeTypes.getNodeType(primaryTypeName)).toReturn(primaryType); + stub(primaryType.getPrimaryItemName()).toReturn("/this/is/not/valid"); + // Define the node info to use this primary type ... + stub(info.getPrimaryTypeName()).toReturn(primaryTypeName); + // Now call the method ... + node.getPrimaryItem(); } - @Test( expected = PathNotFoundException.class ) - public void shouldNotProvideDescendentPropertyIfNotAvailable() throws Exception { - MockAbstractJcrNode child = createChild(session, "child", 1, children, node); - Map properties = new HashMap(); - child.setProperties(properties); - MockAbstractJcrNode propNode = createChild(session, "prop", 1, children, child); - node.setChildren(children); - stub(session.getItem("/node/child/prop")).toReturn(propNode); - node.getProperty("child/prop"); + @Test( expected = ItemNotFoundException.class ) + public void shoudFailToReturnItemFromGetPrimaryItemIfTheNodeHasNoItemMatchingThatSpecifiedByThePrimaryType() throws Exception { + // Define the node type ... + Name primaryTypeName = name("thePrimaryType"); + JcrNodeType primaryType = mock(JcrNodeType.class); + stub(nodeTypes.getNodeType(primaryTypeName)).toReturn(primaryType); + stub(primaryType.getPrimaryItemName()).toReturn("thePrimaryItemName"); + // Define the node info to use this primary type ... + stub(info.getPrimaryTypeName()).toReturn(primaryTypeName); + // Now call the method ... + stub(cache.findJcrItem(uuid, path("thePrimaryItemName"))).toThrow(new ItemNotFoundException()); + node.getPrimaryItem(); } @Test - public void shouldNotAllowGetReferences() throws Exception { + public void shouldReturnEmptyIteratorFromGetReferencesWhenThereAreNoProperties() throws Exception { + // Set up two properties (one containing references, the other not) ... + List props = Arrays.asList(new AbstractJcrProperty[] {}); + stub(cache.findJcrPropertiesFor(uuid)).toReturn(props); + // Now call the method ... PropertyIterator iter = node.getReferences(); - assertThat(iter, is(notNullValue())); assertThat(iter.getSize(), is(0L)); + assertThat(iter.hasNext(), is(false)); } @Test + public void shouldReturnEmptyIteratorFromGetReferencesWhenThereAreNoReferenceProperties() throws Exception { + // Set up two properties (one containing references, the other not) ... + AbstractJcrProperty propertyA = mock(AbstractJcrProperty.class); + AbstractJcrProperty propertyB = mock(AbstractJcrProperty.class); + List props = Arrays.asList(new AbstractJcrProperty[] {propertyA, propertyB}); + stub(cache.findJcrPropertiesFor(uuid)).toReturn(props); + stub(propertyA.getType()).toReturn(PropertyType.LONG); + stub(propertyB.getType()).toReturn(PropertyType.BOOLEAN); + // Now call the method ... + PropertyIterator iter = node.getReferences(); + assertThat(iter.getSize(), is(0L)); + assertThat(iter.hasNext(), is(false)); + } + + @Test + public void shouldReturnIteratorFromGetReferencesWhenThereIsAtLeastOneReferenceProperty() throws Exception { + // Set up two properties (one containing references, the other not) ... + AbstractJcrProperty propertyA = mock(AbstractJcrProperty.class); + AbstractJcrProperty propertyB = mock(AbstractJcrProperty.class); + List props = Arrays.asList(new AbstractJcrProperty[] {propertyA, propertyB}); + stub(cache.findJcrPropertiesFor(uuid)).toReturn(props); + stub(propertyA.getType()).toReturn(PropertyType.LONG); + stub(propertyB.getType()).toReturn(PropertyType.REFERENCE); + // Now call the method ... + PropertyIterator iter = node.getReferences(); + assertThat(iter.getSize(), is(1L)); + assertThat(iter.next(), is(sameInstance((Object)propertyB))); + assertThat(iter.hasNext(), is(false)); + } + + @Test public void shouldProvideSession() throws Exception { assertThat((JcrSession)node.getSession(), is(session)); } @Test public void shouldProvideUuidIfReferenceable() throws Exception { - String uuid = "uuid"; - Property mixinProp = Mockito.mock(Property.class); - stub(mixinProp.getName()).toReturn("jcr:mixinTypes"); - Value value = Mockito.mock(Value.class); - stub(value.getString()).toReturn("mix:referenceable"); - stub(mixinProp.getValues()).toReturn(new Value[] {value}); - properties.put(name(mixinProp.getName()), mixinProp); - Property uuidProp = Mockito.mock(Property.class); - stub(uuidProp.getName()).toReturn("jcr:uuid"); - stub(uuidProp.getString()).toReturn(uuid); - properties.put(name(uuidProp.getName()), uuidProp); - assertThat(node.getUUID(), is(uuid)); + // Create the property ... + PropertyId propertyId = new PropertyId(uuid, name("jcr:mixinTypes")); + AbstractJcrProperty property = mock(AbstractJcrProperty.class); + stub(cache.findJcrProperty(propertyId)).toReturn(property); + stub(property.getValues()).toReturn(new Value[] {stringValueFor("acme:someMixin"), stringValueFor("mix:referenceable")}); + // Call the method ... + assertThat(node.getUUID(), is(uuid.toString())); } @Test( expected = UnsupportedRepositoryOperationException.class ) public void shouldNotProvideUuidIfNotReferenceable() throws Exception { - String uuid = "uuid"; - Property mixinProp = Mockito.mock(Property.class); - stub(mixinProp.getName()).toReturn("jcr:mixinTypes"); - Value value = Mockito.mock(Value.class); - stub(mixinProp.getValues()).toReturn(new Value[] {value}); - properties.put(name(mixinProp.getName()), mixinProp); - Property uuidProp = Mockito.mock(Property.class); - stub(uuidProp.getName()).toReturn("jcr:uuid"); - stub(uuidProp.getString()).toReturn(uuid); - properties.put(name(uuidProp.getName()), uuidProp); + // Create the property ... + PropertyId propertyId = new PropertyId(uuid, name("jcr:mixinTypes")); + AbstractJcrProperty property = mock(AbstractJcrProperty.class); + stub(cache.findJcrProperty(propertyId)).toReturn(property); + stub(property.getValues()).toReturn(new Value[] {stringValueFor("acme:someMixin"), stringValueFor("mix:notReferenceable")}); + // Call the method ... node.getUUID(); } @Test( expected = UnsupportedRepositoryOperationException.class ) public void shouldNotProvideUuidIfNoMixinTypes() throws Exception { - String uuid = "uuid"; - Property uuidProp = Mockito.mock(Property.class); - stub(uuidProp.getName()).toReturn("jcr:uuid"); - stub(uuidProp.getString()).toReturn(uuid); - properties.put(name(uuidProp.getName()), uuidProp); + PropertyId propertyId = new PropertyId(uuid, name("jcr:mixinTypes")); + stub(cache.findJcrProperty(propertyId)).toReturn(null); + // Call the method ... node.getUUID(); } @@ -395,76 +483,134 @@ node.getVersionHistory(); } - @Test - public void shouldProvideHasNode() throws Exception { - assertThat(node.hasNode("{}child"), is(false)); - Property prop = Mockito.mock(Property.class); - stub(prop.getName()).toReturn("prop"); - properties.put(name(prop.getName()), prop); - assertThat(node.hasNode("prop"), is(false)); - AbstractJcrNode child = createChild(session, "child", 1, children, node); - AbstractJcrNode child2 = createChild(session, "child2", 1, children, child); - node.setChildren(children); - assertThat(node.hasNode("child"), is(true)); - stub(session.getItem("/node/child/{}child2")).toReturn(child2); - assertThat(node.hasNode("child/{}child2"), is(true)); + @Test( expected = IllegalArgumentException.class ) + public void shouldNotAllowNullPathInHasNode() throws Exception { + node.hasNode((String)null); } @Test( expected = IllegalArgumentException.class ) - public void shouldNotAllowHasNodeWithNoPath() throws Exception { - node.hasNode(null); + public void shouldNotAllowEmptyPathInHasNode() throws Exception { + node.hasNode(""); } @Test( expected = IllegalArgumentException.class ) - public void shouldNotAllowHasNodeWithEmptyPath() throws Exception { - node.hasNode(""); + public void shouldNotAbsolutePathInHasNode() throws Exception { + node.hasNode("/a/b/c"); } @Test - public void shouldProvideHasNodes() throws Exception { - assertThat(node.hasNodes(), is(false)); - createChild(session, "child", 1, children, node); - node.setChildren(children); - assertThat(node.hasNodes(), is(true)); + public void shouldReturnTrueFromHasNodeWithSelfReference() throws Exception { + assertThat(node.hasNode("."), is(true)); } @Test - public void shouldProvideHasProperties() throws Exception { - assertThat(node.hasProperties(), is(false)); - properties.put(name("something"), Mockito.mock(Property.class)); - assertThat(node.hasProperties(), is(true)); + public void shouldReturnTrueFromHasNodeWithParentReferenceIfNotRootNode() throws Exception { + assertThat(node.hasNode(".."), is(!node.isRoot())); } @Test - public void shouldIndicateHasProperty() throws Exception { - assertThat(node.hasProperty("prop"), is(false)); - MockAbstractJcrNode child = createChild(session, "child", 1, children, node); - node.setChildren(children); - assertThat(node.hasProperty("child"), is(false)); - Property prop = Mockito.mock(Property.class); - stub(prop.getName()).toReturn("prop"); - properties.put(name(prop.getName()), prop); - assertThat(node.hasProperty("prop"), is(true)); - Map properties = new HashMap(); - child.setProperties(properties); - Property prop2 = Mockito.mock(Property.class); - stub(prop2.getName()).toReturn("prop2"); - properties.put(name(prop2.getName()), prop2); - stub(session.getItem("/node/child/prop2")).toReturn(prop2); - assertThat(node.hasProperty("child/prop2"), is(true)); + public void shouldReturnTrueFromHasNodeIfRelativePathIdentifiesExistingNode() throws Exception { + AbstractJcrNode otherNode = mock(AbstractJcrNode.class); + stub(cache.findJcrNode(uuid, path("../other/node"))).toReturn(otherNode); + assertThat(node.hasNode("../other/node"), is(true)); } + @Test + public void shouldReturnTrueFromHasNodeIfChildWithNameExists() throws Exception { + children.append(cache, name("child"), UUID.randomUUID()); + assertThat(node.hasNode("child[1]"), is(true)); + assertThat(node.hasNode("child[2]"), is(false)); + children.append(cache, name("child"), UUID.randomUUID()); + assertThat(node.hasNode("child[2]"), is(true)); + assertThat(node.hasNode("child[3]"), is(false)); + } + + @Test + public void shouldReturnFalseFromHasNodeWithNameOfChildThatDoesNotExist() throws Exception { + assertThat(node.hasNode("something"), is(false)); + } + + @Test + public void shouldReturnFalseFromHasNodesIfThereAreNoChildren() throws Exception { + assertThat(node.hasNodes(), is(false)); + } + + @Test + public void shouldReturnTrueFromHasNodesIfThereIsAtLeastOneChild() throws Exception { + children.append(cache, name("child"), UUID.randomUUID()); + assertThat(node.hasNodes(), is(true)); + } + @Test( expected = IllegalArgumentException.class ) - public void shouldNotAllowHasPropertyWithNoPath() throws Exception { - node.hasProperty(null); + public void shouldNotAllowNullPathInHasProperty() throws Exception { + node.hasProperty((String)null); } @Test( expected = IllegalArgumentException.class ) - public void shouldNotAllowHasPropertyWithEmptyPath() throws Exception { + public void shouldNotAllowEmptyPathInHasProperty() throws Exception { node.hasProperty(""); } + @Test( expected = IllegalArgumentException.class ) + public void shouldNotAbsolutePathInHasProperty() throws Exception { + node.hasProperty("/a/b/c"); + } + @Test + public void shouldReturnTrueFromHasPropertyIfPathIsRelativePathToOtherNodeWithNamedProperty() throws Exception { + AbstractJcrProperty property = mock(AbstractJcrProperty.class); + stub(cache.findJcrItem(uuid, relativePath("../good/path"))).toReturn(property); + assertThat(node.hasProperty("../good/path"), is(true)); + } + + @Test + public void shouldReturnFalseFromHasPropertyIfPathIsRelativePathToOtherNodeWithoutNamedProperty() throws Exception { + stub(cache.findJcrItem(uuid, relativePath("../good/path"))).toThrow(new PathNotFoundException()); + assertThat(node.hasProperty("../good/path"), is(false)); + } + + @Test + public void shouldReturnFalseFromHasPropertyIfPathIsParentPath() throws Exception { + assertThat(node.hasProperty(".."), is(false)); + } + + @Test + public void shouldReturnFalseFromHasPropertyIfPathIsSelfPath() throws Exception { + assertThat(node.hasProperty("."), is(false)); + } + + @Test + public void shouldReturnTrueFromHasPropertyIfPathIsNameAndNodeHasProperty() throws Exception { + PropertyInfo propertyInfo = mock(PropertyInfo.class); + stub(cache.findPropertyInfo(new PropertyId(uuid, name("prop")))).toReturn(propertyInfo); + assertThat(node.hasProperty("prop"), is(true)); + } + + @Test + public void shouldReturnFalseFromHasPropertyIfPathIsNameAndNodeDoesNotHaveProperty() throws Exception { + stub(cache.findPropertyInfo(new PropertyId(uuid, name("prop")))).toReturn(null); + assertThat(node.hasProperty("prop"), is(false)); + } + + @Test + @SuppressWarnings( "unchecked" ) + public void shouldReturnTrueFromHasPropertiesIfNodeHasAtLeastOneProperty() throws Exception { + Map properties = mock(Map.class); + stub(properties.size()).toReturn(5); + stub(info.getProperties()).toReturn(properties); + assertThat(node.hasProperties(), is(true)); + } + + @Test + @SuppressWarnings( "unchecked" ) + public void shouldReturnFalseFromHasPropertiesIfNodeHasNoProperties() throws Exception { + Map properties = mock(Map.class); + stub(properties.size()).toReturn(0); + stub(info.getProperties()).toReturn(properties); + assertThat(node.hasProperties(), is(false)); + } + + @Test public void shouldNotAllowHoldsLock() throws Exception { assertThat(node.holdsLock(), is(false)); } @@ -485,29 +631,87 @@ } @Test - public void shouldProvideIsSame() throws Exception { - stub(session.getWorkspace()).toReturn(Mockito.mock(Workspace.class)); - stub(session.getRepository()).toReturn(repository); + public void shouldReturnFalseFromIsSameIfTheRepositoryInstanceIsDifferent() throws Exception { + Workspace workspace1 = mock(Workspace.class); + Repository repository1 = mock(Repository.class); + stub(session.getWorkspace()).toReturn(workspace1); + stub(session.getRepository()).toReturn(repository1); + stub(workspace1.getName()).toReturn("workspace1"); + + Workspace workspace2 = mock(Workspace.class); JcrSession session2 = Mockito.mock(JcrSession.class); - JcrRepository repository2 = Mockito.mock(JcrRepository.class); + Repository repository2 = mock(Repository.class); + SessionCache cache2 = mock(SessionCache.class); + stub(session2.getWorkspace()).toReturn(workspace2); + stub(session2.getRepository()).toReturn(repository2); + stub(workspace2.getName()).toReturn("workspace1"); + stub(cache2.session()).toReturn(session2); - stub(session2.getRepository()).toReturn(repository2); - stub(session2.getWorkspace()).toReturn(workspace); - Node node2 = new MockAbstractJcrNode(session2, node.getName(), node.getParent()); + UUID uuid2 = uuid; + Node node2 = new MockAbstractJcrNode(cache2, uuid2); assertThat(node.isSame(node2), is(false)); + } - Property prop = Mockito.mock(Property.class); - stub(prop.getSession()).toReturn(session); - assertThat(node.isSame(prop), is(false)); - node2 = Mockito.mock(Node.class); - stub(node2.getSession()).toReturn(session); + @Test + public void shouldReturnFalseFromIsSameIfTheWorkspaceNameIsDifferent() throws Exception { + Workspace workspace1 = mock(Workspace.class); + Repository repository1 = mock(Repository.class); + stub(session.getWorkspace()).toReturn(workspace1); + stub(session.getRepository()).toReturn(repository1); + stub(workspace1.getName()).toReturn("workspace1"); + + Workspace workspace2 = mock(Workspace.class); + JcrSession session2 = Mockito.mock(JcrSession.class); + SessionCache cache2 = mock(SessionCache.class); + stub(session2.getWorkspace()).toReturn(workspace2); + stub(session2.getRepository()).toReturn(repository1); + stub(workspace2.getName()).toReturn("workspace2"); + stub(cache2.session()).toReturn(session2); + + UUID uuid2 = uuid; + Node node2 = new MockAbstractJcrNode(cache2, uuid2); assertThat(node.isSame(node2), is(false)); - node2 = new MockAbstractJcrNode(session, node.getName(), node.getParent()); - UUID uuid = UUID.randomUUID(); - node.setInternalUuid(uuid); - ((MockAbstractJcrNode)node2).setInternalUuid(UUID.randomUUID()); + } + + @Test + public void shouldReturnFalseFromIsSameIfTheNodeUuidIsDifferent() throws Exception { + Workspace workspace1 = mock(Workspace.class); + Repository repository1 = mock(Repository.class); + stub(session.getWorkspace()).toReturn(workspace1); + stub(session.getRepository()).toReturn(repository1); + stub(workspace1.getName()).toReturn("workspace1"); + + Workspace workspace2 = mock(Workspace.class); + JcrSession session2 = Mockito.mock(JcrSession.class); + SessionCache cache2 = mock(SessionCache.class); + stub(session2.getWorkspace()).toReturn(workspace2); + stub(session2.getRepository()).toReturn(repository1); + stub(workspace2.getName()).toReturn("workspace1"); + stub(cache2.session()).toReturn(session2); + + UUID uuid2 = UUID.randomUUID(); + Node node2 = new MockAbstractJcrNode(cache2, uuid2); assertThat(node.isSame(node2), is(false)); - ((MockAbstractJcrNode)node2).setInternalUuid(uuid); + } + + @Test + public void shouldReturnTrueFromIsSameIfTheNodeUuidAndWorkspaceNameAndRepositoryInstanceAreSame() throws Exception { + Workspace workspace1 = mock(Workspace.class); + Repository repository1 = mock(Repository.class); + stub(session.getWorkspace()).toReturn(workspace1); + stub(session.getRepository()).toReturn(repository1); + stub(workspace1.getName()).toReturn("workspace1"); + + Workspace workspace2 = mock(Workspace.class); + JcrSession session2 = Mockito.mock(JcrSession.class); + SessionCache cache2 = mock(SessionCache.class); + stub(session2.getWorkspace()).toReturn(workspace2); + stub(session2.getRepository()).toReturn(repository1); + stub(workspace2.getName()).toReturn("workspace1"); + stub(cache2.session()).toReturn(session2); + + UUID uuid2 = uuid; + Node node2 = new MockAbstractJcrNode(cache2, uuid2); assertThat(node.isSame(node2), is(true)); } Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrEmptyPropertyIteratorTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrEmptyPropertyIteratorTest.java (revision 0) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrEmptyPropertyIteratorTest.java (revision 0) @@ -0,0 +1,70 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * Unless otherwise indicated, all code in JBoss DNA is licensed + * to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.jcr; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import java.util.NoSuchElementException; +import javax.jcr.PropertyIterator; +import org.junit.Before; +import org.junit.Test; + +/** + * + */ +public class JcrEmptyPropertyIteratorTest { + + private PropertyIterator iter; + + @Before + public void beforeEach() { + iter = new JcrEmptyPropertyIterator(); + } + + @Test + public void shouldNotHaveNext() { + assertThat(iter.hasNext(), is(false)); + } + + @Test + public void shouldHavePositionOfZero() { + assertThat(iter.getPosition(), is(0L)); + } + + @Test + public void shouldHaveSizeOfZero() { + assertThat(iter.getSize(), is(0L)); + } + + @Test( expected = UnsupportedOperationException.class ) + public void shouldNotAllowRemove() { + iter.remove(); + } + + @Test( expected = NoSuchElementException.class ) + public void shouldFailWhenNextIsCalled() { + iter.next(); + } + +} Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrEmptyNodeIteratorTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrEmptyNodeIteratorTest.java (revision 0) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrEmptyNodeIteratorTest.java (revision 0) @@ -0,0 +1,70 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * Unless otherwise indicated, all code in JBoss DNA is licensed + * to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.jcr; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import java.util.NoSuchElementException; +import javax.jcr.NodeIterator; +import org.junit.Before; +import org.junit.Test; + +/** + * + */ +public class JcrEmptyNodeIteratorTest { + + private NodeIterator iter; + + @Before + public void beforeEach() { + iter = new JcrEmptyNodeIterator(); + } + + @Test + public void shouldNotHaveNext() { + assertThat(iter.hasNext(), is(false)); + } + + @Test + public void shouldHavePositionOfZero() { + assertThat(iter.getPosition(), is(0L)); + } + + @Test + public void shouldHaveSizeOfZero() { + assertThat(iter.getSize(), is(0L)); + } + + @Test( expected = UnsupportedOperationException.class ) + public void shouldNotAllowRemove() { + iter.remove(); + } + + @Test( expected = NoSuchElementException.class ) + public void shouldFailWhenNextIsCalled() { + iter.next(); + } + +} Index: dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrItemTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrItemTest.java (revision 776) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrItemTest.java (working copy) @@ -27,24 +27,36 @@ import static org.junit.Assert.assertThat; import javax.jcr.ItemVisitor; import javax.jcr.Node; -import javax.jcr.Session; +import org.jboss.dna.graph.property.Path; import org.junit.Before; import org.junit.Test; +import org.mockito.MockitoAnnotations; +import org.mockito.MockitoAnnotations.Mock; -/** - * @author jverhaeg - */ public class AbstractJcrItemTest { private AbstractJcrItem item; + @Mock + private SessionCache cache; @Before public void before() { - item = new AbstractJcrItem() { + MockitoAnnotations.initMocks(this); + item = new AbstractJcrItem(cache) { public void accept( ItemVisitor visitor ) { } + /** + * {@inheritDoc} + * + * @see org.jboss.dna.jcr.AbstractJcrItem#path() + */ + @Override + Path path() { + throw new UnsupportedOperationException(); + } + public String getName() { return null; } @@ -57,10 +69,6 @@ return null; } - public Session getSession() { - return null; - } - public boolean isNode() { return false; } Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrSessionTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrSessionTest.java (revision 776) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrSessionTest.java (working copy) @@ -23,13 +23,13 @@ */ package org.jboss.dna.jcr; -import static org.junit.Assert.assertTrue; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.hamcrest.core.IsNot.not; import static org.hamcrest.core.IsNull.notNullValue; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.stub; import java.io.ByteArrayOutputStream; @@ -71,7 +71,7 @@ public class JcrSessionTest { private static final String MULTI_LINE_VALUE = "Line\t1\nLine 2\rLine 3\r\nLine 4"; - + private String workspaceName; private ExecutionContext context; private InMemoryRepositorySource source; @@ -102,7 +102,7 @@ graph.set("booleanProperty").on("/a/b").to(true); graph.set("stringProperty").on("/a/b/c").to("value"); graph.set("multiLineProperty").on("/a/b/c").to(MULTI_LINE_VALUE); - + // Make sure the path to the namespaces exists ... graph.create("/jcr:system").and().create("/jcr:system/dna:namespaces"); @@ -287,13 +287,10 @@ @Test public void shouldProvideRootNode() throws Exception { - Map nodesByUuid = session.getNodesByUuid(); - assertThat(nodesByUuid.isEmpty(), is(true)); Node root = session.getRootNode(); assertThat(root, notNullValue()); UUID uuid = ((JcrRootNode)root).internalUuid(); assertThat(uuid, notNullValue()); - assertThat(nodesByUuid.get(uuid), is(root)); } @Test @@ -394,14 +391,14 @@ assertThat(session.getNamespaceURI("sv"), is("http://www.jcp.org/jcr/sv/1.0")); // assertThat(session.getNamespaceURI("xml"), is("http://www.w3.org/XML/1998/namespace")); } - + @Test public void shouldExportMultiLinePropertiesInSystemView() throws Exception { OutputStream os = new ByteArrayOutputStream(); - + session.exportSystemView("/a/b/c", os, false, true); - + String fileContents = os.toString(); - assertTrue(fileContents.contains(MULTI_LINE_VALUE)); + assertTrue(fileContents.contains(MULTI_LINE_VALUE)); } } Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrSingleValuePropertyTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrSingleValuePropertyTest.java (revision 776) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrSingleValuePropertyTest.java (working copy) @@ -31,17 +31,15 @@ import java.io.InputStream; import java.util.UUID; import javax.jcr.Node; -import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.Value; import javax.jcr.ValueFormatException; -import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.jboss.dna.graph.ExecutionContext; -import org.jboss.dna.graph.Location; import org.jboss.dna.graph.property.DateTime; import org.jboss.dna.graph.property.Name; import org.jboss.dna.graph.property.Path; +import org.jboss.dna.jcr.SessionCache.PropertyInfo; import org.junit.Before; import org.junit.Test; import org.mockito.MockitoAnnotations; @@ -52,39 +50,57 @@ */ public class JcrSingleValuePropertyTest { - private Property prop; - private AbstractJcrNode node; + private PropertyId propertyId; + private JcrSingleValueProperty prop; private ExecutionContext executionContext; + private org.jboss.dna.graph.property.Property dnaProperty; @Mock + private SessionCache cache; + @Mock private JcrSession session; @Mock - private NodeDefinition nodeDefinition; + private PropertyInfo propertyInfo; @Mock - Name name; + private JcrPropertyDefinition definition; @Mock - private PropertyDefinition definition; - private org.jboss.dna.graph.property.Property dnaProperty; + private JcrNodeTypeManager nodeTypes; @Before - public void before() { + public void before() throws Exception { MockitoAnnotations.initMocks(this); executionContext = new ExecutionContext(); - - UUID rootUuid = UUID.randomUUID(); - Path rootPath = executionContext.getValueFactories().getPathFactory().createRootPath(); - Location rootLocation = Location.create(rootPath, rootUuid); - node = new JcrRootNode(session, rootLocation, nodeDefinition); + stub(cache.session()).toReturn(session); + stub(cache.context()).toReturn(executionContext); + stub(session.nodeTypeManager()).toReturn(nodeTypes); stub(session.getExecutionContext()).toReturn(executionContext); dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, "text/plain"); + stub(definition.getRequiredType()).toReturn(PropertyType.STRING); stub(definition.isMultiple()).toReturn(false); - prop = new JcrSingleValueProperty(node, definition, PropertyType.STRING, dnaProperty); + PropertyDefinitionId definitionId = new PropertyDefinitionId(name("nodeTypeName"), name("propDefnName")); + stub(nodeTypes.getPropertyDefinition(definitionId, false)).toReturn(definition); + + UUID uuid = UUID.randomUUID(); + propertyId = new PropertyId(uuid, JcrLexicon.MIMETYPE); + prop = new JcrSingleValueProperty(cache, propertyId); + + stub(cache.findPropertyInfo(propertyId)).toReturn(propertyInfo); + stub(propertyInfo.getDefinitionId()).toReturn(definitionId); + stub(propertyInfo.getPropertyType()).toReturn(PropertyType.STRING); + stub(propertyInfo.isMultiValued()).toReturn(false); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); + stub(propertyInfo.getPropertyName()).toReturn(dnaProperty.getName()); } + protected Name name( String name ) { + return executionContext.getValueFactories().getNameFactory().create(name); + } + @Test public void shouldProvideBoolean() throws Exception { + stub(propertyInfo.getPropertyType()).toReturn(PropertyType.BOOLEAN); dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, "true"); - prop = new JcrSingleValueProperty(node, definition, PropertyType.BOOLEAN, dnaProperty); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); assertThat(prop.getBoolean(), is(true)); assertThat(prop.getType(), is(PropertyType.BOOLEAN)); } @@ -92,7 +108,6 @@ @Test public void shouldIndicateHasSingleValue() throws Exception { PropertyDefinition def = prop.getDefinition(); - assertThat(def, notNullValue()); assertThat(def.isMultiple(), is(false)); } @@ -100,7 +115,8 @@ public void shouldProvideDate() throws Exception { DateTime dnaDate = executionContext.getValueFactories().getDateFactory().create(); dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, dnaDate); - prop = new JcrSingleValueProperty(node, definition, PropertyType.DATE, dnaProperty); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); + stub(propertyInfo.getPropertyType()).toReturn(PropertyType.DATE); assertThat(prop.getDate(), is(dnaDate.toCalendar())); assertThat(prop.getLong(), is(dnaDate.getMilliseconds())); assertThat(prop.getType(), is(PropertyType.DATE)); @@ -111,9 +127,10 @@ public void shouldProvideNode() throws Exception { UUID referencedUuid = UUID.randomUUID(); dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, referencedUuid); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); + stub(propertyInfo.getPropertyType()).toReturn(PropertyType.REFERENCE); AbstractJcrNode referencedNode = mock(AbstractJcrNode.class); - stub(session.getNode(referencedUuid)).toReturn(referencedNode); - prop = new JcrSingleValueProperty(node, definition, PropertyType.REFERENCE, dnaProperty); + stub(cache.findJcrNode(referencedUuid)).toReturn(referencedNode); assertThat(prop.getNode(), is((Node)referencedNode)); assertThat(prop.getType(), is(PropertyType.REFERENCE)); assertThat(prop.getName(), is(dnaProperty.getName().getString(executionContext.getNamespaceRegistry()))); @@ -122,12 +139,13 @@ @Test public void shouldProvideDouble() throws Exception { dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, 1.0); - prop = new JcrSingleValueProperty(node, definition, PropertyType.DOUBLE, dnaProperty); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); + stub(propertyInfo.getPropertyType()).toReturn(PropertyType.DOUBLE); assertThat(prop.getDouble(), is(1.0)); assertThat(prop.getType(), is(PropertyType.DOUBLE)); assertThat(prop.getName(), is(dnaProperty.getName().getString(executionContext.getNamespaceRegistry()))); dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, 1.0F); - prop = new JcrSingleValueProperty(node, definition, PropertyType.DOUBLE, dnaProperty); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); assertThat(prop.getDouble(), is(1.0)); assertThat(prop.getType(), is(PropertyType.DOUBLE)); assertThat(prop.getName(), is(dnaProperty.getName().getString(executionContext.getNamespaceRegistry()))); @@ -136,23 +154,26 @@ @Test public void shouldProvideLong() throws Exception { dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, 1); - prop = new JcrSingleValueProperty(node, definition, PropertyType.DOUBLE, dnaProperty); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); + stub(propertyInfo.getPropertyType()).toReturn(PropertyType.LONG); assertThat(prop.getLong(), is(1L)); - assertThat(prop.getType(), is(PropertyType.DOUBLE)); + assertThat(prop.getType(), is(PropertyType.LONG)); assertThat(prop.getName(), is(dnaProperty.getName().getString(executionContext.getNamespaceRegistry()))); dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, 1L); - prop = new JcrSingleValueProperty(node, definition, PropertyType.DOUBLE, dnaProperty); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); + stub(propertyInfo.getPropertyType()).toReturn(PropertyType.LONG); assertThat(prop.getLong(), is(1L)); assertThat(prop.getString(), is("1")); - assertThat(prop.getType(), is(PropertyType.DOUBLE)); + assertThat(prop.getType(), is(PropertyType.LONG)); assertThat(prop.getName(), is(dnaProperty.getName().getString(executionContext.getNamespaceRegistry()))); } @Test public void shouldProvideStream() throws Exception { dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, new Object()); - prop = new JcrSingleValueProperty(node, definition, PropertyType.BINARY, dnaProperty); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); + stub(propertyInfo.getPropertyType()).toReturn(PropertyType.BINARY); assertThat(prop.getType(), is(PropertyType.BINARY)); assertThat(prop.getName(), is(dnaProperty.getName().getString(executionContext.getNamespaceRegistry()))); InputStream stream = prop.getStream(); @@ -168,7 +189,8 @@ @Test public void shouldProvideString() throws Exception { dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, "value"); - prop = new JcrSingleValueProperty(node, definition, PropertyType.STRING, dnaProperty); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); + stub(propertyInfo.getPropertyType()).toReturn(PropertyType.STRING); assertThat(prop.getString(), is("value")); assertThat(prop.getType(), is(PropertyType.STRING)); assertThat(prop.getName(), is(dnaProperty.getName().getString(executionContext.getNamespaceRegistry()))); @@ -178,7 +200,8 @@ public void shouldAllowReferenceValue() throws Exception { UUID uuid = UUID.randomUUID(); dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, uuid); - prop = new JcrSingleValueProperty(node, definition, PropertyType.STRING, dnaProperty); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); + stub(propertyInfo.getPropertyType()).toReturn(PropertyType.STRING); assertThat(prop.getString(), is(uuid.toString())); assertThat(prop.getType(), is(PropertyType.STRING)); assertThat(prop.getName(), is(dnaProperty.getName().getString(executionContext.getNamespaceRegistry()))); @@ -189,7 +212,8 @@ executionContext.getNamespaceRegistry().register("acme", "http://example.com"); Name path = executionContext.getValueFactories().getNameFactory().create("acme:something"); dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, path); - prop = new JcrSingleValueProperty(node, definition, PropertyType.NAME, dnaProperty); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); + stub(propertyInfo.getPropertyType()).toReturn(PropertyType.NAME); assertThat(prop.getString(), is("acme:something")); assertThat(prop.getType(), is(PropertyType.NAME)); assertThat(prop.getName(), is(dnaProperty.getName().getString(executionContext.getNamespaceRegistry()))); @@ -203,7 +227,8 @@ executionContext.getNamespaceRegistry().register("acme", "http://example.com"); Path path = executionContext.getValueFactories().getPathFactory().create("/a/b/acme:c"); dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, (Object)path); - prop = new JcrSingleValueProperty(node, definition, PropertyType.PATH, dnaProperty); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); + stub(propertyInfo.getPropertyType()).toReturn(PropertyType.PATH); assertThat(prop.getString(), is("/a/b/acme:c")); assertThat(prop.getType(), is(PropertyType.PATH)); assertThat(prop.getName(), is(dnaProperty.getName().getString(executionContext.getNamespaceRegistry()))); @@ -230,13 +255,13 @@ dnaValue = "some other value"; dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, dnaValue); - prop = new JcrSingleValueProperty(node, definition, PropertyType.STRING, dnaProperty); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); assertThat(prop.getLength(), is((long)dnaValue.length())); Object obj = new Object(); long binaryLength = executionContext.getValueFactories().getBinaryFactory().create(obj).getSize(); dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, obj); - prop = new JcrSingleValueProperty(node, definition, PropertyType.BINARY, dnaProperty); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); assertThat(prop.getLength(), is(binaryLength)); } Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrNodeTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrNodeTest.java (revision 776) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrNodeTest.java (working copy) @@ -25,17 +25,14 @@ import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.stub; -import java.util.ArrayList; -import java.util.HashMap; import java.util.UUID; import javax.jcr.ItemNotFoundException; -import javax.jcr.Property; -import javax.jcr.nodetype.NodeDefinition; import org.jboss.dna.graph.ExecutionContext; -import org.jboss.dna.graph.Location; import org.jboss.dna.graph.property.Name; import org.jboss.dna.graph.property.Path; +import org.jboss.dna.jcr.SessionCache.NodeInfo; import org.junit.Before; import org.junit.Test; import org.mockito.MockitoAnnotations; @@ -46,56 +43,62 @@ */ public class JcrNodeTest { + private UUID uuid; private JcrNode node; - private AbstractJcrNode root; + private ExecutionContext context; @Mock - private JcrSession session; - @Mock - private NodeDefinition rootNodeDefinition; - @Mock - private NodeDefinition nodeDefinition; + private SessionCache cache; @Before public void before() throws Exception { MockitoAnnotations.initMocks(this); - ExecutionContext context = new ExecutionContext(); - UUID rootUuid = UUID.randomUUID(); - Path rootPath = context.getValueFactories().getPathFactory().createRootPath(); - Location rootLocation = Location.create(rootPath, rootUuid); - root = new JcrRootNode(session, rootLocation, rootNodeDefinition); - UUID uuid = UUID.randomUUID(); - Path path = context.getValueFactories().getPathFactory().create("/name[2]"); - Location location = Location.create(path, uuid); - node = new JcrNode(session, rootUuid, location, nodeDefinition); - stub(session.getExecutionContext()).toReturn(context); - stub(session.getNode(rootUuid)).toReturn(root); - stub(session.getNode(uuid)).toReturn(node); - node.setProperties(new HashMap()); - node.setChildren(new ArrayList()); + + uuid = UUID.randomUUID(); + node = new JcrNode(cache, uuid); + + context = new ExecutionContext(); + stub(cache.context()).toReturn(context); } + protected Name name( String name ) { + return context.getValueFactories().getNameFactory().create(name); + } + + protected Path path( String path ) { + return context.getValueFactories().getPathFactory().create(path); + } + @Test( expected = ItemNotFoundException.class ) public void shouldNotAllowAncestorDepthGreaterThanNodeDepth() throws Exception { - node.getAncestor(2); + NodeInfo info = mock(NodeInfo.class); + stub(cache.findNodeInfo(uuid)).toReturn(info); + stub(cache.getPathFor(info)).toReturn(path("/a/b/c/name[2]")); + node.getAncestor(6); } @Test public void shouldProvideDepth() throws Exception { - assertThat(node.getDepth(), is(1)); + NodeInfo info = mock(NodeInfo.class); + stub(cache.findNodeInfo(uuid)).toReturn(info); + stub(cache.getPathFor(info)).toReturn(path("/a/b/c/name[2]")); + assertThat(node.getDepth(), is(4)); } @Test public void shouldProvideIndex() throws Exception { - assertThat(node.getIndex(), is(2)); + stub(cache.getSnsIndexOf(uuid)).toReturn(1); + assertThat(node.getIndex(), is(1)); } @Test public void shouldProvideName() throws Exception { + stub(cache.getNameOf(uuid)).toReturn(name("name")); assertThat(node.getName(), is("name")); } @Test public void shouldProvidePath() throws Exception { - assertThat(node.getPath(), is("/name[2]")); + stub(cache.getPathFor(uuid)).toReturn(path("/a/b/c/name[2]")); + assertThat(node.getPath(), is("/a/b/c/name[2]")); } } Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRootNodeTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRootNodeTest.java (revision 776) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRootNodeTest.java (working copy) @@ -26,14 +26,10 @@ import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.notNullValue; import static org.junit.Assert.assertThat; -import java.util.HashMap; -import java.util.Map; +import static org.mockito.Mockito.stub; import java.util.UUID; import javax.jcr.ItemNotFoundException; -import javax.jcr.Property; -import javax.jcr.nodetype.NodeDefinition; import org.jboss.dna.graph.ExecutionContext; -import org.jboss.dna.graph.Location; import org.jboss.dna.graph.property.Name; import org.jboss.dna.graph.property.Path; import org.junit.Before; @@ -46,25 +42,31 @@ */ public class JcrRootNodeTest { + private UUID uuid; private JcrRootNode root; + private ExecutionContext context; @Mock - private JcrSession session; - @Mock - private NodeDefinition nodeDefinition; - private Map properties; + private SessionCache cache; @Before public void before() throws Exception { MockitoAnnotations.initMocks(this); - properties = new HashMap(); - ExecutionContext context = new ExecutionContext(); - UUID rootUuid = UUID.randomUUID(); - Path rootPath = context.getValueFactories().getPathFactory().createRootPath(); - Location rootLocation = Location.create(rootPath, rootUuid); - root = new JcrRootNode(session, rootLocation, nodeDefinition); - root.setProperties(properties); + + uuid = UUID.randomUUID(); + root = new JcrRootNode(cache, uuid); + + context = new ExecutionContext(); + stub(cache.context()).toReturn(context); } + protected Name name( String name ) { + return context.getValueFactories().getNameFactory().create(name); + } + + protected Path path( String path ) { + return context.getValueFactories().getPathFactory().create(path); + } + @Test( expected = ItemNotFoundException.class ) public void shouldNotAllowAncestorDepthGreaterThanNodeDepth() throws Exception { root.getAncestor(1); Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrMultiValuePropertyTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrMultiValuePropertyTest.java (revision 776) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrMultiValuePropertyTest.java (working copy) @@ -28,15 +28,13 @@ import static org.junit.Assert.assertThat; import static org.mockito.Mockito.stub; import java.util.UUID; -import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.Value; import javax.jcr.ValueFormatException; -import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.jboss.dna.graph.ExecutionContext; -import org.jboss.dna.graph.Location; -import org.jboss.dna.graph.property.Path; +import org.jboss.dna.graph.property.Name; +import org.jboss.dna.jcr.SessionCache.PropertyInfo; import org.junit.Before; import org.junit.Test; import org.mockito.MockitoAnnotations; @@ -47,34 +45,50 @@ */ public class JcrMultiValuePropertyTest { - private Property prop; - private AbstractJcrNode node; + private PropertyId propertyId; + private JcrMultiValueProperty prop; private ExecutionContext executionContext; + private org.jboss.dna.graph.property.Property dnaProperty; @Mock + private SessionCache cache; + @Mock private JcrSession session; @Mock - private NodeDefinition nodeDefinition; + private PropertyInfo propertyInfo; @Mock - private PropertyDefinition definition; - private org.jboss.dna.graph.property.Property dnaProperty; + private JcrPropertyDefinition definition; + @Mock + private JcrNodeTypeManager nodeTypes; @Before - public void before() { + public void before() throws Exception { MockitoAnnotations.initMocks(this); executionContext = new ExecutionContext(); + stub(cache.session()).toReturn(session); + stub(cache.context()).toReturn(executionContext); + stub(session.nodeTypeManager()).toReturn(nodeTypes); - UUID rootUuid = UUID.randomUUID(); - Path rootPath = executionContext.getValueFactories().getPathFactory().createRootPath(); - Location rootLocation = Location.create(rootPath, rootUuid); - node = new JcrRootNode(session, rootLocation, nodeDefinition); - stub(session.getExecutionContext()).toReturn(executionContext); - dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, true); stub(definition.getRequiredType()).toReturn(PropertyType.BOOLEAN); stub(definition.isMultiple()).toReturn(true); - prop = new JcrMultiValueProperty(node, definition, definition.getRequiredType(), dnaProperty); + PropertyDefinitionId definitionId = new PropertyDefinitionId(name("nodeTypeName"), name("propDefnName")); + stub(nodeTypes.getPropertyDefinition(definitionId, true)).toReturn(definition); + + UUID uuid = UUID.randomUUID(); + propertyId = new PropertyId(uuid, JcrLexicon.MIMETYPE); + prop = new JcrMultiValueProperty(cache, propertyId); + + stub(cache.findPropertyInfo(propertyId)).toReturn(propertyInfo); + stub(propertyInfo.getDefinitionId()).toReturn(definitionId); + stub(propertyInfo.getPropertyType()).toReturn(PropertyType.BOOLEAN); + stub(propertyInfo.isMultiValued()).toReturn(true); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); } + protected Name name( String name ) { + return executionContext.getValueFactories().getNameFactory().create(name); + } + @Test public void shouldProvideAppropriateType() throws Exception { assertThat(prop.getType(), is(definition.getRequiredType())); @@ -82,7 +96,7 @@ @Test public void shouldProvidePropertyDefinition() throws Exception { - assertThat(prop.getDefinition(), notNullValue()); + assertThat(prop.getDefinition(), is((PropertyDefinition)definition)); } @Test @@ -149,9 +163,10 @@ Object value = "value"; dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, value); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); stub(definition.getRequiredType()).toReturn(PropertyType.STRING); stub(definition.isMultiple()).toReturn(true); - prop = new JcrMultiValueProperty(node, definition, definition.getRequiredType(), dnaProperty); + prop = new JcrMultiValueProperty(cache, propertyId); lengths = prop.getLengths(); assertThat(lengths, notNullValue()); assertThat(lengths.length, is(1)); @@ -160,9 +175,10 @@ value = new Object(); long expectedLength = executionContext.getValueFactories().getBinaryFactory().create(value).getSize(); dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, value); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); stub(definition.getRequiredType()).toReturn(PropertyType.STRING); stub(definition.isMultiple()).toReturn(true); - prop = new JcrMultiValueProperty(node, definition, definition.getRequiredType(), dnaProperty); + prop = new JcrMultiValueProperty(cache, propertyId); lengths = prop.getLengths(); assertThat(lengths, notNullValue()); assertThat(lengths.length, is(1)); @@ -170,9 +186,10 @@ String[] values = new String[] {"value1", "value2", "value 3 is longer"}; dnaProperty = executionContext.getPropertyFactory().create(JcrLexicon.MIMETYPE, (Object[])values); + stub(propertyInfo.getProperty()).toReturn(dnaProperty); stub(definition.getRequiredType()).toReturn(PropertyType.STRING); stub(definition.isMultiple()).toReturn(true); - prop = new JcrMultiValueProperty(node, definition, definition.getRequiredType(), dnaProperty); + prop = new JcrMultiValueProperty(cache, propertyId); lengths = prop.getLengths(); assertThat(lengths, notNullValue()); assertThat(lengths.length, is(values.length)); Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrPropertyIteratorTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrPropertyIteratorTest.java (revision 776) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrPropertyIteratorTest.java (working copy) @@ -32,31 +32,30 @@ import javax.jcr.PropertyIterator; import org.jboss.dna.graph.ExecutionContext; import org.jboss.dna.graph.property.Name; -import org.jboss.dna.jcr.AbstractJcrNodeTest.MockAbstractJcrNode; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.mockito.MockitoAnnotations.Mock; /** * @author jverhaeg */ public class JcrPropertyIteratorTest { - private AbstractJcrNode node; - @Mock - private JcrSession session; private Map properties; private ExecutionContext context; + private PropertyIterator iter; @Before public void before() throws Exception { MockitoAnnotations.initMocks(this); context = new ExecutionContext(); properties = new HashMap(); - node = new MockAbstractJcrNode(session, "node", null); - node.setProperties(properties); + properties.put(name("prop1"), Mockito.mock(Property.class)); + properties.put(name("prop2"), Mockito.mock(Property.class)); + properties.put(name("prop3"), Mockito.mock(Property.class)); + properties.put(name("prop4"), Mockito.mock(Property.class)); + iter = new JcrPropertyIterator(properties.values()); } protected Name name( String name ) { @@ -65,11 +64,6 @@ @Test public void shouldProvidePropertyIterator() throws Exception { - properties.put(name("prop1"), Mockito.mock(Property.class)); - properties.put(name("prop2"), Mockito.mock(Property.class)); - properties.put(name("prop3"), Mockito.mock(Property.class)); - properties.put(name("prop4"), Mockito.mock(Property.class)); - PropertyIterator iter = node.getProperties(); assertThat(iter, notNullValue()); assertThat(iter.getSize(), is(4L)); assertThat(iter.getPosition(), is(0L)); @@ -87,11 +81,11 @@ @Test( expected = UnsupportedOperationException.class ) public void shouldNotAllowPropertyIteratorRemove() throws Exception { - node.getProperties().remove(); + new JcrPropertyIterator(properties.values()).remove(); } @Test( expected = IllegalArgumentException.class ) public void shouldNotAllowPropertyIteratorNegativeSkip() throws Exception { - node.getProperties().skip(-1); + new JcrPropertyIterator(properties.values()).skip(-1); } } Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrNodeIteratorTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrNodeIteratorTest.java (revision 776) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrNodeIteratorTest.java (working copy) @@ -1,101 +0,0 @@ -/* - * JBoss DNA (http://www.jboss.org/dna) - * See the COPYRIGHT.txt file distributed with this work for information - * regarding copyright ownership. Some portions may be licensed - * to Red Hat, Inc. under one or more contributor license agreements. - * See the AUTHORS.txt file in the distribution for a full listing of - * individual contributors. - * - * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA - * is licensed to you under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * JBoss DNA is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - */ -package org.jboss.dna.jcr; - -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsNull.notNullValue; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.stub; -import java.util.ArrayList; -import java.util.List; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import org.jboss.dna.graph.ExecutionContext; -import org.jboss.dna.graph.Location; -import org.jboss.dna.jcr.AbstractJcrNodeTest.MockAbstractJcrNode; -import org.junit.Before; -import org.junit.Test; -import org.mockito.MockitoAnnotations; -import org.mockito.MockitoAnnotations.Mock; - -/** - * @author jverhaeg - */ -public class JcrNodeIteratorTest { - - private AbstractJcrNode node; - @Mock - private JcrSession session; - private List children; - - @Before - public void before() throws Exception { - MockitoAnnotations.initMocks(this); - ExecutionContext context = new ExecutionContext(); - stub(session.getExecutionContext()).toReturn(context); - children = new ArrayList(); - node = new MockAbstractJcrNode(session, "node", null); - } - - @Test - public void shouldProvideNodeIterator() throws Exception { - Node child1 = AbstractJcrNodeTest.createChild(session, "child1", 1, children, node); - Node child2_1 = AbstractJcrNodeTest.createChild(session, "child2", 1, children, node); - Node child2_2 = AbstractJcrNodeTest.createChild(session, "child2", 2, children, node); - AbstractJcrNodeTest.createChild(session, "child3", 1, children, node); - AbstractJcrNodeTest.createChild(session, "child4", 1, children, node); - Node child5 = AbstractJcrNodeTest.createChild(session, "child5", 1, children, node); - node.setChildren(children); - NodeIterator iter = node.getNodes(); - assertThat(iter, notNullValue()); - assertThat(iter.getSize(), is(6L)); - assertThat(iter.getPosition(), is(0L)); - assertThat(iter.hasNext(), is(true)); - assertThat((Node)iter.next(), is(child1)); - assertThat(iter.getPosition(), is(1L)); - assertThat(iter.hasNext(), is(true)); - assertThat(iter.nextNode(), is(child2_1)); - assertThat(iter.getPosition(), is(2L)); - assertThat(iter.hasNext(), is(true)); - assertThat(iter.nextNode(), is(child2_2)); - assertThat(iter.getPosition(), is(3L)); - assertThat(iter.hasNext(), is(true)); - iter.skip(2); - assertThat(iter.getPosition(), is(5L)); - assertThat(iter.hasNext(), is(true)); - assertThat(iter.nextNode(), is(child5)); - assertThat(iter.getPosition(), is(6L)); - assertThat(iter.hasNext(), is(false)); - } - - @Test( expected = UnsupportedOperationException.class ) - public void shouldNotAllowNodeIteratorRemove() throws Exception { - node.getNodes().remove(); - } - - @Test( expected = IllegalArgumentException.class ) - public void shouldNotAllowNodeIteratorNegativeSkip() throws Exception { - node.getNodes().skip(-1); - } -} Index: dna-jcr/src/test/resources/vehicles.xml =================================================================== --- dna-jcr/src/test/resources/vehicles.xml (revision 0) +++ dna-jcr/src/test/resources/vehicles.xml (revision 0) @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file Index: dna-jcr/src/test/resources/cars.xml =================================================================== --- dna-jcr/src/test/resources/cars.xml (revision 776) +++ dna-jcr/src/test/resources/cars.xml (working copy) @@ -45,4 +45,4 @@ -\ No newline at end of file + \ No newline at end of file Index: dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyId.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyId.java (revision 0) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyId.java (revision 0) @@ -0,0 +1,111 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * Unless otherwise indicated, all code in JBoss DNA is licensed + * to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.jcr; + +import java.util.UUID; +import net.jcip.annotations.Immutable; +import org.jboss.dna.common.util.HashCode; +import org.jboss.dna.graph.property.Name; + +/** + * An immutable identifier for a property, often used to reference information a property held within the {@link SessionCache}. + */ +@Immutable +public final class PropertyId { + + private final UUID nodeId; + private final Name propertyName; + private final int hc; + + /** + * Create a new property identifier. + * + * @param nodeId the UUID of the node that owns the property being reference; may not be null + * @param propertyName the name of the property being referenced; may not be null + */ + public PropertyId( UUID nodeId, + Name propertyName ) { + assert nodeId != null; + assert propertyName != null; + this.nodeId = nodeId; + this.propertyName = propertyName; + this.hc = HashCode.compute(this.nodeId, this.propertyName); + } + + /** + * Get the UUID of the node that owns the property. + * + * @return the node's UUID; never null + */ + public UUID getNodeId() { + return nodeId; + } + + /** + * Get the name of the property. + * + * @return the property name; never null + */ + public Name getPropertyName() { + return propertyName; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return hc; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( Object obj ) { + if (obj == this) return true; + if (obj instanceof PropertyId) { + PropertyId that = (PropertyId)obj; + if (this.hc != that.hc) return false; + if (!this.nodeId.equals(that.nodeId)) return false; + return this.propertyName.equals(that.propertyName); + } + return false; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return this.nodeId.toString() + '@' + this.propertyName.toString(); + } + +} Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrChildNodeIterator.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrChildNodeIterator.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrChildNodeIterator.java (working copy) @@ -24,34 +24,31 @@ package org.jboss.dna.jcr; import java.util.Iterator; -import java.util.List; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; import net.jcip.annotations.Immutable; import org.jboss.dna.common.util.CheckArg; -import org.jboss.dna.graph.Location; +import org.jboss.dna.jcr.SessionCache.ChildNode; /** - * @author jverhaeg */ @Immutable final class JcrChildNodeIterator implements NodeIterator { - private final AbstractJcrNode parent; - private final Iterator iterator; - private final JcrSession session; + private final SessionCache cache; + private final Iterator iterator; private int ndx; private int size; - JcrChildNodeIterator( AbstractJcrNode parent, - List children ) { - assert parent != null; + JcrChildNodeIterator( SessionCache cache, + Iterable children, + int size ) { + assert cache != null; assert children != null; - this.parent = parent; - this.session = parent.session(); + this.cache = cache; iterator = children.iterator(); - size = children.size(); + this.size = size; } /** @@ -96,10 +93,10 @@ * @see javax.jcr.NodeIterator#nextNode() */ public Node nextNode() { - Location location = iterator.next(); + ChildNode child = iterator.next(); ndx++; try { - return session.getChild(parent, location); + return cache.findJcrNode(child.getUuid()); } catch (RepositoryException error) { throw new RuntimeException(error); } Index: dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java (working copy) @@ -28,14 +28,16 @@ import javax.jcr.Item; import javax.jcr.ItemVisitor; import javax.jcr.Node; +import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.RepositoryException; -import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.nodetype.PropertyDefinition; import net.jcip.annotations.NotThreadSafe; import org.jboss.dna.common.util.CheckArg; -import org.jboss.dna.graph.ExecutionContext; +import org.jboss.dna.graph.property.Name; +import org.jboss.dna.graph.property.Path; +import org.jboss.dna.jcr.SessionCache.PropertyInfo; /** * @author jverhaeg @@ -43,22 +45,13 @@ @NotThreadSafe abstract class AbstractJcrProperty extends AbstractJcrItem implements Property { - private final AbstractJcrNode node; - private final org.jboss.dna.graph.property.Property dnaProperty; - private final PropertyDefinition jcrPropertyDefinition; - private final int propertyType; + private final PropertyId propertyId; - AbstractJcrProperty( AbstractJcrNode node, - PropertyDefinition definition, - int propertyType, - org.jboss.dna.graph.property.Property dnaProperty ) { - assert node != null; - assert dnaProperty != null; - assert definition != null; - this.node = node; - this.dnaProperty = dnaProperty; - this.jcrPropertyDefinition = definition; - this.propertyType = propertyType; + AbstractJcrProperty( SessionCache cache, + PropertyId propertyId ) { + super(cache); + assert propertyId != null; + this.propertyId = propertyId; } /** @@ -72,25 +65,34 @@ visitor.visit(this); } - JcrValue createValue( Object value ) { - return new JcrValue(getExecutionContext().getValueFactories(), getType(), value); + final PropertyInfo propertyInfo() throws PathNotFoundException, RepositoryException { + return cache.findPropertyInfo(propertyId); } - final ExecutionContext getExecutionContext() { - return node.session().getExecutionContext(); + final Name name() throws RepositoryException { + return propertyInfo().getPropertyName(); } - final org.jboss.dna.graph.property.Property getDnaProperty() { - return dnaProperty; + final org.jboss.dna.graph.property.Property property() throws RepositoryException { + return propertyInfo().getProperty(); } + JcrValue createValue( Object value ) throws RepositoryException { + return new JcrValue(context().getValueFactories(), propertyInfo().getPropertyType(), value); + } + + @Override + Path path() throws RepositoryException { + return cache.getPathFor(propertyInfo()); + } + /** * {@inheritDoc} * * @see javax.jcr.Property#getType() */ - public final int getType() { - return propertyType; + public int getType() throws RepositoryException { + return propertyInfo().getPropertyType(); } /** @@ -98,8 +100,11 @@ * * @see javax.jcr.Property#getDefinition() */ - public final PropertyDefinition getDefinition() { - return jcrPropertyDefinition; + public final PropertyDefinition getDefinition() throws RepositoryException { + PropertyInfo info = propertyInfo(); + PropertyDefinitionId definitionId = info.getDefinitionId(); + boolean multiValued = info.isMultiValued(); + return cache.session().nodeTypeManager().getPropertyDefinition(definitionId, multiValued); } /** @@ -111,8 +116,8 @@ * * @see javax.jcr.Item#getName() */ - public final String getName() { - return dnaProperty.getName().getString(node.namespaces()); + public final String getName() throws RepositoryException { + return propertyInfo().getPropertyName().getString(namespaces()); } /** @@ -120,8 +125,8 @@ * * @see javax.jcr.Item#getParent() */ - public final Node getParent() { - return node; + public final Node getParent() throws RepositoryException { + return cache.findJcrNode(propertyId.getNodeId()); } /** @@ -130,21 +135,12 @@ * @see javax.jcr.Item#getPath() */ public final String getPath() throws RepositoryException { - return getPath(node.getPath(), getName()); + return cache.getPathFor(propertyInfo()).getString(namespaces()); } /** * {@inheritDoc} * - * @see javax.jcr.Item#getSession() - */ - public final Session getSession() { - return node.getSession(); - } - - /** - * {@inheritDoc} - * * @return false * @see javax.jcr.Item#isNode() */ Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSystemViewExporter.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSystemViewExporter.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSystemViewExporter.java (working copy) @@ -114,7 +114,7 @@ AbstractJcrProperty prop = (AbstractJcrProperty)property; - Name propertyName = prop.getDnaProperty().getName(); + Name propertyName = prop.name(); if (SPECIAL_PROPERTY_NAMES.contains(propertyName)) { return; } Index: dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java (revision 0) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java (revision 0) @@ -0,0 +1,1583 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * Unless otherwise indicated, all code in JBoss DNA is licensed + * to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.jcr; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import javax.jcr.Item; +import javax.jcr.ItemNotFoundException; +import javax.jcr.Node; +import javax.jcr.PathNotFoundException; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeDefinition; +import javax.jcr.nodetype.NodeType; +import javax.jcr.nodetype.NodeTypeManager; +import javax.jcr.nodetype.PropertyDefinition; +import net.jcip.annotations.Immutable; +import net.jcip.annotations.NotThreadSafe; +import net.jcip.annotations.ThreadSafe; +import org.jboss.dna.graph.ExecutionContext; +import org.jboss.dna.graph.Graph; +import org.jboss.dna.graph.Location; +import org.jboss.dna.graph.Results; +import org.jboss.dna.graph.connector.RepositorySourceException; +import org.jboss.dna.graph.property.Name; +import org.jboss.dna.graph.property.NameFactory; +import org.jboss.dna.graph.property.NamespaceRegistry; +import org.jboss.dna.graph.property.Path; +import org.jboss.dna.graph.property.PathFactory; +import org.jboss.dna.graph.property.Property; +import org.jboss.dna.graph.property.PropertyFactory; +import org.jboss.dna.graph.property.ValueFactories; +import org.jboss.dna.graph.property.ValueFactory; +import org.jboss.dna.graph.property.ValueFormatException; +import com.google.common.base.ReferenceType; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.ReferenceMap; + +/** + * The class that manages the session's information that has been locally-cached after reading from the underlying {@link Graph + * repository} or modified by the session but not yet saved or commited to the repository. + *

+ * The cached information is broken into several different categories that are each described below. + *

+ *

JCR API objects

+ *

+ * Clients using the DNA JCR implementation obtain a {@link JcrSession JCR Session} (which generally owns this cache instance) as + * well as the JCR {@link JcrNode Node} and {@link AbstractJcrProperty Property} instances. This cache ensures that the same JCR + * Node or Property objects are always returned for the same item in the repository, ensuring that the "==" operator always holds + * true for the same item. However, as soon as all (client) references to these objects are garbage collected, this class is free + * to also release those objects and, when needed, recreate new implementation objects. + *

+ *

+ * This approach helps reduce memory utilization since any unused items are available for garbage collection, but it also + * guarantees that once a client maintains a reference to an item, the same Java object will always be used for any references to + * that item. + *

+ *

Cached nodes

+ *

+ * The session cache is also responsible for maintaining a local cache of node information retrieved from the underlying + * repository, reducing the need to request information any more than necessary. This information includes that obtained directly + * from the repository store, including node properties, children, and references to the parent. It also includes computed + * information, such as the NodeDefinition for a node, the name of the primary type and mixin types, and the original + * {@link Location} of the node in the repository. + *

+ *

Transient changes

+ *

+ * Any time content is changed in the session, those changes are held within the session until they are saved either by + * {@link Session#save() saving the session} or {@link Item#save() saving an individual item} (which includes any content below + * that item). This cache maintains all these transient changes, and when requested will send the change requests down the + * repository. At any point, these transient changes may be rolled back (or "released"), again either for the + * {@link Session#refresh(boolean) whole session} or for {@link Item#refresh(boolean) individual items}. + *

+ */ +@ThreadSafe +public class SessionCache { + + /** + * Hidden flag that controls whether properties that appear on DNA nodes but not allowed by the node type or mixins should be + * included anyway. This is currently {@value} . + */ + private static final boolean INCLUDE_PROPERTIES_NOT_ALLOWED_BY_NODE_TYPE_OR_MIXINS = true; + + private final JcrSession session; + private final String workspaceName; + protected final ExecutionContext context; + protected final PathFactory pathFactory; + private final NameFactory nameFactory; + private final ValueFactory stringFactory; + private final NamespaceRegistry namespaces; + private final PropertyFactory propertyFactory; + private final Graph store; + private final Name defaultPrimaryTypeName; + private final Property defaultPrimaryTypeProperty; + private final Path rootPath; + + private UUID root; + private final ReferenceMap jcrNodes; + private final ReferenceMap jcrProperties; + + private final HashMap cachedNodes; + private final HashMap changedNodes; + + public SessionCache( JcrSession session, + String workspaceName, + ExecutionContext context, + JcrNodeTypeManager nodeTypes, + Graph store ) { + assert session != null; + assert workspaceName != null; + assert context != null; + assert store != null; + this.session = session; + this.workspaceName = workspaceName; + this.store = store; + this.context = context; + this.pathFactory = context.getValueFactories().getPathFactory(); + this.nameFactory = context.getValueFactories().getNameFactory(); + this.stringFactory = context.getValueFactories().getStringFactory(); + this.namespaces = context.getNamespaceRegistry(); + this.propertyFactory = context.getPropertyFactory(); + this.defaultPrimaryTypeName = JcrNtLexicon.UNSTRUCTURED; + this.defaultPrimaryTypeProperty = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, this.defaultPrimaryTypeName); + this.rootPath = pathFactory.createRootPath(); + + this.jcrNodes = new ReferenceMap(ReferenceType.STRONG, ReferenceType.SOFT); + this.jcrProperties = new ReferenceMap(ReferenceType.STRONG, ReferenceType.SOFT); + + this.cachedNodes = new HashMap(); + this.changedNodes = new HashMap(); + } + + JcrSession session() { + return session; + } + + ExecutionContext context() { + return context; + } + + String workspaceName() { + return workspaceName; + } + + JcrNodeTypeManager nodeTypes() { + return session.nodeTypeManager(); + } + + public JcrRootNode findJcrRootNode() throws RepositoryException { + return (JcrRootNode)findJcrNode(findNodeInfoForRoot().getUuid()); + } + + /** + * Find the JCR {@link JcrNode Node implementation} for the given UUID. + * + * @param uuid the node's UUID + * @return the existing node implementation + * @throws ItemNotFoundException if a node with the supplied UUID could not be found + * @throws RepositoryException if an error resulting in finding this node in the repository + */ + public AbstractJcrNode findJcrNode( UUID uuid ) throws ItemNotFoundException, RepositoryException { + AbstractJcrNode node = jcrNodes.get(uuid); + if (node != null) return node; + + // An existing JCR Node object was not found, so we'll have to create it by finding the underlying + // NodeInfo for the node (from the changed state or the cache) ... + NodeInfo info = findNodeInfo(uuid); + assert info != null; + + // Create the appropriate JCR Node object ... + return createAndCacheJcrNodeFor(info); + } + + /** + * Find the JCR {@link AbstractJcrNode Node implementation} for the node given by the UUID of a reference node and a relative + * path from the reference node. The relative path should already have been {@link Path#getNormalizedPath() normalized}. + * + * @param uuidOfReferenceNode the UUID of the reference node; may be null if the root node is to be used as the reference + * @param relativePath the relative (but normalized) path from the reference node, but which may be an absolute (and + * normalized) path only when the reference node is the root node; may not be null + * @return the information for the referenced node; never null + * @throws ItemNotFoundException if the reference node with the supplied UUID does not exist + * @throws PathNotFoundException if the node given by the relative path does not exist + * @throws RepositoryException if any other error occurs while reading information from the repository + * @see #findNodeInfoForRoot() + */ + public AbstractJcrNode findJcrNode( UUID uuidOfReferenceNode, + Path relativePath ) throws PathNotFoundException, RepositoryException { + // An existing JCR Node object was not found, so we'll have to create it by finding the underlying + // NodeInfo for the node (from the changed state or the cache) ... + NodeInfo info = findNodeInfo(uuidOfReferenceNode, relativePath); + assert info != null; + + // Look for an existing node ... + AbstractJcrNode node = jcrNodes.get(info.getUuid()); + if (node != null) return node; + + // Create the appropriate JCR Node object ... + return createAndCacheJcrNodeFor(info); + } + + public AbstractJcrProperty findJcrProperty( PropertyId propertyId ) throws PathNotFoundException, RepositoryException { + AbstractJcrProperty property = jcrProperties.get(propertyId); + if (property != null) return property; + + // An existing JCR Property object was not found, so we'll have to create it by finding the underlying + // NodeInfo for the property's parent (from the changed state or the cache) ... + PropertyInfo info = findPropertyInfo(propertyId); // throws PathNotFoundException if node not there + if (info == null) return null; // no such property on this node + + // Now create the appropriate JCR Property object ... + return createAndCacheJcrPropertyFor(info); + } + + public Collection findJcrPropertiesFor( UUID nodeUuid ) + throws ItemNotFoundException, RepositoryException { + NodeInfo info = findNodeInfo(nodeUuid); + Map properties = info.getProperties(); + Collection result = new ArrayList(properties.size()); + for (PropertyInfo propertyInfo : properties.values()) { + result.add(findJcrProperty(propertyInfo.getPropertyId())); + } + return result; + } + + /** + * Find the JCR {@link AbstractJcrItem Item implementation} for the node or property given by the UUID of a reference node and + * a relative path from the reference node to the desired item. The relative path should already have been + * {@link Path#getNormalizedPath() normalized}. + * + * @param uuidOfReferenceNode the UUID of the reference node; may be null if the root node is to be used as the reference + * @param relativePath the relative (but normalized) path from the reference node to the desired item, but which may be an + * absolute (and normalized) path only when the reference node is the root node; may not be null + * @return the information for the referenced item; never null + * @throws ItemNotFoundException if the reference node with the supplied UUID does not exist, or if an item given by the + * supplied relative path does not exist + * @throws RepositoryException if any other error occurs while reading information from the repository + * @see #findNodeInfoForRoot() + */ + public AbstractJcrItem findJcrItem( UUID uuidOfReferenceNode, + Path relativePath ) throws ItemNotFoundException, RepositoryException { + // A pathological case is an empty relative path ... + if (relativePath.size() == 0) { + return findJcrNode(uuidOfReferenceNode); + } + if (relativePath.size() == 1) { + Path.Segment segment = relativePath.getLastSegment(); + if (segment.isSelfReference()) return findJcrNode(uuidOfReferenceNode); + if (segment.isParentReference()) { + NodeInfo referencedNode = findNodeInfo(uuidOfReferenceNode); + return findJcrNode(referencedNode.getParent()); + } + } + + // Peek into the last segment of the path to see whether it uses a SNS index (and it's > 1) ... + Path.Segment lastSegment = relativePath.getLastSegment(); + if (lastSegment.getIndex() > 1) { + // Only nodes can have SNS index (but an index of 1 is the default)... + return findJcrNode(uuidOfReferenceNode); + } + + NodeInfo parent = null; + if (relativePath.size() == 1) { + // The referenced node must be the parent ... + parent = findNodeInfo(uuidOfReferenceNode); + } else { + // We know that the parent of the referenced item should be a node (if the path is right) ... + parent = findNodeInfo(uuidOfReferenceNode, relativePath.getParent()); + } + + // JSR-170 doesn't allow children and proeprties to have the same name, but this is relaxed in JSR-283. + // But JSR-283 Section 3.3.4 states "The method Session.getItem will return the item at the specified path + // if there is only one such item, if there is both a node and a property at the specified path, getItem + // will return the node." Therefore, look for a child first ... + ChildNode child = parent.getChildren().getChild(lastSegment); + if (child != null) { + return findJcrNode(child.getUuid()); + } + + // Otherwise it should be a property ... + PropertyInfo propertyInfo = parent.getProperty(lastSegment.getName()); + if (propertyInfo != null) { + return findJcrProperty(propertyInfo.getPropertyId()); + } + + // It was not found, so prepare a good exception message ... + String msg = null; + if (findNodeInfoForRoot().getUuid().equals(uuidOfReferenceNode)) { + // The reference node was the root, so use this fact to convert the path to an absolute path in the message + Path absolutePath = rootPath.resolve(relativePath); + msg = JcrI18n.itemNotFoundAtPath.text(absolutePath, workspaceName); + } else { + // Find the path of the reference node ... + Path referenceNodePath = getPathFor(uuidOfReferenceNode); + msg = JcrI18n.itemNotFoundAtPathRelativeToReferenceNode.text(relativePath, referenceNodePath, workspaceName); + } + throw new ItemNotFoundException(msg); + } + + /** + * Utility method that creates and caches the appropriate kind of AbstractJcrNode implementation for node given by the + * supplied information. + * + * @param info the information for the node; may not be null + * @return the new instance of the {@link Node}; never null + */ + private AbstractJcrNode createAndCacheJcrNodeFor( NodeInfo info ) { + UUID uuid = info.getUuid(); + Location location = info.getOriginalLocation(); + NodeDefinitionId nodeDefinitionId = info.getDefinitionId(); + JcrNodeDefinition definition = nodeTypes().getNodeDefinition(nodeDefinitionId); + assert definition != null; + + if (root == null) { + // Need to determine if this is the root node ... + if (location.getPath().isRoot()) { + // It is a root node ... + JcrRootNode node = new JcrRootNode(this, uuid); + jcrNodes.put(uuid, node); + root = uuid; + return node; + } + } + + // It is not a root node ... + JcrNode node = new JcrNode(this, uuid); + jcrNodes.put(uuid, node); + return node; + } + + /** + * Utility method that creates and caches the appropriate kind of AbstractJcrProperty implementation for property given by the + * supplied information. + * + * @param info the information for the property; may not be null + * @return the new instance of the {@link Property}; never null + */ + private AbstractJcrProperty createAndCacheJcrPropertyFor( PropertyInfo info ) { + boolean multiValued = info.getProperty().isMultiple(); + JcrPropertyDefinition definition = nodeTypes().getPropertyDefinition(info.getDefinitionId(), multiValued); + assert definition != null; + if (multiValued) { + return new JcrMultiValueProperty(this, info.getPropertyId()); + } + return new JcrSingleValueProperty(this, info.getPropertyId()); + } + + // public AbstractJcrProperty findJcrProperty( Path propertyPath ) throws PathNotFoundException, RepositoryException { + // if (propertyPath.isRoot()) return null; + // Path nodePath = propertyPath.getParent(); + // NodeInfo nodeInfo = findNodeInfo(nodePath); + // Name propertyName = propertyPath.getLastSegment().getName(); + // PropertyInfo info = nodeInfo.getProperty(propertyName); + // + // // Look for an existing JCR Property object ... + // PropertyId propertyId = info.getPropertyId(); + // AbstractJcrProperty property = jcrProperties.get(propertyId); + // if (property != null) return property; + // + // // Now create the appropriate JCR Property object ... + // return createAndCacheJcrPropertyFor(info); + // } + + /** + * Find the information for the node given by the UUID of the node. This is often the fastest way to find information, + * especially after the information has been cached. Note, however, that this method first checks the cache, and if the + * information is not in the cache, the information is read from the repository. + * + * @param uuid the UUID for the node; may not be null + * @return the information for the node with the supplied UUID, or null if the information is not in the cache + * @throws ItemNotFoundException if there is no node with the supplied UUID + * @throws RepositoryException if any other error occurs while reading information from the repository + * @see #findNodeInfoInCache(UUID) + * @see #findNodeInfo(UUID, Path) + * @see #findNodeInfoForRoot() + */ + NodeInfo findNodeInfo( UUID uuid ) throws ItemNotFoundException, RepositoryException { + // See if we already have something in the changed nodes ... + NodeInfo info = changedNodes.get(uuid); + if (info == null) { + // Or in the cache ... + info = cachedNodes.get(uuid); + if (info == null) { + info = loadFromGraph(uuid, null); + } + } + return info; + } + + /** + * Find the information for the node given by the UUID of the node. This is often the fastest way to find information, + * especially after the information has been cached. Note, however, that this method only checks the cache. + * + * @param uuid the UUID for the node; may not be null + * @return the information for the node with the supplied UUID, or null if the information is not in the cache + * @see #findNodeInfo(UUID) + * @see #findNodeInfo(UUID, Path) + * @see #findNodeInfoForRoot() + */ + NodeInfo findNodeInfoInCache( UUID uuid ) { + // See if we already have something in the changed nodes ... + NodeInfo info = changedNodes.get(uuid); + if (info == null) { + // Or in the cache ... + info = cachedNodes.get(uuid); + } + return info; + } + + /** + * Find the information for the root node. Generally, this returns information that's in the cache, except for the first time + * the root node is needed. + * + * @return the node information + * @throws RepositoryException if there is an error while obtaining the information from the repository + */ + NodeInfo findNodeInfoForRoot() throws RepositoryException { + if (root == null) { + // We haven't found the root yet ... + NodeInfo info = loadFromGraph(this.rootPath, null); + root = info.getUuid(); + this.jcrNodes.put(root, new JcrRootNode(this, root)); + return info; + } + return findNodeInfo(root); + } + + /** + * Find the information for the node given by the UUID of a reference node and a relative path from the reference node. + * + * @param node the reference node; may be null if the root node is to be used as the reference + * @param relativePath the relative path from the reference node, but which may be an absolute path only when the reference + * node is the root node; may not be null + * @return the information for the referenced node; never null + * @throws ItemNotFoundException if the reference node with the supplied UUID does not exist + * @throws PathNotFoundException if the node given by the relative path does not exist + * @throws RepositoryException if any other error occurs while reading information from the repository + * @see #findNodeInfoForRoot() + */ + NodeInfo findNodeInfo( UUID node, + Path relativePath ) throws ItemNotFoundException, PathNotFoundException, RepositoryException { + // The relative path must be normalized ... + assert relativePath.isNormalized(); + + // Find the node from which we're starting ... + NodeInfo fromInfo = null; + if (node == null) { + // This is only valid when the path is relative to the root (or it's an absolute path) + fromInfo = findNodeInfoForRoot(); + node = fromInfo.getUuid(); + } else { + fromInfo = findNodeInfo(node); + assert relativePath.isAbsolute() ? node == root : true; + } + if (relativePath.isAbsolute()) { + relativePath = relativePath.relativeTo(this.rootPath); + } + + // If the relative path is of zero-length ... + if (relativePath.size() == 0) { + return fromInfo; + } + // Or it is of length 1 but it is a self reference ... + if (relativePath.size() == 1 && relativePath.getLastSegment().isSelfReference()) { + return fromInfo; + } + + // TODO: This could be more efficient than always walking the path. For example, we could + // maintain a cache of paths. Right now, we are walking as much of the path as we can, + // but as soon as we reach the bottom-most cached/changed node, we need to read the rest + // from the graph. We are figuring out all of the remaining nodes and read them from + // the graph in one batch operation, so that part is pretty good. + + // Now, walk the path to find the nodes, being sure to look for changed information ... + NodeInfo info = fromInfo; + Iterator pathsIter = relativePath.iterator(); + while (pathsIter.hasNext()) { + Path.Segment child = pathsIter.next(); + if (child.isParentReference()) { + // Walk up ... + UUID parentUuid = info.getParent(); + if (parentUuid == null) { + assert info.getUuid() == findNodeInfoForRoot().getUuid(); + String msg = JcrI18n.errorWhileFindingNodeWithPath.text(relativePath, workspaceName); + throw new PathNotFoundException(msg); + } + info = findNodeInfo(parentUuid); + } else { + // Walk down ... + // Note that once we start walking down, a normalized path should never have any more parent + // or self references + ChildNode childNodeInfo = info.getChildren().getChild(child); + if (childNodeInfo == null) { + // The node (no longer?) exists, so compute the + Path fromPath = getPathFor(fromInfo); + String msg = JcrI18n.pathNotFoundRelativeTo.text(relativePath, fromPath, workspaceName); + throw new PathNotFoundException(msg); + } + // See if we already have something in the changed nodes ... + UUID uuid = childNodeInfo.getUuid(); + NodeInfo childInfo = changedNodes.get(uuid); + if (childInfo == null) { + // Or in the cache ... + childInfo = cachedNodes.get(uuid); + if (childInfo == null) { + // At this point, we've reached the bottom of the nodes that we have locally. + // Get the actual location of the last 'info', since all paths will be relative to it... + Location actualLocation = info.getOriginalLocation(); + Path actualPath = actualLocation.getPath(); + Path nextPath = pathFactory.create(actualPath, child); + if (pathsIter.hasNext()) { + // There are multiple remaining paths, so load them all in one batch operation, + // starting at the top-most path (the one we're currently at)... + List pathsInBatch = new LinkedList(); + Results batchResults = null; + try { + Graph.Batch batch = store.batch(); + batch.read(nextPath); + pathsInBatch.add(nextPath); + while (pathsIter.hasNext()) { + child = pathsIter.next(); + nextPath = pathFactory.create(nextPath, child); + batch.read(nextPath); + pathsInBatch.add(nextPath); + } + batchResults = batch.execute(); + } catch (org.jboss.dna.graph.property.PathNotFoundException e) { + Path fromPath = getPathFor(fromInfo); + throw new PathNotFoundException(JcrI18n.pathNotFoundRelativeTo.text(relativePath, + fromPath, + workspaceName)); + } catch (RepositorySourceException e) { + throw new RepositoryException(JcrI18n.errorWhileFindingNodeWithUuid.text(uuid, + workspaceName, + e.getLocalizedMessage())); + } + // Now process all of the nodes that we loaded, again starting at the top and going down ... + for (Path batchPath : pathsInBatch) { + org.jboss.dna.graph.Node dnaNode = batchResults.getNode(batchPath); + childInfo = createNodeInfoFrom(dnaNode, info); + this.cachedNodes.put(childInfo.getUuid(), childInfo); + info = childInfo; + } + } else { + // This is the last path, so do it a little more efficiently than above ... + childInfo = loadFromGraph(nextPath, info); + info = childInfo; + } + } else { + info = childInfo; + } + } else { + info = childInfo; + } + } + } + return info; + } + + /** + * Find the property information with the given identifier. If the property is not yet loaded into the cache, the node (and + * its properties) will be read from the repository. + * + * @param propertyId the identifier for the property; may not be null + * @return the property information, or null if the node does not contain the specified property + * @throws PathNotFoundException if the node containing this property does not exist + * @throws RepositoryException if there is an error while obtaining the information + */ + PropertyInfo findPropertyInfo( PropertyId propertyId ) throws PathNotFoundException, RepositoryException { + NodeInfo info = findNodeInfo(propertyId.getNodeId()); + return info.getProperty(propertyId.getPropertyName()); + } + + Path getPathFor( UUID uuid ) throws ItemNotFoundException, RepositoryException { + if (uuid == root) return rootPath; + return getPathFor(findNodeInfo(uuid)); + } + + Path getPathFor( NodeInfo info ) throws ItemNotFoundException, RepositoryException { + if (info != null && info.getUuid() == root) return rootPath; + LinkedList segments = new LinkedList(); + while (info != null) { + UUID parent = info.getParent(); + if (parent == null) break; + NodeInfo parentInfo = findNodeInfo(parent); + ChildNode child = parentInfo.getChildren().getChild(info.getUuid()); + if (child == null) break; + Path.Segment segment = pathFactory.createSegment(child.getName(), child.getSnsIndex()); + + segments.addFirst(segment); + info = parentInfo; + } + return pathFactory.createAbsolutePath(segments); + } + + Path getPathFor( PropertyInfo propertyInfo ) throws ItemNotFoundException, RepositoryException { + Path nodePath = getPathFor(propertyInfo.getNodeUuid()); + return pathFactory.create(nodePath, propertyInfo.getPropertyName()); + } + + Path getPathFor( PropertyId propertyId ) throws ItemNotFoundException, RepositoryException { + return getPathFor(findPropertyInfo(propertyId)); + } + + protected Name getNameOf( UUID nodeUuid ) throws ItemNotFoundException, RepositoryException { + findNodeInfoForRoot(); + if (nodeUuid == root) return nameFactory.create(""); + // Get the parent ... + NodeInfo info = findNodeInfo(nodeUuid); + NodeInfo parent = findNodeInfo(info.getParent()); + ChildNode child = parent.getChildren().getChild(info.getUuid()); + return child.getName(); + } + + protected int getSnsIndexOf( UUID nodeUuid ) throws ItemNotFoundException, RepositoryException { + findNodeInfoForRoot(); + if (nodeUuid == root) return 1; + // Get the parent ... + NodeInfo info = findNodeInfo(nodeUuid); + NodeInfo parent = findNodeInfo(info.getParent()); + ChildNode child = parent.getChildren().getChild(info.getUuid()); + return child.getSnsIndex(); + } + + /** + * Load from the underlying repository graph the information for the node with the supplied UUID. This method returns the + * information for the requested node (after placing it in the cache), but this method may (at its discretion) also load and + * cache information for other nodes. + *

+ * Note that this method does not check the cache before loading from the repository graph. + *

+ * + * @param uuid the UUID of the node; may not be null + * @param parentInfo the parent information; may be null if not known + * @return the information for the node + * @throws ItemNotFoundException if the node does not exist in the repository + * @throws RepositoryException if there was an error obtaining this information from the repository + */ + protected NodeInfo loadFromGraph( UUID uuid, + NodeInfo parentInfo ) throws ItemNotFoundException, RepositoryException { + // Load the node information from the store ... + try { + org.jboss.dna.graph.Node node = store.getNodeAt(uuid); + NodeInfo info = createNodeInfoFrom(node, parentInfo); + this.cachedNodes.put(info.getUuid(), info); + return info; + } catch (org.jboss.dna.graph.property.PathNotFoundException e) { + throw new ItemNotFoundException(JcrI18n.itemNotFoundWithUuid.text(uuid, workspaceName, e.getLocalizedMessage())); + } catch (RepositorySourceException e) { + throw new RepositoryException( + JcrI18n.errorWhileFindingNodeWithUuid.text(uuid, workspaceName, e.getLocalizedMessage())); + } + } + + /** + * Load from the underlying repository graph the information for the node with the supplied UUID. This method returns the + * information for the requested node (after placing it in the cache), but this method may (at its discretion) also load and + * cache information for other nodes. + *

+ * Note that this method does not check the cache before loading from the repository graph. + *

+ * + * @param path the path to the node; may not be null + * @param parentInfo the parent information; may be null if not known + * @return the information for the node + * @throws PathNotFoundException if the node does not exist in the repository + * @throws RepositoryException if there was an error obtaining this information from the repository + */ + protected NodeInfo loadFromGraph( Path path, + NodeInfo parentInfo ) throws PathNotFoundException, RepositoryException { + // Load the node information from the store ... + try { + org.jboss.dna.graph.Node node = store.getNodeAt(path); + NodeInfo info = createNodeInfoFrom(node, parentInfo); + this.cachedNodes.put(info.getUuid(), info); + return info; + } catch (org.jboss.dna.graph.property.PathNotFoundException e) { + throw new PathNotFoundException(JcrI18n.pathNotFound.text(path, workspaceName)); + } catch (RepositorySourceException e) { + throw new RepositoryException(JcrI18n.errorWhileFindingNodeWithPath.text(path, workspaceName)); + } + } + + /** + * Create the {@link NodeInfo} object given the DNA graph node and the parent node information (if it is available). + * + * @param graphNode the DNA graph node; may not be null + * @param parentInfo the information for the parent node, or null if the supplied graph node represents the root node, or if + * the parent information is not known + * @return the node information; never null + * @throws RepositoryException if there is an error determining the child {@link NodeDefinition} for the supplied node, + * preventing the node information from being constructed + */ + private NodeInfo createNodeInfoFrom( org.jboss.dna.graph.Node graphNode, + NodeInfo parentInfo ) throws RepositoryException { + // Now get the DNA node's UUID and find the DNA property containing the UUID ... + Location location = graphNode.getLocation(); + ValueFactories factories = context.getValueFactories(); + UUID uuid = location.getUuid(); + org.jboss.dna.graph.property.Property uuidProperty = null; + if (uuid != null) { + // Check for an identification property ... + uuidProperty = location.getIdProperty(JcrLexicon.UUID); + if (uuidProperty == null) uuidProperty = location.getIdProperty(DnaLexicon.UUID); + } + if (uuidProperty == null) { + uuidProperty = graphNode.getProperty(JcrLexicon.UUID); + if (uuidProperty != null) { + // Grab the first 'good' UUID value ... + for (Object uuidValue : uuidProperty) { + try { + uuid = factories.getUuidFactory().create(uuidValue); + break; + } catch (ValueFormatException e) { + // Ignore; just continue with the next property value + } + } + } + if (uuid == null) { + // Look for the DNA UUID property ... + org.jboss.dna.graph.property.Property dnaUuidProperty = graphNode.getProperty(DnaLexicon.UUID); + if (dnaUuidProperty != null) { + // Grab the first 'good' UUID value ... + for (Object uuidValue : dnaUuidProperty) { + try { + uuid = factories.getUuidFactory().create(uuidValue); + break; + } catch (ValueFormatException e) { + // Ignore; just continue with the next property value + } + } + } + } + } + if (uuid == null) uuid = UUID.randomUUID(); + if (uuidProperty == null) uuidProperty = propertyFactory.create(JcrLexicon.UUID, uuid); + + // Either the UUID is not known, or there was no node. Either way, we have to create the node ... + if (uuid == null) uuid = UUID.randomUUID(); + + // Look for the primary type of the node ... + Map graphProperties = graphNode.getPropertiesByName(); + final boolean isRoot = location.getPath().isRoot(); + Name primaryTypeName = null; + org.jboss.dna.graph.property.Property primaryTypeProperty = graphNode.getProperty(JcrLexicon.PRIMARY_TYPE); + if (primaryTypeProperty != null && !primaryTypeProperty.isEmpty()) { + try { + primaryTypeName = factories.getNameFactory().create(primaryTypeProperty.getFirstValue()); + } catch (ValueFormatException e) { + // use the default ... + } + } + if (primaryTypeName == null) { + // We have to have a primary type, so use the default ... + if (isRoot) { + primaryTypeName = DnaLexicon.ROOT; + primaryTypeProperty = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, primaryTypeName); + } else { + primaryTypeName = defaultPrimaryTypeName; + primaryTypeProperty = defaultPrimaryTypeProperty; + } + // We have to add this property to the graph node... + graphProperties = new HashMap(graphProperties); + graphProperties.put(primaryTypeProperty.getName(), primaryTypeProperty); + } + assert primaryTypeProperty != null; + assert primaryTypeProperty.isEmpty() == false; + + // Look for a node definition stored on the node ... + JcrNodeDefinition definition = null; + org.jboss.dna.graph.property.Property nodeDefnProperty = graphProperties.get(DnaLexicon.NODE_DEFINITON); + if (nodeDefnProperty != null && !nodeDefnProperty.isEmpty()) { + String nodeDefinitionString = stringFactory.create(nodeDefnProperty.getFirstValue()); + NodeDefinitionId id = NodeDefinitionId.fromString(nodeDefinitionString, nameFactory); + definition = nodeTypes().getNodeDefinition(id); + } + // Figure out the node definition for this node ... + if (definition == null) { + if (isRoot) { + definition = nodeTypes().getRootNodeDefinition(); + } else { + // We need the parent ... + Path path = location.getPath(); + if (parentInfo == null) { + Path parentPath = path.getParent(); + parentInfo = findNodeInfo(null, parentPath.getNormalizedPath()); + } + Name childName = path.getLastSegment().getName(); + definition = findNodeDefinitionForChild(parentInfo, childName, primaryTypeName); + if (definition == null) { + String msg = JcrI18n.nodeDefinitionCouldNotBeDeterminedForNode.text(path, workspaceName); + throw new RepositorySourceException(msg); + } + } + } + + // Create the node information ... + NodeInfo info = new NodeInfo(location, primaryTypeName, definition.getId()); + if (parentInfo != null) { + info.setParent(parentInfo.getUuid()); + } + + // -------------------------------------------------- + // Set the node's children ... + // -------------------------------------------------- + info.setChildren(graphNode.getChildren()); + + // ------------------------------------------------------ + // Set the node's properties ... + // ------------------------------------------------------ + // First get the property type for each property, based upon the primary type and mixins ... + // The map with single-valued properties... + Map svPropertyDefinitionsByPropertyName = new HashMap(); + // ... and the map with multi-valued properties + Map mvPropertyDefinitionsByPropertyName = new HashMap(); + + boolean referenceable = false; + + List anyPropertyDefinitions = new LinkedList(); + // Start with the primary type ... + NodeType primaryType = nodeTypes().getNodeType(primaryTypeName); + for (PropertyDefinition propertyDefn : primaryType.getPropertyDefinitions()) { + String nameString = propertyDefn.getName(); + if ("*".equals(nameString)) { + anyPropertyDefinitions.add(propertyDefn); + continue; + } + Name name = nameFactory.create(nameString); + + if (propertyDefn.isMultiple()) { + PropertyDefinition prev = mvPropertyDefinitionsByPropertyName.put(name, propertyDefn); + if (prev != null) mvPropertyDefinitionsByPropertyName.put(name, prev); // put the first one back ... + } else { + PropertyDefinition prev = svPropertyDefinitionsByPropertyName.put(name, propertyDefn); + if (prev != null) svPropertyDefinitionsByPropertyName.put(name, prev); // put the first one back ... + } + } + // The process the mixin types ... + org.jboss.dna.graph.property.Property mixinTypesProperty = graphProperties.get(JcrLexicon.MIXIN_TYPES); + if (mixinTypesProperty != null && !mixinTypesProperty.isEmpty()) { + for (Object mixinTypeValue : mixinTypesProperty) { + Name mixinTypeName = nameFactory.create(mixinTypeValue); + if (!referenceable && JcrMixLexicon.REFERENCEABLE.equals(mixinTypeName)) referenceable = true; + String mixinTypeNameString = mixinTypeName.getString(namespaces); + NodeType mixinType = nodeTypes().getNodeType(mixinTypeNameString); + for (PropertyDefinition propertyDefn : mixinType.getPropertyDefinitions()) { + String nameString = propertyDefn.getName(); + if ("*".equals(nameString)) { + anyPropertyDefinitions.add(propertyDefn); + continue; + } + Name name = nameFactory.create(nameString); + if (propertyDefn.isMultiple()) { + PropertyDefinition prev = mvPropertyDefinitionsByPropertyName.put(name, propertyDefn); + if (prev != null) mvPropertyDefinitionsByPropertyName.put(name, prev); // put the first one back ... + } else { + PropertyDefinition prev = svPropertyDefinitionsByPropertyName.put(name, propertyDefn); + if (prev != null) svPropertyDefinitionsByPropertyName.put(name, prev); // put the first one back ... + } + } + } + } + + // Now create the JCR property object wrappers around the other properties ... + for (org.jboss.dna.graph.property.Property dnaProp : graphProperties.values()) { + Name name = dnaProp.getName(); + + // Figure out the JCR property type for this property ... + PropertyDefinition propertyDefinition; + if (dnaProp.isMultiple()) { + propertyDefinition = mvPropertyDefinitionsByPropertyName.get(name); + } else { + propertyDefinition = svPropertyDefinitionsByPropertyName.get(name); + + // If the property has only one value, dnaProp.isMultiple() will return false, but the + // property may actually be a multi-valued property that happens to have one property set. + if (propertyDefinition == null) { + propertyDefinition = mvPropertyDefinitionsByPropertyName.get(name); + } + } + + // If no property type was found for this property, see if there is a wildcard property ... + if (propertyDefinition == null) { + for (Iterator iter = anyPropertyDefinitions.iterator(); iter.hasNext();) { + PropertyDefinition nextDef = iter.next(); + + // Grab the first residual definition that matches on cardinality (single-valued vs. multi-valued) + if ((nextDef.isMultiple() && dnaProp.isMultiple()) || (!nextDef.isMultiple() && !dnaProp.isMultiple())) { + propertyDefinition = nextDef; + break; + } + } + } + + // If there still is no property type defined ... + if (propertyDefinition == null) { + assert anyPropertyDefinitions.isEmpty(); + if (INCLUDE_PROPERTIES_NOT_ALLOWED_BY_NODE_TYPE_OR_MIXINS) { + // We can use the "nt:unstructured" property definitions for any property ... + NodeType unstructured = nodeTypes().getNodeType(JcrNtLexicon.UNSTRUCTURED); + for (PropertyDefinition anyDefinition : unstructured.getDeclaredPropertyDefinitions()) { + if (anyDefinition.isMultiple()) { + propertyDefinition = anyDefinition; + break; + } + } + } + } + if (propertyDefinition == null) { + // We're supposed to skip this property (since we don't have a definition for it) ... + continue; + } + + // Figure out if this is a multi-valued property ... + boolean isMultiple = propertyDefinition.isMultiple(); + if (!isMultiple && dnaProp.isEmpty()) { + // Only multi-valued properties can have no values; so if not multi-valued, then skip ... + continue; + } + + // Figure out the property type ... + int propertyType = propertyDefinition.getRequiredType(); + if (propertyType == PropertyType.UNDEFINED) { + propertyType = JcrSession.jcrPropertyTypeFor(dnaProp); + } + + // Record the property in the node information ... + PropertyId propId = new PropertyId(uuid, name); + info.setProperty(propId, (JcrPropertyDefinition)propertyDefinition, propertyType, dnaProp); + } + + // Now add the "jcr:uuid" property if and only if referenceable ... + if (referenceable) { + // We know that this property is single-valued + PropertyDefinition propertyDefinition = svPropertyDefinitionsByPropertyName.get(JcrLexicon.UUID); + PropertyId propId = new PropertyId(uuid, JcrLexicon.UUID); + info.setProperty(propId, (JcrPropertyDefinition)propertyDefinition, PropertyType.STRING, uuidProperty); + } else { + // Make sure there is NOT a "jcr:uuid" property ... + info.removeProperty(JcrLexicon.UUID); + } + // Make sure the "dna:uuid" property did not get in there ... + info.removeProperty(DnaLexicon.UUID); + + return info; + } + + /** + * Utility method to find the {@link NodeDefinition} for a child node with the supplied {@link Name name}, parent information, + * and the primary node type of the child. + * + * @param parentInfo the parent information; may not be null + * @param childName the name of the child node (without any same-name-sibling index); may not be null + * @param primaryTypeOfChild the name of the child's primary type + * @return the node definition for this child, as best as can be determined, or null if the node definition could not be + * determined + * @throws RepositoryException if the parent's pimary node type cannot be found in the {@link NodeTypeManager} + */ + protected JcrNodeDefinition findNodeDefinitionForChild( NodeInfo parentInfo, + Name childName, + Name primaryTypeOfChild ) throws RepositoryException { + // Get the primary type of the parent, and look at it's child definitions ... + Name primaryTypeName = parentInfo.getPrimaryTypeName(); + JcrNodeType primaryType = nodeTypes().getNodeType(primaryTypeName); + if (primaryType == null) { + String msg = JcrI18n.missingNodeTypeForExistingNode.text(primaryTypeName, parentInfo.getUuid(), workspaceName); + throw new RepositoryException(msg); + } + // TODO: should this also check the mixins? + return primaryType.findBestNodeDefinitionForChild(childName, primaryTypeOfChild); + } + + /** + * The information that describes a node. This is the information that is kept in the {@link SessionCache#cachedNodes cache} + * and in the record of {@link SessionCache#changedNodes changes} made by the session but not yet commited/saved. + */ + @NotThreadSafe + protected class NodeInfo { + private final Location originalLocation; + private final UUID uuid; + private UUID parent; + private final Name primaryTypeName; + private final NodeDefinitionId definition; + private final Children children; + private final Map properties; + + protected NodeInfo( Location originalLocation, + Name primaryTypeName, + NodeDefinitionId definition ) { + this.originalLocation = originalLocation; + this.primaryTypeName = primaryTypeName; + this.definition = definition; + this.uuid = this.originalLocation.getUuid(); + this.children = new Children(this.uuid); + this.properties = new HashMap(); + assert this.uuid != null; + assert this.definition != null; + assert this.primaryTypeName != null; + } + + /** + * @return location + */ + public Location getOriginalLocation() { + return originalLocation; + } + + /** + * @return uuid + */ + public UUID getUuid() { + return uuid; + } + + /** + * @return parent + */ + public UUID getParent() { + return parent; + } + + /** + * @param parent Sets parent to the specified value. + */ + protected void setParent( UUID parent ) { + this.parent = parent; + } + + /** + * @return primaryTypeName + */ + public Name getPrimaryTypeName() { + return primaryTypeName; + } + + /** + * @return definition + */ + public NodeDefinitionId getDefinitionId() { + return definition; + } + + /** + * Get the children for this node. + * + * @return the children; never null but possibly empty + */ + public Children getChildren() { + return children; + } + + /** + * @param children Sets children to the specified value. + * @return the children information; never null + */ + public Children setChildren( List children ) { + this.children.append(SessionCache.this, children, true); + return this.children; + } + + /** + * Get the map of information for each property. + * + * @return the information for each property; never null but possibly (though unlikely) empty + */ + public Map getProperties() { + return this.properties; // never null + } + + public PropertyInfo getProperty( Name name ) { + return this.properties.get(name); + } + + public PropertyInfo setProperty( PropertyId id, + JcrPropertyDefinition definition, + int propertyType, + Property dnaProperty ) { + // Initialize the map if required (this never replaces it, though) ... + PropertyInfo info = new PropertyInfo(id, definition.getId(), propertyType, dnaProperty, definition.isMultiple()); + return this.properties.put(id.getPropertyName(), info); + } + + public PropertyInfo removeProperty( Name name ) { + return this.properties.remove(name); + } + } + + /** + * An immutable representation of the name and current value(s) for a property, along with the JCR metadata for the property, + * including the {@link PropertyInfo#getDefinitionId() property definition} and {@link PropertyInfo#getPropertyType() property + * type}. + *

+ * This class is immutable, which means that clients should never hold onto an instance. Instead, clients can obtain an + * instance by using a {@link PropertyId}, quickly use the information in the instance, and then immediately discard their + * reference. This is because these instances are replaced and discarded whenever anything about the property changes. + *

+ */ + @Immutable + public static class PropertyInfo { + private final PropertyId propertyId; + private final PropertyDefinitionId definitionId; + private final Property dnaProperty; + private final int propertyType; + private final boolean multiValued; + + protected PropertyInfo( PropertyId propertyId, + PropertyDefinitionId definitionId, + int propertyType, + Property dnaProperty, + boolean multiValued ) { + this.propertyId = propertyId; + this.definitionId = definitionId; + this.propertyType = propertyType; + this.dnaProperty = dnaProperty; + this.multiValued = multiValued; + } + + /** + * Get the durable identifier for this property. + * + * @return propertyId + */ + public PropertyId getPropertyId() { + return propertyId; + } + + /** + * Get the UUID of the node to which this property belongs. + * + * @return the owner node's UUID; never null + */ + public UUID getNodeUuid() { + return propertyId.getNodeId(); + } + + /** + * The identifier for the property definition. + * + * @return the property definition ID; never null + */ + public PropertyDefinitionId getDefinitionId() { + return definitionId; + } + + /** + * Get the DNA Property, which contains the name and value(s) + * + * @return the property; never null + */ + public Property getProperty() { + return dnaProperty; + } + + /** + * Get the property name. + * + * @return the property name; never null + */ + public Name getPropertyName() { + return dnaProperty.getName(); + } + + /** + * Get the JCR {@link PropertyType} for this property. + * + * @return the property type + */ + public int getPropertyType() { + return propertyType; + } + + /** + * @return multiValued + */ + public boolean isMultiValued() { + return multiValued; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return propertyId.hashCode(); + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( Object obj ) { + if (obj == this) return true; + if (obj instanceof PropertyInfo) { + return propertyId.equals(((PropertyInfo)obj).getPropertyId()); + } + return false; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(propertyId); + sb.append(" defined by ").append(definitionId); + sb.append(" of type ").append(PropertyType.nameFromValue(propertyType)); + if (dnaProperty.isSingle()) { + sb.append(" with value "); + } else { + sb.append(" with values "); + } + sb.append(dnaProperty.getValuesAsArray()); + return sb.toString(); + } + } + + /** + * Class that maintains the ordered list of {@link ChildNode} instances. This class uses a {@link ListMultimap} to maintain + * insertion order of the child nodes, and to allow fast access to the children with a specified name. + */ + @ThreadSafe + public final static class Children implements Iterable { + private final UUID parentUuid; + private final Map childrenByUuid; + private final ListMultimap childrenByName; + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + Children( UUID parentUuid ) { + this.parentUuid = parentUuid; + this.childrenByUuid = new HashMap(); + this.childrenByName = new LinkedListMultimap(); + } + + /** + * Get the number of children. + * + * @return the number of children + */ + public int size() { + try { + lock.readLock().lock(); + return childrenByName.size(); + } finally { + lock.readLock().unlock(); + } + } + + /** + * Get the children. + * + * @return a copy of the list of children + */ + public List asList() { + try { + lock.readLock().lock(); + return new ArrayList(childrenByName.values()); + } finally { + lock.readLock().unlock(); + } + } + + /** + * Get the children. + * + * @return a copy of the list of children + */ + public Iterator iterator() { + return asList().iterator(); + } + + /** + * The UUID of the parent node. + * + * @return the parent node's UUID + */ + public UUID getParentUuid() { + return parentUuid; + } + + /** + * Get the child with the given UUID. + * + * @param uuid the UUID of the child node + * @return the child node, or null if there is no child with the supplied UUID + */ + public ChildNode getChild( UUID uuid ) { + try { + lock.readLock().lock(); + return this.childrenByUuid.get(uuid); + } finally { + lock.readLock().unlock(); + } + } + + /** + * Get the child given the path segment. + * + * @param segment the path segment for the child, which includes the {@link Path.Segment#getName() name} and + * {@link Path.Segment#getIndex() one-based same-name-sibling index}; may not be null + * @return the information for the child node, or null if no such child existed + */ + public ChildNode getChild( Path.Segment segment ) { + try { + lock.readLock().lock(); + List childrenWithName = this.childrenByName.get(segment.getName()); + int snsIndex = segment.getIndex(); + if (childrenWithName.size() < snsIndex) return null; + return childrenWithName.get(snsIndex - 1); + } finally { + lock.readLock().unlock(); + } + } + + /** + * Get the same-name-sibling children that all share the supplied name. + * + * @param name the name for the children; may not be null + * @return the children with the supplied name; never null + */ + public List getChildren( Name name ) { + try { + lock.readLock().lock(); + return new ArrayList(this.childrenByName.get(name)); + } finally { + lock.readLock().unlock(); + } + } + + /** + * Remove the child with the given path segment. + * + * @param segment the path segment for the child, which includes the {@link Path.Segment#getName() name} and + * {@link Path.Segment#getIndex() one-based same-name-sibling index}; may not be null + * @return the information for the child node that was removed, or null if no such child existed + */ + public ChildNode remove( Path.Segment segment ) { + try { + lock.writeLock().lock(); + List childrenWithName = this.childrenByName.get(segment.getName()); + int snsIndex = segment.getIndex(); + int numChildrenWithName = childrenWithName.size(); + if (numChildrenWithName < snsIndex) return null; + ChildNode result = childrenWithName.remove(snsIndex); + this.childrenByUuid.remove(result.getUuid()); + --numChildrenWithName; + if (numChildrenWithName > snsIndex) { + // We need to reduce the SNS index of every child after the one that was just removed ... + ListIterator siblingIter = childrenWithName.listIterator(snsIndex); + while (siblingIter.hasNext()) { + // Remove the next child and replace with one having the correct SNS index ... + ChildNode next = siblingIter.next(); + siblingIter.remove(); + siblingIter.set(next.withChangedSnsIndex(-1)); + } + } + return result; + } finally { + lock.writeLock().unlock(); + } + } + + /** + * Add a new child to the end of the list of existing children a new child with the supplied name and UUID. The + * same-name-sibling will be determined to be one more than the number of existing children with the same name. + * + * @param cache the cache; may not be null + * @param name the name for the child to be appended; may not be null + * @param childUuid the UUID of the child; may not be null + * @return the information for the newly-added child; never null + */ + public ChildNode append( SessionCache cache, + Name name, + UUID childUuid ) { + try { + lock.writeLock().lock(); + List childrenWithName = this.childrenByName.get(name); + ChildNode child = new ChildNode(childUuid, name, childrenWithName.size() + 1); + childrenWithName.add(child); + this.childrenByUuid.put(childUuid, child); + // Look for the child in the cache/changed nodes ... + NodeInfo childInfo = cache.findNodeInfoInCache(childUuid); + if (childInfo != null) childInfo.setParent(parentUuid); + return child; + } finally { + lock.writeLock().unlock(); + } + } + + /** + * Append the children described by the supplied Location objects, optionally removing all existing children first. + * + * @param cache the cache; may not be null + * @param children a list containing a Location object for each child + * @param removeExistingFirst true if the existing children should be removed before these children are added, or false if + * these children should be appended to the existing children + */ + public void append( SessionCache cache, + List children, + boolean removeExistingFirst ) { + try { + lock.writeLock().lock(); + if (removeExistingFirst && !this.childrenByName.isEmpty()) { + for (ChildNode child : this.childrenByName.values()) { + // Look for the child in the cache/changed nodes ... + NodeInfo childInfo = cache.findNodeInfoInCache(child.getUuid()); + if (childInfo != null) childInfo.setParent(null); + // TODO: These nodes are deleted and should be handled as deletes ... + } + this.childrenByUuid.clear(); + this.childrenByName.clear(); + } + for (Location childLocation : children) { + UUID childUuid = childLocation.getUuid(); + Path.Segment segment = childLocation.getPath().getLastSegment(); + Name name = segment.getName(); + ChildNode child = new ChildNode(childUuid, name, segment.getIndex()); + this.childrenByName.put(name, child); + this.childrenByUuid.put(childUuid, child); + // Look for the child in the cache/changed nodes ... + NodeInfo childInfo = cache.findNodeInfoInCache(childUuid); + if (childInfo != null) childInfo.setParent(parentUuid); + } + } finally { + lock.writeLock().unlock(); + } + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + try { + lock.readLock().lock(); + boolean first = true; + for (ChildNode child : childrenByName.values()) { + if (!first) sb.append(", "); + else first = false; + sb.append(child.getName()).append('[').append(child.getSnsIndex()).append(']'); + } + } finally { + lock.readLock().unlock(); + } + return sb.toString(); + } + } + + /** + * The information about a child node. This is designed to be found in the {@link Children}, used quickly, and discarded. + * Clients should not hold on to these objects, since any changes to the children involve discarding the old ChildNode objects + * and replacing them with new instances. + */ + @Immutable + public final static class ChildNode { + private final UUID uuid; + private final Name name; + private final int snsIndex; + + protected ChildNode( UUID uuid, + Name name, + int snsIndex ) { + this.uuid = uuid; + this.name = name; + this.snsIndex = snsIndex; + assert this.snsIndex > 0; + } + + /** + * Get the UUID of the node. + * + * @return the node's UUID; never null + */ + public UUID getUuid() { + return uuid; + } + + /** + * Get the name of the node. + * + * @return the node's current name; never null + */ + public Name getName() { + return name; + } + + /** + * Get the same-name-sibling index of the node. + * + * @return the node's SNS index; always positive + */ + public int getSnsIndex() { + return snsIndex; + } + + /** + * Return a new child node that has a changed SNS index. + * + * @param delta the amount the change the SNS index, either positive to increase the value or negative to decrease the + * value + * @return the copy of this, with a changed SNS index; never null + */ + public ChildNode withChangedSnsIndex( int delta ) { + return new ChildNode(uuid, name, snsIndex + delta); + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return uuid.hashCode(); + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( Object obj ) { + if (obj == this) return true; + if (obj instanceof ChildNode) { + return this.uuid.equals(((ChildNode)obj).uuid); + } + return false; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return uuid.toString() + " ( " + name + "[" + snsIndex + "] )"; + } + } + +} Index: dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java (working copy) @@ -25,11 +25,10 @@ import java.io.InputStream; import java.util.Calendar; -import java.util.HashSet; +import java.util.Collection; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.UUID; import java.util.regex.Pattern; import javax.jcr.Item; @@ -42,7 +41,6 @@ import javax.jcr.PropertyIterator; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; -import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.lock.Lock; @@ -51,93 +49,51 @@ import javax.jcr.nodetype.NodeTypeManager; import javax.jcr.version.Version; import javax.jcr.version.VersionHistory; -import net.jcip.annotations.NotThreadSafe; +import net.jcip.annotations.Immutable; +import org.jboss.dna.common.i18n.I18n; import org.jboss.dna.common.util.CheckArg; -import org.jboss.dna.graph.Location; import org.jboss.dna.graph.property.Name; import org.jboss.dna.graph.property.NamespaceRegistry; import org.jboss.dna.graph.property.Path; import org.jboss.dna.graph.property.ValueFormatException; +import org.jboss.dna.jcr.SessionCache.ChildNode; +import org.jboss.dna.jcr.SessionCache.Children; +import org.jboss.dna.jcr.SessionCache.NodeInfo; /** - * @author jverhaeg + * An abstract implementation of the JCR {@link Node} interface. Instances of this class are created and managed by the + * {@link SessionCache}. Each instance references the {@link SessionCache.NodeInfo node information} also managed by the + * SessionCache, and finds and operates against this information with each method call. */ -@NotThreadSafe +@Immutable abstract class AbstractJcrNode extends AbstractJcrItem implements Node { private static final NodeType[] EMPTY_NODE_TYPES = new NodeType[] {}; - private final JcrSession session; - private final NodeDefinition definition; - protected Location location; - private Map properties; - private List children; + protected final UUID nodeUuid; - AbstractJcrNode( JcrSession session, - Location location, - NodeDefinition definition ) { - assert session != null; - assert definition != null; - assert location != null; - this.location = location; - this.session = session; - this.definition = definition; + AbstractJcrNode( SessionCache cache, + UUID nodeUuid ) { + super(cache); + this.nodeUuid = nodeUuid; } - /** - * {@inheritDoc} - * - * @see javax.jcr.Item#getSession() - */ - public Session getSession() { - return session; - } + abstract boolean isRoot(); - final JcrSession session() { - return session; - } - final UUID internalUuid() { - return location.getUuid(); + return nodeUuid; } - /** - * Change the internal UUID. Technically, it is possible for this to change, but only because a new node may be assigned a - * UUID when it is created within the session and (according to the spec) this UUID can change when the new node is persisted - * upon {@link #save()}. - * - * @param uuid the new UUID; may not be null - */ - final void setInternalUuid( UUID uuid ) { - assert uuid != null; - location = location.with(uuid); + final NodeInfo nodeInfo() throws ItemNotFoundException, RepositoryException { + return cache.findNodeInfo(nodeUuid); } - final void setChildren( List children ) { - assert children != null; - this.children = children; + @Override + Path path() throws RepositoryException { + return cache.getPathFor(nodeInfo()); } - final void setProperties( Map properties ) { - assert properties != null; - this.properties = properties; - } - /** - * Utility mehtod to create a {@link Name} from the supplied string. - * - * @param name the string - * @return the name, or null if the supplied string is null - */ - final Name nameFrom( String name ) { - return session.getExecutionContext().getValueFactories().getNameFactory().create(name); - } - - final NamespaceRegistry namespaces() { - return session.getExecutionContext().getNamespaceRegistry(); - } - - /** * {@inheritDoc} * * @see javax.jcr.Node#getUUID() @@ -148,7 +104,7 @@ if (mixinsProp != null) { String referenceableMixinName = JcrMixLexicon.REFERENCEABLE.getString(namespaces()); for (Value value : mixinsProp.getValues()) { - if (referenceableMixinName.equals(value.getString())) return getProperty(JcrLexicon.UUID).getString(); + if (referenceableMixinName.equals(value.getString())) return nodeUuid.toString(); } } throw new UnsupportedRepositoryOperationException(); @@ -192,8 +148,9 @@ * * @see javax.jcr.Node#getDefinition() */ - public NodeDefinition getDefinition() { - return definition; + public NodeDefinition getDefinition() throws RepositoryException { + NodeDefinitionId definitionId = nodeInfo().getDefinitionId(); + return session().nodeTypeManager().getNodeDefinition(definitionId); } /** @@ -203,8 +160,8 @@ * @see javax.jcr.Node#getPrimaryNodeType() */ public NodeType getPrimaryNodeType() throws RepositoryException { - String nodeTypeName = getProperty(JcrLexicon.PRIMARY_TYPE).getString(); - return session.getWorkspace().getNodeTypeManager().getNodeType(nodeTypeName); + Name primaryTypeName = nodeInfo().getPrimaryTypeName(); + return session().nodeTypeManager().getNodeType(primaryTypeName); } /** @@ -214,7 +171,7 @@ * @see javax.jcr.Node#getMixinNodeTypes() */ public NodeType[] getMixinNodeTypes() throws RepositoryException { - NodeTypeManager nodeTypeManager = session.getWorkspace().getNodeTypeManager(); + NodeTypeManager nodeTypeManager = session().getWorkspace().getNodeTypeManager(); Property mixinTypesProperty = getProperty(JcrLexicon.MIXIN_TYPES); if (mixinTypesProperty == null) return EMPTY_NODE_TYPES; List mixinNodeTypes = new LinkedList(); @@ -252,12 +209,35 @@ * @see javax.jcr.Node#getPrimaryItem() */ public final Item getPrimaryItem() throws RepositoryException { - // TODO: Check if declared in the node type first + // Get the primary item name from this node's type ... + NodeType primaryType = getPrimaryNodeType(); + String primaryItemNameString = primaryType.getPrimaryItemName(); + if (primaryItemNameString == null) { + I18n msg = JcrI18n.noPrimaryItemNameDefinedOnPrimaryType; + throw new ItemNotFoundException(msg.text(primaryType.getName(), getPath(), cache.workspaceName())); + } try { - Property primaryItemProp = getProperty("jcr:primaryItemName"); - return session.getItem(getPath(getPath(), primaryItemProp.getString())); + Path primaryItemPath = context().getValueFactories().getPathFactory().create(primaryItemNameString); + if (primaryItemPath.size() != 1 || primaryItemPath.isAbsolute()) { + I18n msg = JcrI18n.primaryItemNameForPrimaryTypeIsNotValid; + throw new ItemNotFoundException(msg.text(primaryType.getName(), + primaryItemNameString, + getPath(), + cache.workspaceName())); + } + return cache.findJcrItem(nodeUuid, primaryItemPath); + } catch (ValueFormatException error) { + I18n msg = JcrI18n.primaryItemNameForPrimaryTypeIsNotValid; + throw new ItemNotFoundException(msg.text(primaryType.getName(), + primaryItemNameString, + getPath(), + cache.workspaceName())); } catch (PathNotFoundException error) { - throw new ItemNotFoundException(error); + I18n msg = JcrI18n.primaryItemDoesNotExist; + throw new ItemNotFoundException(msg.text(primaryType.getName(), + primaryItemNameString, + getPath(), + cache.workspaceName())); } } @@ -268,7 +248,7 @@ * @see javax.jcr.Item#isSame(javax.jcr.Item) */ @Override - public final boolean isSame( Item otherItem ) throws RepositoryException { + public boolean isSame( Item otherItem ) throws RepositoryException { CheckArg.isNotNull(otherItem, "otherItem"); if (super.isSame(otherItem) && otherItem instanceof Node) { if (otherItem instanceof AbstractJcrNode) { @@ -285,9 +265,8 @@ * * @see javax.jcr.Node#hasProperties() */ - public final boolean hasProperties() { - assert properties != null; - return !properties.isEmpty(); + public final boolean hasProperties() throws RepositoryException { + return nodeInfo().getProperties().size() > 0; } /** @@ -299,10 +278,17 @@ public final boolean hasProperty( String relativePath ) throws RepositoryException { CheckArg.isNotEmpty(relativePath, "relativePath"); if (relativePath.indexOf('/') >= 0) { - return (getProperty(relativePath) != null); + try { + getProperty(relativePath); + return true; + } catch (PathNotFoundException e) { + return false; + } } - assert properties != null; - return properties.containsKey(nameFrom(relativePath)); + if (relativePath.equals(".")) return false; + if (relativePath.equals("..")) return false; + // Otherwise it should be a property on this node ... + return cache.findPropertyInfo(new PropertyId(nodeUuid, nameFrom(relativePath))) != null; } /** @@ -310,8 +296,8 @@ * * @see javax.jcr.Node#getProperties() */ - public final PropertyIterator getProperties() { - return new JcrPropertyIterator(properties.values()); + public final PropertyIterator getProperties() throws RepositoryException { + return new JcrPropertyIterator(cache.findJcrPropertiesFor(nodeUuid)); } /** @@ -323,40 +309,67 @@ CheckArg.isNotNull(namePattern, "namePattern"); namePattern = namePattern.trim(); if (namePattern.length() == 0) return new JcrEmptyPropertyIterator(); - if ("*".equals(namePattern)) return new JcrPropertyIterator(properties.values()); + Collection properties = cache.findJcrPropertiesFor(nodeUuid); + if ("*".equals(namePattern)) return new JcrPropertyIterator(properties); + + // Figure out the patterns for each of the different disjunctions in the supplied pattern ... List patterns = createPatternsFor(namePattern); - // Implementing exact-matching only for now to prototype types as properties - Set matchingProps = new HashSet(); - boolean foundMatch = false; - for (Property property : properties.values()) { + // Go through the properties and remove any property that doesn't match a pattern ... + boolean foundMatch = true; + Collection matchingProperties = new LinkedList(); + Iterator iter = properties.iterator(); + while (iter.hasNext()) { + AbstractJcrProperty property = iter.next(); String propName = property.getName(); + assert foundMatch == true; for (Object patternOrMatch : patterns) { if (patternOrMatch instanceof Pattern) { Pattern pattern = (Pattern)patternOrMatch; - if (pattern.matcher(propName).matches()) foundMatch = true; + if (pattern.matcher(propName).matches()) break; } else { String match = (String)patternOrMatch; - if (propName.equals(match)) foundMatch = true; + if (propName.equals(match)) break; } - if (foundMatch) { - foundMatch = false; - matchingProps.add(property); - break; - } + // No pattern matched ... + foundMatch = false; } + if (foundMatch) { + matchingProperties.add(property); + foundMatch = true; // for the next iteration .. + } } - return new JcrPropertyIterator(matchingProps); + return new JcrPropertyIterator(matchingProperties); } /** + * {@inheritDoc} + * + * @throws UnsupportedOperationException always + * @see javax.jcr.Node#getReferences() + */ + public final PropertyIterator getReferences() throws RepositoryException { + // Iterate through the properties to see which ones have a REFERENCE type ... + Collection properties = cache.findJcrPropertiesFor(nodeUuid); + Collection references = new LinkedList(); + Iterator iter = properties.iterator(); + while (iter.hasNext()) { + AbstractJcrProperty property = iter.next(); + if (property.getType() == PropertyType.REFERENCE) references.add(property); + } + if (references.isEmpty()) return new JcrEmptyPropertyIterator(); + return new JcrPropertyIterator(references); + } + + /** * A non-standard method to obtain a property given the {@link Name DNA Name} object. This method is faster * * @param propertyName the property name * @return the JCR property with the supplied name, or null if the property doesn't exist + * @throws RepositoryException if there is an error finding the property with the supplied name */ - public final Property getProperty( Name propertyName ) { - return properties.get(propertyName); + public final Property getProperty( Name propertyName ) throws RepositoryException { + return cache.findJcrProperty(new PropertyId(nodeUuid, propertyName)); } /** @@ -367,19 +380,23 @@ */ public final Property getProperty( String relativePath ) throws RepositoryException { CheckArg.isNotEmpty(relativePath, "relativePath"); - if (relativePath.indexOf('/') >= 0) { - Item item = session.getItem(getPath(getPath(), relativePath)); + int indexOfFirstSlash = relativePath.indexOf('/'); + if (indexOfFirstSlash == 0) { + // Not a relative path ... + throw new IllegalArgumentException(JcrI18n.invalidPathParameter.text(relativePath)); + } + if (indexOfFirstSlash != -1) { + // We know it's a relative path with more than one segment ... + Path path = pathFrom(relativePath).getNormalizedPath(); + AbstractJcrItem item = cache.findJcrItem(nodeUuid, path); if (item instanceof Property) { return (Property)item; } - // The item must be a node. - assert item instanceof Node; - // Since session.getItem() gives precedence to nodes over properties, try explicitly looking for the property with the - // same name as the found node using the returned node's parent. - return ((Node)item).getParent().getProperty(item.getName()); + I18n msg = JcrI18n.propertyNotFoundAtPathRelativeToReferenceNode; + throw new PathNotFoundException(msg.text(relativePath, getPath(), cache.workspaceName())); } - assert properties != null; - Property property = properties.get(nameFrom(relativePath)); + // It's just a name, so look for it directly ... + Property property = getProperty(nameFrom(relativePath)); if (property != null) return property; throw new PathNotFoundException(); } @@ -387,45 +404,31 @@ /** * {@inheritDoc} * - * @throws UnsupportedOperationException always - * @see javax.jcr.Node#getReferences() - */ - public final PropertyIterator getReferences() throws RepositoryException { - // Iterate through the properties to see which ones have a REFERENCE type ... - Set references = new HashSet(); - for (Property property : properties.values()) { - if (property.getType() == PropertyType.REFERENCE) references.add(property); - } - if (references.isEmpty()) return new JcrEmptyPropertyIterator(); - return new JcrPropertyIterator(references); - } - - /** - * {@inheritDoc} - * * @throws IllegalArgumentException if relativePath is empty or null. * @see javax.jcr.Node#hasNode(java.lang.String) */ public final boolean hasNode( String relativePath ) throws RepositoryException { CheckArg.isNotEmpty(relativePath, "relativePath"); - if (relativePath.indexOf('/') >= 0) { - return (getNode(relativePath) != null); + if (relativePath.equals(".")) return true; + if (relativePath.equals("..")) return isRoot() ? false : true; + int indexOfFirstSlash = relativePath.indexOf('/'); + if (indexOfFirstSlash == 0) { + // Not a relative path ... + throw new IllegalArgumentException(JcrI18n.invalidPathParameter.text(relativePath)); } - if (children != null) { + if (indexOfFirstSlash != -1) { + Path path = pathFrom(relativePath).getNormalizedPath(); try { - Path.Segment segment = session.getExecutionContext() - .getValueFactories() - .getPathFactory() - .createSegment(relativePath); - for (Location child : children) { - Path.Segment childSegment = child.getPath().getLastSegment(); - if (childSegment.equals(segment)) return true; - } - } catch (ValueFormatException e) { - throw new RepositoryException(JcrI18n.invalidRelativePath.text(relativePath)); + AbstractJcrNode item = cache.findJcrNode(nodeUuid, path); + return item != null; + } catch (PathNotFoundException e) { + return false; } } - return false; + // It's just a name, so look for a child ... + Path.Segment segment = segmentFrom(relativePath); + ChildNode child = nodeInfo().getChildren().getChild(segment); + return child != null; } /** @@ -433,8 +436,8 @@ * * @see javax.jcr.Node#hasNodes() */ - public final boolean hasNodes() { - return (children != null && !children.isEmpty()); + public final boolean hasNodes() throws RepositoryException { + return nodeInfo().getChildren().size() > 0; } /** @@ -445,12 +448,31 @@ */ public final Node getNode( String relativePath ) throws RepositoryException { CheckArg.isNotEmpty(relativePath, "relativePath"); - String path = getPath(getPath(), relativePath); - Item item = getSession().getItem(path); - if (item instanceof Node) { - return (Node)item; + if (relativePath.equals(".")) return this; + if (relativePath.equals("..")) return this.getParent(); + int indexOfFirstSlash = relativePath.indexOf('/'); + if (indexOfFirstSlash == 0) { + // Not a relative path ... + throw new IllegalArgumentException(JcrI18n.invalidPathParameter.text(relativePath)); } - throw new PathNotFoundException(); + if (indexOfFirstSlash != -1) { + // We know it's a relative path with more than one segment ... + Path path = pathFrom(relativePath).getNormalizedPath(); + AbstractJcrItem item = cache.findJcrItem(nodeUuid, path); + if (item instanceof Node) { + return (Node)item; + } + I18n msg = JcrI18n.nodeNotFoundAtPathRelativeToReferenceNode; + throw new PathNotFoundException(msg.text(relativePath, getPath(), cache.workspaceName())); + } + // It's just a name, so look for a child ... + Path.Segment segment = segmentFrom(relativePath); + ChildNode child = nodeInfo().getChildren().getChild(segment); + if (child != null) { + return cache.findJcrNode(child.getUuid()); + } + String msg = JcrI18n.childNotFoundUnderNode.text(segment, getPath(), cache.workspaceName()); + throw new PathNotFoundException(msg); } /** @@ -458,11 +480,12 @@ * * @see javax.jcr.Node#getNodes() */ - public final NodeIterator getNodes() { - if (children == null) { + public final NodeIterator getNodes() throws RepositoryException { + Children children = nodeInfo().getChildren(); + if (children.size() == 0) { return new JcrEmptyNodeIterator(); } - return new JcrChildNodeIterator(this, children); + return new JcrChildNodeIterator(cache, children, children.size()); } /** @@ -479,11 +502,12 @@ List patterns = createPatternsFor(namePattern); // Implementing exact-matching only for now to prototype types as properties - List matchingChildren = new LinkedList(); + Children children = nodeInfo().getChildren(); + List matchingChildren = new LinkedList(); NamespaceRegistry registry = namespaces(); boolean foundMatch = false; - for (Location child : children) { - String childName = child.getPath().getLastSegment().getName().getString(registry); + for (ChildNode child : children) { + String childName = child.getName().getString(registry); for (Object patternOrMatch : patterns) { if (patternOrMatch instanceof Pattern) { Pattern pattern = (Pattern)patternOrMatch; @@ -499,7 +523,7 @@ } } } - return new JcrChildNodeIterator(this, matchingChildren); + return new JcrChildNodeIterator(cache, matchingChildren, matchingChildren.size()); } /** Index: dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyDefinitionId.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyDefinitionId.java (revision 0) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyDefinitionId.java (revision 0) @@ -0,0 +1,162 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * Unless otherwise indicated, all code in JBoss DNA is licensed + * to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.jcr; + +import java.io.Serializable; +import net.jcip.annotations.Immutable; +import org.jboss.dna.common.util.HashCode; +import org.jboss.dna.graph.property.Name; +import org.jboss.dna.graph.property.NameFactory; +import org.jboss.dna.graph.property.ValueFormatException; + +/** + * An immutable identifier for a property definition. Although instances can be serialized, the property definitions are often + * stored within the graph as {@link #getString() string values} on a property. These string values can later be + * {@link #fromString(String, NameFactory) parsed} to reconstruct the identifier. Note that this string representation does not + * use namespace prefixes, so they are long-lasting and durable. + */ +@Immutable +public final class PropertyDefinitionId implements Serializable { + + /** + * Current version is {@value} . + */ + private static final long serialVersionUID = 1L; + + /** + * The string-form of the name that can be used to represent a residual property definition. + */ + public static final String ANY_NAME = JcrNodeType.RESIDUAL_ITEM_NAME; + + private final Name nodeTypeName; + private final Name propertyDefinitionName; + private final int hc; + + /** + * Create a new identifier for a propety definition. + * + * @param nodeTypeName the name of the node type; may not be null + * @param propertyDefinitionName the name of the property definition, which may be a {@link #ANY_NAME residual property}; may + * not be null + */ + public PropertyDefinitionId( Name nodeTypeName, + Name propertyDefinitionName ) { + this.nodeTypeName = nodeTypeName; + this.propertyDefinitionName = propertyDefinitionName; + this.hc = HashCode.compute(this.nodeTypeName, this.propertyDefinitionName); + } + + /** + * Get the name of the node type on which the property definition is defined + * + * @return the node type's name; may not be null + */ + public Name getNodeTypeName() { + return nodeTypeName; + } + + /** + * Get the name of the property definition. + * + * @return the property definition's name; never null + */ + public Name getPropertyDefinitionName() { + return propertyDefinitionName; + } + + /** + * Determine whether this property definition allows properties with any name. + * + * @return true if this node definition allows properties with any name, or false if this definition requires a particular + * property name + */ + public boolean allowsAnyChildName() { + return propertyDefinitionName.getLocalName().equals(ANY_NAME) && propertyDefinitionName.getNamespaceUri().length() == 0; + } + + /** + * Get the string form of this identifier. This form can be persisted, since it does not rely upon namespace prefixes. + * + * @return the string form + */ + public String getString() { + return this.nodeTypeName.getString() + '/' + this.propertyDefinitionName.getString(); + } + + /** + * Parse the supplied string for of an identifer, and return the object form for that identifier. + * + * @param definition the {@link #getString() string form of the identifier}; may not be null + * @param factory the factory that should be used to create Name objects; may not be null + * @return the object form of the identifier; never null + * @throws ValueFormatException if the definition is not the valid format + */ + public static PropertyDefinitionId fromString( String definition, + NameFactory factory ) { + int index = definition.indexOf('/'); + String nodeTypeNameString = definition.substring(0, index); + String propertyDefinitionNameString = definition.substring(index + 1); + Name nodeTypeName = factory.create(nodeTypeNameString); + Name propertyDefinitionName = factory.create(propertyDefinitionNameString); + return new PropertyDefinitionId(nodeTypeName, propertyDefinitionName); + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return hc; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( Object obj ) { + if (obj == this) return true; + if (obj instanceof PropertyDefinitionId) { + PropertyDefinitionId that = (PropertyDefinitionId)obj; + if (this.hc != that.hc) return false; + if (!this.nodeTypeName.equals(that.nodeTypeName)) return false; + return this.propertyDefinitionName.equals(that.propertyDefinitionName); + } + return false; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return this.nodeTypeName.toString() + '/' + this.propertyDefinitionName.toString(); + } + +} Index: dna-jcr/src/main/java/org/jboss/dna/jcr/NodeDefinitionId.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/NodeDefinitionId.java (revision 0) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/NodeDefinitionId.java (revision 0) @@ -0,0 +1,164 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * Unless otherwise indicated, all code in JBoss DNA is licensed + * to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * JBoss DNA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.dna.jcr; + +import java.io.Serializable; +import net.jcip.annotations.Immutable; +import org.jboss.dna.common.util.HashCode; +import org.jboss.dna.graph.property.Name; +import org.jboss.dna.graph.property.NameFactory; +import org.jboss.dna.graph.property.ValueFormatException; + +/** + * An immutable identifier for a node definition. Although instances can be serialized, the node definitions are often stored + * within the graph as {@link #getString() string values} on a property. These string values can later be + * {@link #fromString(String, NameFactory) parsed} to reconstruct the identifier. Note that this string representation does not + * use namespace prefixes, so they are long-lasting and durable. + */ +@Immutable +public final class NodeDefinitionId implements Serializable { + + /** + * Current version is {@value} . + */ + private static final long serialVersionUID = 1L; + + /** + * The string-form of the name that can be used to represent a residual property definition. + */ + public static final String ANY_NAME = JcrNodeType.RESIDUAL_ITEM_NAME; + + private final Name nodeTypeName; + private final Name childDefinitionName; + private final int hc; + + /** + * Create an identifier for a node definition. + * + * @param nodeTypeName the name of the node type on which this child node definition is defined; may not be null + * @param childDefinitionName the name of the child node definition, which may be a {@link #ANY_NAME residual child + * definition}; may not be null + */ + public NodeDefinitionId( Name nodeTypeName, + Name childDefinitionName ) { + assert nodeTypeName != null; + assert childDefinitionName != null; + this.nodeTypeName = nodeTypeName; + this.childDefinitionName = childDefinitionName; + this.hc = HashCode.compute(this.nodeTypeName, this.childDefinitionName); + } + + /** + * Get the name of the node type on which the child node definition is defined. + * + * @return the node type's name; never null + */ + public Name getNodeTypeName() { + return nodeTypeName; + } + + /** + * Get the name of the child definition. + * + * @return the child definition's name; never null + */ + public Name getChildDefinitionName() { + return childDefinitionName; + } + + /** + * Determine whether this node definition defines any named child. + * + * @return true if this node definition allows children with any name, or false if this definition requires a particular child + * name + */ + public boolean allowsAnyChildName() { + return childDefinitionName.getLocalName().equals(ANY_NAME) && childDefinitionName.getNamespaceUri().length() == 0; + } + + /** + * Get the string form of this identifier. This form can be persisted, since it does not rely upon namespace prefixes. + * + * @return the string form + */ + public String getString() { + return this.nodeTypeName.getString() + '/' + this.childDefinitionName.getString(); + } + + /** + * Parse the supplied string for of an identifer, and return the object form for that identifier. + * + * @param definition the {@link #getString() string form of the identifier}; may not be null + * @param factory the factory that should be used to create Name objects; may not be null + * @return the object form of the identifier; never null + * @throws ValueFormatException if the definition is not the valid format + */ + public static NodeDefinitionId fromString( String definition, + NameFactory factory ) { + int index = definition.indexOf('/'); + String nodeTypeNameString = definition.substring(0, index); + String childDefinitionNameString = definition.substring(index + 1); + Name nodeTypeName = factory.create(nodeTypeNameString); + Name childDefinitionName = factory.create(childDefinitionNameString); + return new NodeDefinitionId(nodeTypeName, childDefinitionName); + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return hc; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( Object obj ) { + if (obj == this) return true; + if (obj instanceof NodeDefinitionId) { + NodeDefinitionId that = (NodeDefinitionId)obj; + if (this.hc != that.hc) return false; + if (!this.nodeTypeName.equals(that.nodeTypeName)) return false; + return this.childDefinitionName.equals(that.childDefinitionName); + } + return false; + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return this.nodeTypeName.toString() + '/' + this.childDefinitionName.toString(); + } + +} Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java (working copy) @@ -28,11 +28,6 @@ import java.security.AccessControlException; import java.security.Principal; import java.util.Calendar; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -42,7 +37,6 @@ import javax.jcr.NamespaceException; import javax.jcr.Node; import javax.jcr.PathNotFoundException; -import javax.jcr.Property; import javax.jcr.PropertyType; import javax.jcr.Repository; import javax.jcr.RepositoryException; @@ -51,10 +45,6 @@ import javax.jcr.Value; import javax.jcr.ValueFactory; import javax.jcr.Workspace; -import javax.jcr.nodetype.NodeDefinition; -import javax.jcr.nodetype.NodeType; -import javax.jcr.nodetype.NodeTypeManager; -import javax.jcr.nodetype.PropertyDefinition; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; @@ -62,20 +52,14 @@ import org.jboss.dna.common.util.CheckArg; import org.jboss.dna.graph.ExecutionContext; import org.jboss.dna.graph.Graph; -import org.jboss.dna.graph.Location; -import org.jboss.dna.graph.connector.RepositorySourceException; import org.jboss.dna.graph.property.Name; -import org.jboss.dna.graph.property.NameFactory; import org.jboss.dna.graph.property.NamespaceRegistry; import org.jboss.dna.graph.property.Path; import org.jboss.dna.graph.property.ValueFactories; -import org.jboss.dna.graph.property.ValueFormatException; import org.jboss.dna.graph.property.basic.LocalNamespaceRegistry; import org.jboss.dna.jcr.JcrNamespaceRegistry.Behavior; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; -import com.google.common.base.ReferenceType; -import com.google.common.collect.ReferenceMap; /** * @author John Verhaeg @@ -84,12 +68,6 @@ @NotThreadSafe class JcrSession implements Session { - /** - * Hidden flag that controls whether properties that appear on DNA nodes but not allowed by the node type or mixins should be - * included anyway. This is currently {@value} . - */ - private static final boolean INCLUDE_PROPERTIES_NOT_ALLOWED_BY_NODE_TYPE_OR_MIXINS = true; - private static final String[] NO_ATTRIBUTES_NAMES = new String[] {}; /** @@ -114,21 +92,23 @@ protected final ExecutionContext executionContext; /** + * The session-specific attributes that came from the {@link SimpleCredentials}' {@link SimpleCredentials#getAttributeNames()} + */ + private final Map sessionAttributes; + + /** * The graph representing this session, which uses the {@link #graph session's graph}. */ private final Graph graph; + private final SessionCache cache; + /** - * The session-specific attributes that came from the {@link SimpleCredentials}' {@link SimpleCredentials#getAttributeNames()} + * A cached instance of the root path. */ - private final Map sessionAttributes; + private final Path rootPath; - private final ReferenceMap nodesByUuid; - private final ReferenceMap nodesByJcrUuid; private boolean isLive; - private JcrRootNode rootNode; - private PropertyDefinition anyMultiplePropertyDefinition; - private transient org.jboss.dna.graph.property.Property defaultPrimaryType; JcrSession( JcrRepository repository, JcrWorkspace workspace, @@ -147,14 +127,15 @@ NamespaceRegistry local = new LocalNamespaceRegistry(workspaceRegistry); this.executionContext = workspaceContext.with(local); this.sessionRegistry = new JcrNamespaceRegistry(Behavior.JSR170_SESSION, local, workspaceRegistry); + this.rootPath = this.executionContext.getValueFactories().getPathFactory().createRootPath(); // Set up the graph to use for this session (which uses the session's namespace registry and context) ... this.graph = Graph.create(this.repository.getRepositorySourceName(), this.repository.getConnectionFactory(), this.executionContext); - this.nodesByUuid = new ReferenceMap(ReferenceType.STRONG, ReferenceType.SOFT); - this.nodesByJcrUuid = new ReferenceMap(ReferenceType.STRONG, ReferenceType.SOFT); + this.cache = new SessionCache(this, workspace.getName(), this.executionContext, this.workspace.nodeTypeManager(), + this.graph); this.isLive = true; assert this.repository != null; @@ -169,24 +150,15 @@ return this.executionContext; } - final JcrNodeTypeManager nodeTypeManager() { + JcrNodeTypeManager nodeTypeManager() { return this.workspace.nodeTypeManager(); } - final NamespaceRegistry namespaces() { + NamespaceRegistry namespaces() { return this.executionContext.getNamespaceRegistry(); } /** - * Return an unmodifiable map of nodes given then UUID. - * - * @return nodesByUuid - */ - Map getNodesByUuid() { - return Collections.unmodifiableMap(nodesByUuid); - } - - /** * {@inheritDoc} * * @see javax.jcr.Session#getWorkspace() @@ -417,21 +389,6 @@ throw new UnsupportedOperationException(); } - Node getChild( AbstractJcrNode parent, - Location location ) throws RepositoryException { - Node child = null; - UUID uuid = location.getUuid(); - if (uuid != null) { - // The location has a UUID, so look up the node by this UUID in the session ... - child = getNode(uuid); - } - if (child == null) { - // The child was not found in the session's cache, so create the node using its path ... - child = loadNode(parent, location.getPath()); - } - return child; - } - /** * Find or create a JCR Node for the given path. This method works for the root node, too. * @@ -441,212 +398,25 @@ * @throws RepositoryException if there is a problem */ Node getNode( Path path ) throws RepositoryException, PathNotFoundException { - if (path.isRoot()) return rootNode(); - - // Start at the root and walk down the path ... - AbstractJcrNode parent = rootNode(); - AbstractJcrNode node = null; - Iterator pathIter = path.pathsFromRoot(); - while (pathIter.hasNext()) { - node = loadNode(parent, pathIter.next()); // should throw PathNotFoundException if not there - parent = node; - } - return node; + return cache.findJcrNode(null, path.relativeTo(rootPath)); } /** - * Find or create a JCR Node for the given path. This method works for the root node, too. - * - * @param parent the parent of the node, if known; null if the parent is not known - * @param path the path; may not be null - * @return the JCR node instance for the given path; never null - * @throws PathNotFoundException if the path could not be found - * @throws RepositoryException if there is a problem - */ - private AbstractJcrNode loadNode( AbstractJcrNode parent, - Path path ) throws RepositoryException, PathNotFoundException { - boolean isRoot = path.isRoot(); - if (isRoot && rootNode != null) return rootNode; - - // Get node from source and get it's UUID ... - org.jboss.dna.graph.Node graphNode = null; - try { - graphNode = graph.getNodeAt(path); - } catch (org.jboss.dna.graph.property.PathNotFoundException e) { - // If the node isn't found, throw a PathNotFoundException - throw new PathNotFoundException(JcrI18n.pathNotFound.text(path)); - } - - // Now get the DNA node's UUID ... - Location location = graphNode.getLocation(); - ValueFactories factories = executionContext.getValueFactories(); - UUID uuid = location.getUuid(); - org.jboss.dna.graph.property.Property uuidProperty = null; - if (uuid != null) { - // Check for an existing node at this UUID ... - AbstractJcrNode existing = nodesByUuid.get(uuid); - if (existing != null) return existing; - - // Considered an identification property ... - uuidProperty = location.getIdProperty(JcrLexicon.UUID); - if (uuidProperty == null) uuidProperty = location.getIdProperty(DnaLexicon.UUID); - } - if (uuidProperty == null) { - uuidProperty = graphNode.getProperty(JcrLexicon.UUID); - if (uuidProperty != null) { - // Grab the first 'good' UUID value ... - for (Object uuidValue : uuidProperty) { - try { - uuid = factories.getUuidFactory().create(uuidValue); - break; - } catch (ValueFormatException e) { - // Ignore; just continue with the next property value - } - } - } - if (uuid == null) { - // Look for the DNA UUID property ... - org.jboss.dna.graph.property.Property dnaUuidProperty = graphNode.getProperty(DnaLexicon.UUID); - if (dnaUuidProperty != null) { - // Grab the first 'good' UUID value ... - for (Object uuidValue : dnaUuidProperty) { - try { - uuid = factories.getUuidFactory().create(uuidValue); - break; - } catch (ValueFormatException e) { - // Ignore; just continue with the next property value - } - } - } - } - } - if (uuid == null) uuid = UUID.randomUUID(); - if (uuidProperty == null) uuidProperty = executionContext.getPropertyFactory().create(JcrLexicon.UUID, uuid); - - // See if there is already a JCR node object for this UUID ... - if (uuid != null && !isRoot) { - AbstractJcrNode node = getNode(uuid); - if (node != null) return node; - } - - // Either the UUID is not known, or there was no node. Either way, we have to create the node ... - if (uuid == null) uuid = UUID.randomUUID(); - - // Look for the primary type of the node ... - String primaryTypeNameString = null; - NamespaceRegistry namespaces = namespaces(); - org.jboss.dna.graph.property.Property primaryTypeProperty = graphNode.getProperty(JcrLexicon.PRIMARY_TYPE); - if (primaryTypeProperty != null && !primaryTypeProperty.isEmpty()) { - Name primaryTypeName = factories.getNameFactory().create(primaryTypeProperty.getFirstValue()); - primaryTypeNameString = primaryTypeName.getString(namespaces); - } else { - // We have to have a primary type, so use the default ... - if (defaultPrimaryType == null) { - defaultPrimaryType = executionContext.getPropertyFactory().create(JcrLexicon.PRIMARY_TYPE, - JcrNtLexicon.UNSTRUCTURED); - } - primaryTypeProperty = defaultPrimaryType; - // We have to add this property to the graph node... - graphNode.getPropertiesByName().put(primaryTypeProperty.getName(), primaryTypeProperty); - } - assert primaryTypeProperty.isEmpty() == false; - - // Look for a node definition stored on the node ... - NodeDefinition definition = null; - org.jboss.dna.graph.property.Property nodeDefnProperty = graphNode.getProperty(DnaLexicon.NODE_DEFINITON); - if (nodeDefnProperty != null && !nodeDefnProperty.isEmpty()) { - Path nodeDefnPath = factories.getPathFactory().create(nodeDefnProperty.getFirstValue()); - // Look up the node definition ... - Name nodeTypeName = nodeDefnPath.getSegment(0).getName(); - JcrNodeType nodeType = nodeTypeManager().getNodeType(nodeTypeName); - if (nodeType != null && nodeDefnPath.size() > 1) { - // Look up the definition for the child name rule (in the second segment of the relative path) ... - String childNameRule = nodeDefnPath.getSegment(1).getString(namespaces); - definition = nodeType.findBestNodeDefinitionForChild(childNameRule, primaryTypeNameString); - } - } - - AbstractJcrNode node = null; - if (isRoot) { - // The node definition should be set ... - if (definition == null) { - definition = nodeTypeManager().getRootNodeDefinition(); - assert definition != null; - } - - // Create the new node ... - node = new JcrRootNode(this, location, definition); - } else { - // Find the parent ... - if (parent == null) { - parent = (AbstractJcrNode)getNode(path.getParent()); - } - assert parent != null; - - // Find the node definition for this node ... - if (definition == null) { - // Look for the parent's node type, and look for a node definition based upon the name ... - JcrNodeType nodeType = (JcrNodeType)parent.getPrimaryNodeType(); - String childName = path.getLastSegment().getName().getString(namespaces); - definition = nodeType.findBestNodeDefinitionForChild(childName, primaryTypeNameString); - if (definition == null) { - String msg = JcrI18n.nodeDefinitionCouldNotBeDeterminedForNode.text(path, workspace.getName()); - throw new RepositorySourceException(msg); - } - } - - // Now create the node object ... - node = new JcrNode(this, parent.internalUuid(), location, definition); - } - - // Now populate the node and add to the cache ... - populateNode(node, graphNode, uuid, uuidProperty, primaryTypeProperty); - if (isRoot) rootNode = (JcrRootNode)node; - return node; - } - - AbstractJcrNode getNode( UUID uuid ) { - return nodesByUuid.get(uuid); - } - - /** * {@inheritDoc} * * @see javax.jcr.Session#getNodeByUUID(java.lang.String) */ public Node getNodeByUUID( String uuid ) throws ItemNotFoundException, RepositoryException { - Node result = null; - try { - result = nodesByUuid.get(UUID.fromString(uuid)); - } catch (IllegalArgumentException e) { - throw new ItemNotFoundException(JcrI18n.itemNotFoundWithUuid.text(uuid, workspace.getName())); - } catch (Throwable e) { - throw new RepositoryException(JcrI18n.errorWhileFindingNodeWithUuid.text(uuid, workspace.getName())); - } - if (result == null) { - throw new ItemNotFoundException(JcrI18n.itemNotFoundWithUuid.text(uuid, workspace.getName())); - } - return result; + return cache.findJcrNode(UUID.fromString(uuid)); } - JcrRootNode rootNode() throws RepositoryException { - // Return cached root node if available - if (rootNode != null) { - return rootNode; - } - Path rootPath = executionContext.getValueFactories().getPathFactory().createRootPath(); - loadNode(null, rootPath); // sets the root node - assert rootNode != null; - return rootNode; - } - /** * {@inheritDoc} * * @see javax.jcr.Session#getRootNode() */ public Node getRootNode() throws RepositoryException { - return rootNode(); + return cache.findJcrRootNode(); } /** @@ -842,187 +612,6 @@ return PropertyType.UNDEFINED; } - final void populateNode( AbstractJcrNode node, - org.jboss.dna.graph.Node graphNode, - UUID uuid, - org.jboss.dna.graph.property.Property uuidProperty, - org.jboss.dna.graph.property.Property primaryTypeProperty ) throws RepositoryException { - assert node != null; - assert graphNode != null; - assert uuid != null; - assert uuidProperty != null; - assert primaryTypeProperty != null; - - // -------------------------------------------------- - // Create JCR children for corresponding DNA children - // -------------------------------------------------- - node.setChildren(graphNode.getChildren()); - - // ------------------------------------------------------ - // Create JCR properties for corresponding DNA properties - // ------------------------------------------------------ - // First get the property type for each property, based upon the primary type and mixins ... - // The map with single-valued properties... - Map svPropertyDefinitionsByPropertyName = new HashMap(); - // ... and the map with multi-valued properties - Map mvPropertyDefinitionsByPropertyName = new HashMap(); - - - boolean referenceable = false; - - NamespaceRegistry registry = namespaces(); - ValueFactories factories = executionContext.getValueFactories(); - NameFactory nameFactory = factories.getNameFactory(); - NodeTypeManager nodeTypeManager = getWorkspace().getNodeTypeManager(); - List anyPropertyDefinitions = new LinkedList(); - // Start with the primary type ... - Name primaryTypeName = nameFactory.create(primaryTypeProperty.getFirstValue()); - String primaryTypeNameString = primaryTypeName.getString(registry); - NodeType primaryType = nodeTypeManager.getNodeType(primaryTypeNameString); - for (PropertyDefinition propertyDefn : primaryType.getPropertyDefinitions()) { - String nameString = propertyDefn.getName(); - if ("*".equals(nameString)) { - anyPropertyDefinitions.add(propertyDefn); - continue; - } - Name name = nameFactory.create(nameString); - - if (propertyDefn.isMultiple()) { - PropertyDefinition prev = mvPropertyDefinitionsByPropertyName.put(name, propertyDefn); - if (prev != null) mvPropertyDefinitionsByPropertyName.put(name, prev); // put the first one back ... - } - else { - PropertyDefinition prev = svPropertyDefinitionsByPropertyName.put(name, propertyDefn); - if (prev != null) svPropertyDefinitionsByPropertyName.put(name, prev); // put the first one back ... - } - } - // The process the mixin types ... - org.jboss.dna.graph.property.Property mixinTypesProperty = graphNode.getProperty(JcrLexicon.MIXIN_TYPES); - if (mixinTypesProperty != null && !mixinTypesProperty.isEmpty()) { - for (Object mixinTypeValue : mixinTypesProperty) { - Name mixinTypeName = nameFactory.create(mixinTypeValue); - if (!referenceable && JcrMixLexicon.REFERENCEABLE.equals(mixinTypeName)) referenceable = true; - String mixinTypeNameString = mixinTypeName.getString(registry); - NodeType mixinType = nodeTypeManager.getNodeType(mixinTypeNameString); - for (PropertyDefinition propertyDefn : mixinType.getPropertyDefinitions()) { - String nameString = propertyDefn.getName(); - if ("*".equals(nameString)) { - anyPropertyDefinitions.add(propertyDefn); - continue; - } - Name name = nameFactory.create(nameString); - if (propertyDefn.isMultiple()) { - PropertyDefinition prev = mvPropertyDefinitionsByPropertyName.put(name, propertyDefn); - if (prev != null) mvPropertyDefinitionsByPropertyName.put(name, prev); // put the first one back ... - } - else { - PropertyDefinition prev = svPropertyDefinitionsByPropertyName.put(name, propertyDefn); - if (prev != null) svPropertyDefinitionsByPropertyName.put(name, prev); // put the first one back ... - } - } - } - } - - // Now create the JCR property object wrapper around the "jcr:uuid" property ... - Map properties = new HashMap(); - if (referenceable) { - // We know that this property is single-valued - PropertyDefinition propertyDefinition = svPropertyDefinitionsByPropertyName.get(JcrLexicon.UUID); - properties.put(JcrLexicon.UUID, new JcrSingleValueProperty(node, propertyDefinition, PropertyType.STRING, - uuidProperty)); - } - - // Now create the JCR property object wrappers around the other properties ... - for (org.jboss.dna.graph.property.Property dnaProp : graphNode.getProperties()) { - Name name = dnaProp.getName(); - - // Skip the JCR and DNA UUID properties (using the EXACT Name instances on the Property) ... - if (JcrLexicon.UUID.equals(name) || DnaLexicon.UUID.equals(name)) continue; - - // Figure out the JCR property type for this property ... - PropertyDefinition propertyDefinition; - - if (dnaProp.isMultiple()) { - propertyDefinition = mvPropertyDefinitionsByPropertyName.get(name); - } - else { - propertyDefinition = svPropertyDefinitionsByPropertyName.get(name); - - // If the property has only one value, dnaProp.isMultiple() will return false, but the - // property may actually be a multi-valued property that happens to have one property set. - if (propertyDefinition == null) { - propertyDefinition = mvPropertyDefinitionsByPropertyName.get(name); - } - } - - // If no property type was found for this property, see if there is a wildcard property ... - if (propertyDefinition == null) { - for (Iterator iter = anyPropertyDefinitions.iterator(); iter.hasNext(); ) { - PropertyDefinition nextDef = iter.next(); - - // Grab the first residual definition that matches on cardinality (single-valued vs. multi-valued) - if ((nextDef.isMultiple() && dnaProp.isMultiple()) - || (!nextDef.isMultiple() && !dnaProp.isMultiple())) { - propertyDefinition = nextDef; - break; - } - } - } - - - // If there still is no property type defined ... - if (propertyDefinition == null) { - assert anyPropertyDefinitions.isEmpty(); - if (INCLUDE_PROPERTIES_NOT_ALLOWED_BY_NODE_TYPE_OR_MIXINS) { - // We can use the "nt:unstructured" property definitions for any property ... - if (anyMultiplePropertyDefinition == null) { - String unstructuredName = JcrNtLexicon.UNSTRUCTURED.getString(registry); - NodeType unstructured = nodeTypeManager.getNodeType(unstructuredName); - for (PropertyDefinition definition : unstructured.getDeclaredPropertyDefinitions()) { - if (definition.isMultiple()) { - anyMultiplePropertyDefinition = definition; - } - } - } - propertyDefinition = anyMultiplePropertyDefinition; - } - } - if (propertyDefinition == null) { - // We're supposed to skip this property (since we don't have a definition for it) ... - continue; - } - - // Figure out if this is a multi-valued property ... - boolean isMultiple = propertyDefinition.isMultiple(); - if (!isMultiple && dnaProp.isEmpty()) { - // Only multi-valued properties can have no values; so if not multi-valued, then skip ... - continue; - } - - // Figure out the property type ... - int propertyType = propertyDefinition.getRequiredType(); - if (propertyType == PropertyType.UNDEFINED) { - propertyType = jcrPropertyTypeFor(dnaProp); - } - - // Create the appropriate JCR property wrapper ... - if (isMultiple) { - properties.put(name, new JcrMultiValueProperty(node, propertyDefinition, propertyType, dnaProp)); - } else { - properties.put(name, new JcrSingleValueProperty(node, propertyDefinition, propertyType, dnaProp)); - } - } - - // Now set the properties on the node ... - node.setProperties(properties); - - // Set node's UUID, creating one if necessary - nodesByJcrUuid.put(uuid.toString(), node); - node.setInternalUuid(uuid); - // Setup node to be retrieved by DNA UUID - nodesByUuid.put(uuid, node); - } - /** * {@inheritDoc} * Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java (working copy) @@ -102,43 +102,113 @@ * * @param propertyName the name of the property for which the definition should be retrieved. Use * {@link JcrNodeType#RESIDUAL_ITEM_NAME} to retrieve the residual property definition (if any). + * @param preferMultiValued true if the property definition would prefer multiple values, or false if single-valued definition + * is preferred * @return the property definition for the given name or null if no such definition exists. * @see JcrNodeType#RESIDUAL_ITEM_NAME */ - JcrPropertyDefinition getPropertyDefinition( String propertyName ) { + JcrPropertyDefinition getPropertyDefinition( String propertyName, + boolean preferMultiValued ) { + JcrPropertyDefinition result = null; for (JcrPropertyDefinition property : propertyDefinitions) { if (propertyName.equals(property.getName())) { - return property; + result = property; + if (property.isMultiple() == preferMultiValued) return result; + // Otherwise, keep looking for a better match ... } } for (NodeType nodeType : declaredSupertypes) { - JcrPropertyDefinition definition = ((JcrNodeType)nodeType).getPropertyDefinition(propertyName); + JcrPropertyDefinition definition = ((JcrNodeType)nodeType).getPropertyDefinition(propertyName, preferMultiValued); + if (definition != null) { + if (definition.isMultiple() == preferMultiValued) return definition; + if (result == null) result = definition; + } + } + return result; // may be null + } + + /** + * Returns the property definition with the given name. This method first checks the property definitions declared within this + * type to see if any property definitions have the given name. If no matches are found, this method initiates a recursive + * depth first search up the type hierarchy to attempt to find a definition in one of the supertypes (or one the supertypes of + * the supertypes). + * + * @param propertyName the name of the property for which the definition should be retrieved. Use + * {@link JcrNodeType#RESIDUAL_ITEM_NAME} to retrieve the residual property definition (if any). + * @param preferMultiValued true if the property definition would prefer multiple values, or false if single-valued definition + * is preferred + * @return the property definition for the given name or null if no such definition exists. + * @see JcrNodeType#RESIDUAL_ITEM_NAME + */ + JcrPropertyDefinition getPropertyDefinition( Name propertyName, + boolean preferMultiValued ) { + JcrPropertyDefinition result = null; + for (JcrPropertyDefinition property : propertyDefinitions) { + if (propertyName.equals(property.getInternalName())) { + result = property; + if (property.isMultiple() == preferMultiValued) return result; + // Otherwise, keep looking for a better match ... + } + } + + for (NodeType nodeType : declaredSupertypes) { + JcrPropertyDefinition definition = ((JcrNodeType)nodeType).getPropertyDefinition(propertyName, preferMultiValued); + if (definition != null) { + if (definition.isMultiple() == preferMultiValued) return definition; + if (result == null) result = definition; + } + } + return result; // may be null + } + + /** + * Returns the child node definition with the given name. This method first checks the child node definitions declared within + * this type to see if any child node definitions have the given name. If no matches are found, this method initiates a + * recursive depth first search up the type hierarchy to attempt to find a definition in one of the supertypes (or one the + * supertypes of the supertypes). + * + * @param childDefinitionName the name of the child node definition to be retrieved, or a name containing + * {@link JcrNodeType#RESIDUAL_ITEM_NAME '*'} to retrieve the residual child node definition (if any). + * @return the child node definition with the given name or null if no such definition exists. + * @see JcrNodeType#RESIDUAL_ITEM_NAME + * @see #getChildNodeDefinition(Name) + */ + JcrNodeDefinition getChildNodeDefinition( String childDefinitionName ) { + for (JcrNodeDefinition childNode : childNodeDefinitions) { + if (childDefinitionName.equals(childNode.getName())) { + return childNode; + } + } + + for (NodeType nodeType : declaredSupertypes) { + JcrNodeDefinition definition = ((JcrNodeType)nodeType).getChildNodeDefinition(childDefinitionName); if (definition != null) return definition; } return null; } /** - * Returns the node definition for the child node with the given name. This method first checks the child node definitions - * declared within this type to see if any child node definitions have the given name. If no matches are found, this method - * initiates a recursive depth first search up the type hierarchy to attempt to find a definition in one of the supertypes (or - * one the supertypes of the supertypes). + * Returns the child node definition with the given name. This method first checks the child node definitions declared within + * this type to see if any child node definitions have the given name. If no matches are found, this method initiates a + * recursive depth first search up the type hierarchy to attempt to find a definition in one of the supertypes (or one the + * supertypes of the supertypes). * - * @param childNodeName the name of the child node for which the definition should be retrieved. Use - * {@link JcrNodeType#RESIDUAL_ITEM_NAME} to retrieve the residual child node definition (if any). + * @param childDefinitionName the name of the child node definition to be retrieved, or a name containing + * {@link JcrNodeType#RESIDUAL_ITEM_NAME '*'} to retrieve the residual child node definition (if any). * @return the child node definition with the given name or null if no such definition exists. * @see JcrNodeType#RESIDUAL_ITEM_NAME + * @see #getChildNodeDefinition(String) */ - JcrNodeDefinition getChildNodeDefinition( String childNodeName ) { + JcrNodeDefinition getChildNodeDefinition( Name childDefinitionName ) { for (JcrNodeDefinition childNode : childNodeDefinitions) { - if (childNodeName.equals(childNode.getName())) { + if (childDefinitionName.equals(childNode.name)) { return childNode; } } for (NodeType nodeType : declaredSupertypes) { - JcrNodeDefinition definition = ((JcrNodeType)nodeType).getChildNodeDefinition(childNodeName); + JcrNodeDefinition definition = ((JcrNodeType)nodeType).getChildNodeDefinition(childDefinitionName); if (definition != null) return definition; } return null; @@ -153,6 +223,7 @@ * @return the {@link NodeDefinition} that best matches the child, or null if a child with the supplied name and primary type * are not allowed given this node type */ + @Deprecated JcrNodeDefinition findBestNodeDefinitionForChild( String childName, String primaryNodeTypeName ) { // First, try to find a child node definition with the given name @@ -173,6 +244,46 @@ } /** + * Determine the best (most specific) {@link NodeDefinition} for a child with the supplied name and primary type. If the + * primary type is not supplied, then only the name is considered when finding a best match. + * + * @param childName the name of the child + * @param primaryNodeTypeName the name of the primary node type for the child + * @return the {@link NodeDefinition} that best matches the child, or null if a child with the supplied name and primary type + * are not allowed given this node type + */ + JcrNodeDefinition findBestNodeDefinitionForChild( Name childName, + Name primaryNodeTypeName ) { + // First, try to find a child node definition with the given name + JcrNodeDefinition childNode = getChildNodeDefinition(childName); + + // If there are no named definitions in the type hierarchy, try to find a residual node definition + boolean checkResidual = true; + if (childNode == null) { + childNode = getChildNodeDefinition(RESIDUAL_ITEM_NAME); + checkResidual = false; + } + + // Check if the node can be added with the named child node definition + if (childNode != null && primaryNodeTypeName != null) { + NodeType primaryNodeType = getPrimaryNodeType(primaryNodeTypeName); + if (primaryNodeType == null) return null; + if (!checkTypeAgainstDefinition(primaryNodeType, childNode)) { + if (checkResidual) { + // Find a residual child definition ... + childNode = getChildNodeDefinition(RESIDUAL_ITEM_NAME); + if (childNode != null) { + // Check the residual child definition ... + if (checkTypeAgainstDefinition(primaryNodeType, childNode)) return childNode; + } + } + return null; + } + } + return childNode; + } + + /** * {@inheritDoc} * * @see javax.jcr.nodetype.NodeType#canAddChildNode(java.lang.String) @@ -211,6 +322,10 @@ } } + protected final NodeType getPrimaryNodeType( Name primaryNodeTypeName ) { + return session.nodeTypeManager().getNodeType(primaryNodeTypeName); + } + /** * {@inheritDoc} * @@ -306,9 +421,9 @@ Value value ) { CheckArg.isNotNull(propertyName, "propertyName"); - JcrPropertyDefinition property = getPropertyDefinition(propertyName); + JcrPropertyDefinition property = getPropertyDefinition(propertyName, false); if (property == null) { - property = getPropertyDefinition(RESIDUAL_ITEM_NAME); + property = getPropertyDefinition(RESIDUAL_ITEM_NAME, false); } if (property == null) { @@ -324,12 +439,11 @@ if (value == null) { return !property.isMandatory(); } - + try { assert value instanceof JcrValue : "Illegal implementation of Value interface"; - ((JcrValue) value).asType(property.getRequiredType()); - } - catch (javax.jcr.ValueFormatException vfe) { + ((JcrValue)value).asType(property.getRequiredType()); + } catch (javax.jcr.ValueFormatException vfe) { // Cast failed return false; } @@ -345,9 +459,9 @@ Value[] values ) { CheckArg.isNotNull(propertyName, "propertyName"); - JcrPropertyDefinition property = getPropertyDefinition(propertyName); + JcrPropertyDefinition property = getPropertyDefinition(propertyName, true); if (property == null) { - property = getPropertyDefinition(RESIDUAL_ITEM_NAME); + property = getPropertyDefinition(RESIDUAL_ITEM_NAME, true); } if (property == null) { @@ -368,9 +482,8 @@ if (values[i] != null) { try { assert values[i] instanceof JcrValue : "Illegal implementation of Value interface"; - ((JcrValue) values[i]).asType(property.getRequiredType()); - } - catch (javax.jcr.ValueFormatException vfe) { + ((JcrValue)values[i]).asType(property.getRequiredType()); + } catch (javax.jcr.ValueFormatException vfe) { // Cast failed return false; } Index: dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNodeTypeSource.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNodeTypeSource.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNodeTypeSource.java (working copy) @@ -44,9 +44,9 @@ protected static final List NO_CHILD_NODES = Collections.emptyList(); protected static final List NO_PROPERTIES = Collections.emptyList(); - // Indicates that the node type has no primary item name - added for readability + /** Indicates that the node type has no primary item name - added for readability */ protected static final Name NO_PRIMARY_ITEM_NAME = null; - // Indicates that the definition should apply to all property definition or child node definitions - added for readability + /** Indicates that the definition should apply to all property definition or child node definitions - added for readability */ protected static final Name ALL_NODES = null; // Indicates whether or not the node type is a mixin - added for readability Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrDocumentViewExporter.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrDocumentViewExporter.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrDocumentViewExporter.java (working copy) @@ -57,7 +57,7 @@ class JcrDocumentViewExporter extends AbstractJcrExporter { private static final TextEncoder VALUE_ENCODER = new JcrDocumentViewExporter.JcrDocumentViewPropertyEncoder(); - + JcrDocumentViewExporter( JcrSession session ) { super(session, Collections.emptyList()); } @@ -103,7 +103,7 @@ continue; } - Name propName = ((AbstractJcrProperty)prop).getDnaProperty().getName(); + Name propName = ((AbstractJcrProperty)prop).name(); String localPropName = getPrefixedName(propName); @@ -141,16 +141,15 @@ } endElement(contentHandler, name); - + } /** - * Indicates whether the current node is an XML text node as per section 6.4.2.3 of the JCR 1.0 specification. - * XML text nodes are nodes that have the name "jcr:xmltext" and only one property (besides the mandatory - * "jcr:primaryType"). The property must have a property name of "jcr:xmlcharacters", a type of String, - * and does not have multiple values.

- * In practice, this is handled in DNA by making XML text nodes have a type of "dna:xmltext", which - * enforces these property characteristics. + * Indicates whether the current node is an XML text node as per section 6.4.2.3 of the JCR 1.0 specification. XML text nodes + * are nodes that have the name "jcr:xmltext" and only one property (besides the mandatory + * "jcr:primaryType"). The property must have a property name of "jcr:xmlcharacters", a type of + * String, and does not have multiple values.

In practice, this is handled in DNA by making XML text nodes + * have a type of "dna:xmltext", which enforces these property characteristics. * * @param node the node to test * @return whether this node is a special xml text node @@ -190,8 +189,8 @@ } /** - * Returns the XML characters for the given node. - * The node must be an XML text node, as defined in {@link #isXmlTextNode(Node)}. + * Returns the XML characters for the given node. The node must be an XML text node, as defined in + * {@link #isXmlTextNode(Node)}. * * @param node the node for which XML characters will be retrieved. * @return the xml characters for this node @@ -201,7 +200,7 @@ // ./xmltext/xmlcharacters exception (see JSR-170 Spec 6.4.2.3) assert isXmlTextNode(node); - + Property xmlCharacters = node.getProperty(getPrefixedName(JcrLexicon.XMLCHARACTERS)); assert xmlCharacters != null; @@ -221,22 +220,21 @@ /** * Special {@link TextEncoder} that implements the subset of XML name encoding suggested by section 6.4.4 of the JCR 1.0.1 - * specification. This encoder only encodes space (0x20), carriage return (0x0D), new line (0x0A), tab (0x09), and any + * specification. This encoder only encodes space (0x20), carriage return (0x0D), new line (0x0A), tab (0x09), and any * underscore characters that might otherwise suggest an encoding, as defined in {@link XmlNameEncoder}. - * */ protected static class JcrDocumentViewPropertyEncoder extends XmlNameEncoder { private static final Set MAPPED_CHARACTERS; - + static { MAPPED_CHARACTERS = new HashSet(); MAPPED_CHARACTERS.add(' '); MAPPED_CHARACTERS.add('\r'); MAPPED_CHARACTERS.add('\n'); MAPPED_CHARACTERS.add('\t'); - + } - + /** * {@inheritDoc} * Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrItemDefinition.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrItemDefinition.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrItemDefinition.java (working copy) @@ -37,7 +37,7 @@ protected final JcrSession session; - private final NodeType declaringNodeType; + protected final JcrNodeType declaringNodeType; protected final Name name; private final int onParentVersion; private final boolean autoCreated; @@ -45,7 +45,7 @@ private final boolean protectedItem; JcrItemDefinition( JcrSession session, - NodeType declaringNodeType, + JcrNodeType declaringNodeType, Name name, int onParentVersion, boolean autoCreated, @@ -54,13 +54,20 @@ super(); this.session = session; this.declaringNodeType = declaringNodeType; - this.name = name; + this.name = name != null ? name : session.getExecutionContext() + .getValueFactories() + .getNameFactory() + .create(JcrNodeType.RESIDUAL_ITEM_NAME); this.onParentVersion = onParentVersion; this.autoCreated = autoCreated; this.mandatory = mandatory; this.protectedItem = protectedItem; } + final Name getInternalName() { + return name; + } + /** * {@inheritDoc} * Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java (working copy) @@ -39,6 +39,7 @@ public static I18n inputStreamConsumed; public static I18n nonInputStreamConsumed; public static I18n pathNotFound; + public static I18n pathNotFoundRelativeTo; public static I18n permissionDenied; public static I18n repositoryMustBeConfigured; public static I18n sourceInUse; @@ -61,9 +62,19 @@ public static I18n invalidRelativePath; public static I18n invalidPathParameter; public static I18n invalidNamePattern; + public static I18n noPrimaryItemNameDefinedOnPrimaryType; + public static I18n primaryItemNameForPrimaryTypeIsNotValid; + public static I18n primaryItemDoesNotExist; public static I18n itemNotFoundWithUuid; + public static I18n itemNotFoundAtPath; + public static I18n itemNotFoundAtPathRelativeToReferenceNode; + public static I18n propertyNotFoundAtPathRelativeToReferenceNode; + public static I18n nodeNotFoundAtPathRelativeToReferenceNode; + public static I18n childNotFoundUnderNode; public static I18n errorWhileFindingNodeWithUuid; + public static I18n errorWhileFindingNodeWithPath; public static I18n nodeDefinitionCouldNotBeDeterminedForNode; + public static I18n missingNodeTypeForExistingNode; public static I18n typeNotFound; public static I18n supertypeNotFound; Index: dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrItem.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrItem.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrItem.java (working copy) @@ -26,26 +26,62 @@ import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.RepositoryException; +import javax.jcr.Session; +import org.jboss.dna.graph.ExecutionContext; +import org.jboss.dna.graph.property.Name; +import org.jboss.dna.graph.property.NamespaceRegistry; +import org.jboss.dna.graph.property.Path; /** * @author jverhaeg */ abstract class AbstractJcrItem implements Item { - protected final String getPath( String absolutePath, - String relativePath ) { - assert absolutePath != null; - assert absolutePath.length() > 0; - assert relativePath != null; - if (absolutePath.charAt(absolutePath.length() - 1) == '/') { - return absolutePath + relativePath; - } - return absolutePath + '/' + relativePath; + protected final SessionCache cache; + + protected AbstractJcrItem( SessionCache cache ) { + assert cache != null; + this.cache = cache; } /** * {@inheritDoc} * + * @see javax.jcr.Item#getSession() + */ + public Session getSession() { + return cache.session(); + } + + final JcrSession session() { + return cache.session(); + } + + final ExecutionContext context() { + return cache.context(); + } + + final Name nameFrom( String name ) { + return context().getValueFactories().getNameFactory().create(name); + } + + final Path pathFrom( String path ) { + return context().getValueFactories().getPathFactory().create(path); + } + + final Path.Segment segmentFrom( String segment ) { + return context().getValueFactories().getPathFactory().createSegment(segment); + } + + final NamespaceRegistry namespaces() { + return context().getNamespaceRegistry(); + } + + abstract Path path() throws RepositoryException; + + /** + * {@inheritDoc} + * * @return false * @see javax.jcr.Item#isModified() */ @@ -96,7 +132,7 @@ * * @see javax.jcr.Item#getAncestor(int) */ - public final Item getAncestor( int depth ) throws RepositoryException { + public Item getAncestor( int depth ) throws RepositoryException { if (depth < 0) { throw new ItemNotFoundException(JcrI18n.noNegativeDepth.text(depth)); } @@ -127,7 +163,7 @@ * @see javax.jcr.Item#getDepth() */ public int getDepth() throws RepositoryException { - return getParent().getDepth() + 1; + return path().size(); } /** Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeManager.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeManager.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeManager.java (working copy) @@ -96,7 +96,7 @@ return new JcrNodeTypeIterator(mixinNodeTypes.values()); } - final JcrNodeType getNodeType( Name nodeTypeName ) { + JcrNodeType getNodeType( Name nodeTypeName ) { JcrNodeType nodeType = primaryNodeTypes.get(nodeTypeName); if (nodeType == null) { @@ -141,4 +141,32 @@ return null; } + /** + * Get the node definition given the supplied identifier. + * + * @param definitionId the identifier of the node definition + * @return the node definition, or null if there is no such definition (or if the ID was null) + */ + JcrNodeDefinition getNodeDefinition( NodeDefinitionId definitionId ) { + if (definitionId == null) return null; + Name nodeTypeName = definitionId.getNodeTypeName(); + JcrNodeType nodeType = getNodeType(nodeTypeName); + return nodeType.getChildNodeDefinition(definitionId.getChildDefinitionName()); + } + + /** + * Get the property definition given the supplied identifier. + * + * @param definitionId the identifier of the node definition + * @param prefersMultiValued true if the property should be a multi-valued, or false if it should be single-valued + * @return the node definition, or null if there is no such definition (or if the ID was null) + */ + JcrPropertyDefinition getPropertyDefinition( PropertyDefinitionId definitionId, + boolean prefersMultiValued ) { + if (definitionId == null) return null; + Name nodeTypeName = definitionId.getNodeTypeName(); + JcrNodeType nodeType = getNodeType(nodeTypeName); + return nodeType.getPropertyDefinition(definitionId.getPropertyDefinitionName(), prefersMultiValued); + } + } Index: dna-jcr/src/main/java/org/jboss/dna/jcr/DnaBuiltinNodeTypeSource.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/DnaBuiltinNodeTypeSource.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/DnaBuiltinNodeTypeSource.java (working copy) @@ -127,18 +127,25 @@ NO_PRIMARY_ITEM_NAME, Arrays.asList(new JcrNodeDefinition[] { new JcrNodeDefinition(session, null, JcrLexicon.SYSTEM, OnParentVersionBehavior.IGNORE.getJcrValue(), true, true, - true, false, DnaLexicon.NAMESPACES, - new NodeType[] {namespaces}), - new JcrNodeDefinition(session, null, null, + true, false, DnaLexicon.SYSTEM, new NodeType[] {system}), + new JcrNodeDefinition(session, null, ALL_NODES, OnParentVersionBehavior.VERSION.getJcrValue(), false, false, - false, true, DnaLexicon.NAMESPACES, - new NodeType[] {namespaces}), + false, true, JcrNtLexicon.UNSTRUCTURED, + new NodeType[] {base}), - }), NO_PROPERTIES, NOT_MIXIN, UNORDERABLE_CHILD_NODES); + }), Arrays.asList(new JcrPropertyDefinition[] { + new JcrPropertyDefinition(session, null, ALL_NODES, + OnParentVersionBehavior.COPY.getJcrValue(), false, + false, false, NO_DEFAULT_VALUES, PropertyType.UNDEFINED, + NO_CONSTRAINTS, false), + new JcrPropertyDefinition(session, null, ALL_NODES, + OnParentVersionBehavior.COPY.getJcrValue(), false, + false, false, NO_DEFAULT_VALUES, PropertyType.UNDEFINED, + NO_CONSTRAINTS, true),}), NOT_MIXIN, + ORDERABLE_CHILD_NODES); - - primaryNodeTypes.addAll(Arrays.asList(new JcrNodeType[] {root, system, namespaces, namespace, })); - mixinNodeTypes.addAll(Arrays.asList(new JcrNodeType[] { })); + primaryNodeTypes.addAll(Arrays.asList(new JcrNodeType[] {root, system, namespaces, namespace,})); + mixinNodeTypes.addAll(Arrays.asList(new JcrNodeType[] {})); } Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSingleValueProperty.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSingleValueProperty.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSingleValueProperty.java (working copy) @@ -30,9 +30,7 @@ import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; -import javax.jcr.nodetype.PropertyDefinition; import org.jboss.dna.graph.property.Binary; -import org.jboss.dna.graph.property.Property; import org.jboss.dna.graph.property.Reference; import org.jboss.dna.graph.property.ValueFactories; @@ -41,11 +39,9 @@ */ final class JcrSingleValueProperty extends AbstractJcrProperty { - JcrSingleValueProperty( AbstractJcrNode node, - PropertyDefinition definition, - int propertyType, - Property dnaProperty ) { - super(node, definition, propertyType, dnaProperty); + JcrSingleValueProperty( SessionCache cache, + PropertyId propertyId ) { + super(cache, propertyId); } /** @@ -55,7 +51,7 @@ */ public boolean getBoolean() throws RepositoryException { try { - return getExecutionContext().getValueFactories().getBooleanFactory().create(getDnaProperty().getFirstValue()); + return context().getValueFactories().getBooleanFactory().create(property().getFirstValue()); } catch (org.jboss.dna.graph.property.ValueFormatException e) { throw new ValueFormatException(e.getMessage(), e); } @@ -68,10 +64,7 @@ */ public Calendar getDate() throws RepositoryException { try { - return getExecutionContext().getValueFactories() - .getDateFactory() - .create(getDnaProperty().getFirstValue()) - .toCalendar(); + return context().getValueFactories().getDateFactory().create(property().getFirstValue()).toCalendar(); } catch (org.jboss.dna.graph.property.ValueFormatException e) { throw new ValueFormatException(e.getMessage(), e); } @@ -84,7 +77,7 @@ */ public double getDouble() throws RepositoryException { try { - return getExecutionContext().getValueFactories().getDoubleFactory().create(getDnaProperty().getFirstValue()); + return context().getValueFactories().getDoubleFactory().create(property().getFirstValue()); } catch (org.jboss.dna.graph.property.ValueFormatException e) { throw new ValueFormatException(e.getMessage(), e); } @@ -96,7 +89,7 @@ * @see javax.jcr.Property#getLength() */ public long getLength() throws RepositoryException { - return createValue(getDnaProperty().getFirstValue()).getLength(); + return createValue(property().getFirstValue()).getLength(); } /** @@ -116,7 +109,7 @@ */ public long getLong() throws RepositoryException { try { - return getExecutionContext().getValueFactories().getLongFactory().create(getDnaProperty().getFirstValue()); + return context().getValueFactories().getLongFactory().create(property().getFirstValue()); } catch (org.jboss.dna.graph.property.ValueFormatException e) { throw new ValueFormatException(e.getMessage(), e); } @@ -129,10 +122,10 @@ */ public final Node getNode() throws RepositoryException { try { - ValueFactories factories = getExecutionContext().getValueFactories(); - Reference dnaReference = factories.getReferenceFactory().create(getDnaProperty().getFirstValue()); + ValueFactories factories = context().getValueFactories(); + Reference dnaReference = factories.getReferenceFactory().create(property().getFirstValue()); UUID uuid = factories.getUuidFactory().create(dnaReference); - return ((JcrSession)getSession()).getNode(uuid); + return cache.findJcrNode(uuid); } catch (org.jboss.dna.graph.property.ValueFormatException e) { throw new ValueFormatException(e.getMessage(), e); } @@ -145,7 +138,7 @@ */ public InputStream getStream() throws RepositoryException { try { - Binary binary = getExecutionContext().getValueFactories().getBinaryFactory().create(getDnaProperty().getFirstValue()); + Binary binary = context().getValueFactories().getBinaryFactory().create(property().getFirstValue()); return new SelfClosingInputStream(binary); } catch (org.jboss.dna.graph.property.ValueFormatException e) { throw new ValueFormatException(e.getMessage(), e); @@ -159,7 +152,7 @@ */ public String getString() throws RepositoryException { try { - return getExecutionContext().getValueFactories().getStringFactory().create(getDnaProperty().getFirstValue()); + return context().getValueFactories().getStringFactory().create(property().getFirstValue()); } catch (org.jboss.dna.graph.property.ValueFormatException e) { throw new ValueFormatException(e.getMessage(), e); } @@ -170,8 +163,8 @@ * * @see javax.jcr.Property#getValue() */ - public Value getValue() { - return createValue(getDnaProperty().getFirstValue()); + public Value getValue() throws RepositoryException { + return createValue(property().getFirstValue()); } /** Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java (working copy) @@ -27,10 +27,7 @@ import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeDefinition; import net.jcip.annotations.NotThreadSafe; -import org.jboss.dna.graph.Location; -import org.jboss.dna.graph.property.Path; /** * @author jverhaeg @@ -38,19 +35,19 @@ @NotThreadSafe final class JcrNode extends AbstractJcrNode { - private final UUID parentUuid; - - JcrNode( JcrSession session, - UUID parentUuid, - Location location, - NodeDefinition nodeDefinition ) { - super(session, location, nodeDefinition); - assert parentUuid != null; - this.parentUuid = parentUuid; + JcrNode( SessionCache cache, + UUID nodeUuid ) { + super(cache, nodeUuid); } - final Path.Segment segment() { - return location.getPath().getLastSegment(); + /** + * {@inheritDoc} + * + * @see org.jboss.dna.jcr.AbstractJcrNode#isRoot() + */ + @Override + boolean isRoot() { + return false; } /** @@ -58,8 +55,8 @@ * * @see javax.jcr.Node#getIndex() */ - public int getIndex() { - return segment().getIndex(); + public int getIndex() throws RepositoryException { + return cache.getSnsIndexOf(nodeUuid); } /** @@ -67,8 +64,8 @@ * * @see javax.jcr.Item#getName() */ - public String getName() { - return segment().getName().getString(((JcrSession)getSession()).getExecutionContext().getNamespaceRegistry()); + public String getName() throws RepositoryException { + return cache.getNameOf(nodeUuid).getString(namespaces()); } /** @@ -76,12 +73,8 @@ * * @see javax.jcr.Item#getParent() */ - public Node getParent() throws ItemNotFoundException { - Node node = session().getNode(parentUuid); - if (node == null) { - throw new ItemNotFoundException(); - } - return node; + public Node getParent() throws ItemNotFoundException, RepositoryException { + return cache.findJcrNode(nodeInfo().getParent()); } /** @@ -90,20 +83,6 @@ * @see javax.jcr.Item#getPath() */ public String getPath() throws RepositoryException { - Node parent = getParent(); - StringBuilder builder = new StringBuilder(parent.getPath()); - assert builder.length() > 0; - if (builder.charAt(builder.length() - 1) != '/') { - builder.append('/'); - } - String name = getName(); - builder.append(name); - int ndx = getIndex(); - if (ndx > 1) { - builder.append('['); - builder.append(ndx); - builder.append(']'); - } - return builder.toString(); + return cache.getPathFor(nodeUuid).getString(namespaces()); } } Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRootNode.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRootNode.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRootNode.java (working copy) @@ -23,11 +23,12 @@ */ package org.jboss.dna.jcr; +import java.util.UUID; +import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; -import javax.jcr.nodetype.NodeDefinition; +import javax.jcr.RepositoryException; import net.jcip.annotations.NotThreadSafe; -import org.jboss.dna.graph.Location; /** * @author jverhaeg @@ -35,15 +36,24 @@ @NotThreadSafe final class JcrRootNode extends AbstractJcrNode { - JcrRootNode( JcrSession session, - Location location, - NodeDefinition nodeDefinition ) { - super(session, location, nodeDefinition); + JcrRootNode( SessionCache cache, + UUID nodeUuid ) { + super(cache, nodeUuid); } /** * {@inheritDoc} * + * @see org.jboss.dna.jcr.AbstractJcrNode#isRoot() + */ + @Override + boolean isRoot() { + return true; + } + + /** + * {@inheritDoc} + * * @return 0; * @see javax.jcr.Item#getDepth() */ @@ -91,4 +101,18 @@ public String getPath() { return "/"; } + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.jcr.AbstractJcrItem#getAncestor(int) + */ + @Override + public final Item getAncestor( int depth ) throws RepositoryException { + if (depth == 0) return this; + if (depth < 0) { + throw new ItemNotFoundException(JcrI18n.noNegativeDepth.text(depth)); + } + throw new ItemNotFoundException(JcrI18n.tooDeep.text(depth)); + } } Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java (working copy) @@ -30,7 +30,6 @@ import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; -import javax.jcr.nodetype.PropertyDefinition; import net.jcip.annotations.NotThreadSafe; import org.jboss.dna.graph.property.Property; @@ -40,11 +39,9 @@ @NotThreadSafe final class JcrMultiValueProperty extends AbstractJcrProperty { - JcrMultiValueProperty( AbstractJcrNode node, - PropertyDefinition definition, - int propertyType, - Property dnaProperty ) { - super(node, definition, propertyType, dnaProperty); + JcrMultiValueProperty( SessionCache cache, + PropertyId propertyId ) { + super(cache, propertyId); } /** @@ -102,7 +99,7 @@ * @see javax.jcr.Property#getLengths() */ public long[] getLengths() throws RepositoryException { - Property dnaProperty = getDnaProperty(); + Property dnaProperty = propertyInfo().getProperty(); long[] lengths = new long[dnaProperty.size()]; Iterator iter = dnaProperty.iterator(); for (int ndx = 0; iter.hasNext(); ndx++) { @@ -161,8 +158,8 @@ * * @see javax.jcr.Property#getValues() */ - public Value[] getValues() { - Property dnaProperty = getDnaProperty(); + public Value[] getValues() throws RepositoryException { + Property dnaProperty = propertyInfo().getProperty(); Value[] values = new JcrValue[dnaProperty.size()]; Iterator iter = dnaProperty.iterator(); for (int ndx = 0; iter.hasNext(); ndx++) { Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyDefinition.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyDefinition.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyDefinition.java (working copy) @@ -24,15 +24,13 @@ package org.jboss.dna.jcr; import javax.jcr.Value; -import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; import net.jcip.annotations.Immutable; import org.jboss.dna.graph.property.Name; - /** - * DNA implementation of the {@link PropertyDefinition} interface. This implementation is immutable and has all fields initialized - * through its constructor. + * DNA implementation of the {@link PropertyDefinition} interface. This implementation is immutable and has all fields initialized + * through its constructor. */ @Immutable class JcrPropertyDefinition extends JcrItemDefinition implements PropertyDefinition { @@ -41,9 +39,10 @@ private final int requiredType; private final String[] valueConstraints; private final boolean multiple; + private PropertyDefinitionId id; JcrPropertyDefinition( JcrSession session, - NodeType declaringNodeType, + JcrNodeType declaringNodeType, Name name, int onParentVersion, boolean autoCreated, @@ -61,6 +60,18 @@ } /** + * Get the durable identifier for this property definition. + * + * @return the property definition ID; never null + */ + public PropertyDefinitionId getId() { + if (id == null) { + id = new PropertyDefinitionId(declaringNodeType.getInternalName(), name); + } + return id; + } + + /** * {@inheritDoc} * * @see javax.jcr.nodetype.PropertyDefinition#getDefaultValues() @@ -104,7 +115,7 @@ * @return a new JcrPropertyDefinition that is identical to the current object, but with the given * declaringNodeType. */ - JcrPropertyDefinition with( NodeType declaringNodeType ) { + JcrPropertyDefinition with( JcrNodeType declaringNodeType ) { return new JcrPropertyDefinition(this.session, declaringNodeType, this.name, this.getOnParentVersion(), this.isAutoCreated(), this.isMandatory(), this.isProtected(), this.getDefaultValues(), this.getRequiredType(), this.getValueConstraints(), this.isMultiple()); Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyIterator.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyIterator.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyIterator.java (working copy) @@ -31,16 +31,15 @@ import org.jboss.dna.common.util.CheckArg; /** - * @author jverhaeg */ @Immutable final class JcrPropertyIterator implements PropertyIterator { - private final Iterator iterator; + private final Iterator iterator; private int ndx; private int size; - JcrPropertyIterator( Collection properties ) { + JcrPropertyIterator( Collection properties ) { assert properties != null; iterator = properties.iterator(); size = properties.size(); @@ -88,9 +87,9 @@ * @see javax.jcr.PropertyIterator#nextProperty() */ public Property nextProperty() { - Property property = iterator.next(); + Property next = iterator.next(); ndx++; - return property; + return next; } /** Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeDefinition.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeDefinition.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeDefinition.java (working copy) @@ -47,8 +47,11 @@ /** @see NodeDefinition#getRequiredPrimaryTypes() */ private final NodeType[] requiredPrimaryTypes; + /** A durable identifier for this node definition. */ + private NodeDefinitionId id; + JcrNodeDefinition( JcrSession session, - NodeType declaringNodeType, + JcrNodeType declaringNodeType, Name name, int onParentVersion, boolean autoCreated, @@ -64,6 +67,18 @@ } /** + * Get the durable identifier for this node definition. + * + * @return the node definition ID; never null + */ + public NodeDefinitionId getId() { + if (id == null) { + id = new NodeDefinitionId(declaringNodeType.getInternalName(), name); + } + return id; + } + + /** * {@inheritDoc} * * @see javax.jcr.nodetype.NodeDefinition#allowsSameNameSiblings() @@ -117,7 +132,7 @@ * @return a new JcrNodeDefinition that is identical to the current object, but with the given * declaringNodeType. */ - JcrNodeDefinition with( NodeType declaringNodeType ) { + JcrNodeDefinition with( JcrNodeType declaringNodeType ) { return new JcrNodeDefinition(session, declaringNodeType, name, getOnParentVersion(), isAutoCreated(), isMandatory(), isProtected(), allowsSameNameSiblings(), defaultPrimaryTypeName, requiredPrimaryTypes); } Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java (revision 776) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java (working copy) @@ -148,10 +148,10 @@ this.session = new JcrSession(this.repository, this, this.context, sessionAttributes); // This must be initialized after the session - this.nodeTypeManager = new JcrNodeTypeManager(this.session, - new DnaBuiltinNodeTypeSource(this.session, - new JcrBuiltinNodeTypeSource(this.session))); - + JcrNodeTypeSource source = null; + source = new JcrBuiltinNodeTypeSource(this.session); + source = new DnaBuiltinNodeTypeSource(this.session, source); + this.nodeTypeManager = new JcrNodeTypeManager(this.session, source); } final String getSourceName() { Index: dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties =================================================================== --- dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties (revision 776) +++ dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties (working copy) @@ -28,7 +28,8 @@ defaultWorkspaceName= inputStreamConsumed = This value was already consumed as an input stream. nonInputStreamConsumed = This value was already consumed as a non-input stream. -pathNotFound = No item exists at path {0} +pathNotFound = No item exists at path {0} in workspace "{1}" +pathNotFoundRelativeTo = No item exists at path {0} relative to {1} in workspace "{2}" permissionDenied = Permission denied to perform actions "{1}" on path {0}. repositoryMustBeConfigured = DNA repositories must be configured with either a repository source factory or a repository source. sourceInUse = All sessions must end before a new repository source can be set. @@ -51,9 +52,19 @@ invalidRelativePath = "{0}" is not a valid relative path invalidPathParameter = The "{1}" parameter value "{0}" was not a valid path invalidNamePattern = The "{1}" name pattern contained the '{0}' character, which is not allowed in a name pattern -itemNotFoundWithUuid = An item with UUID "{0}" could not be found in workspace "{1}" -errorWhileFindingNodeWithUuid = Error while finding the item with UUID "{0}" in workspace "{1}" +noPrimaryItemNameDefinedOnPrimaryType = The primary type "{0}" for node "{1}" in workspace "{2}" does not define a primary item name +primaryItemNameForPrimaryTypeIsNotValid = The primary type "{0}" for node "{2}" in workspace "{3}" defines an invalid primary item name ("{1}") +primaryItemDoesNotExist = The node "{2}" in workspace "{3}" does not have an item named "{1}" as defined by its primary type "{0}" +itemNotFoundWithUuid = An item with UUID "{0}" could not be found in workspace "{1}": {2} +itemNotFoundAtPath = An item at "{0}" could not be found in workspace "{1}" +itemNotFoundAtPathRelativeToReferenceNode = An item at "{0}" relative to "{1}" could not be found in workspace "{2}" +propertyNotFoundAtPathRelativeToReferenceNode = A property at "{0}" relative to "{1}" could not be found in workspace "{2}" +nodeNotFoundAtPathRelativeToReferenceNode = A node at "{0}" relative to "{1}" could not be found in workspace "{2}" +childNotFoundUnderNode = The child "{0}" could not be found under "{1}" in workspace "{2}" +errorWhileFindingNodeWithUuid = Error while finding the node with UUID "{0}" in workspace "{1}": {2} +errorWhileFindingNodeWithPath = Error while finding the node "{0}" in workspace "{1}" nodeDefinitionCouldNotBeDeterminedForNode = Unable to determine a valid node definition for the node "{0}" in workspace "{1}" +missingNodeTypeForExistingNode = Missing primary node type "{0}" for node {1} in workspace "{2}" REP_NAME_DESC = DNA Repository REP_VENDOR_DESC = JBoss - A division of Red Hat Middleware LLC