/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.jcr.cache.document; import static org.hamcrest.core.Is.is; 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.fail; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.modeshape.jcr.AbstractSchematicDbTest; import org.modeshape.jcr.ExecutionContext; import org.modeshape.jcr.JcrLexicon; import org.modeshape.jcr.ModeShapeLexicon; import org.modeshape.jcr.cache.CachedNode; import org.modeshape.jcr.cache.ChildReference; import org.modeshape.jcr.cache.ChildReferences; import org.modeshape.jcr.cache.MutableCachedNode; import org.modeshape.jcr.cache.NodeCache; import org.modeshape.jcr.cache.NodeKey; import org.modeshape.jcr.cache.NodeNotFoundException; import org.modeshape.jcr.cache.PathNotFoundException; import org.modeshape.jcr.cache.SessionCache; import org.modeshape.jcr.value.Name; import org.modeshape.jcr.value.Path; import org.modeshape.jcr.value.Path.Segment; import org.modeshape.jcr.value.Property; import org.modeshape.jcr.value.PropertyFactory; import org.modeshape.jcr.value.PropertyType; import org.modeshape.schematic.document.Document; import org.modeshape.schematic.document.Json; /** * Abstract base class for tests that operate against a NodeCache. */ public abstract class AbstractNodeCacheTest extends AbstractSchematicDbTest { protected static final String ROOT_UUID = "cafebabe-cafe-babe-cafe-babecafebabe"; protected static final NodeKey ROOT_KEY_WS1 = new NodeKey("source1works1-" + ROOT_UUID); protected static final NodeKey ROOT_KEY_WS2 = new NodeKey("source1works1-" + ROOT_UUID); protected NodeCache cache; protected ExecutionContext context; private boolean print; @Before @Override public void beforeEach() { super.beforeEach(); print = false; context = new ExecutionContext(); cache = createCache(); } @After @Override public void afterEach() { shutdownCache(cache); super.afterEach(); } protected boolean print() { return print; } protected void print( boolean onOrOff ) { print = onOrOff; } protected abstract NodeCache createCache(); protected void shutdownCache( NodeCache cache ) { cache.clear(); } protected Name name( String name ) { return context.getValueFactories().getNameFactory().create(name); } protected Path path( String path ) { return context.getValueFactories().getPathFactory().create(path); } protected Segment segment( String segment ) { return context.getValueFactories().getPathFactory().createSegment(segment); } protected Segment segment( String name, int index ) { return context.getValueFactories().getPathFactory().createSegment(name, index); } protected Segment segment( Name name, int index ) { return context.getValueFactories().getPathFactory().createSegment(name, index); } protected String string( Object value ) { return context.getValueFactories().getStringFactory().create(value); } protected float millis( long nanos ) { return (float)(nanos / 1000000.0D); } protected void print( String msg ) { if (print) System.out.println(msg); } protected void print( Document doc ) { if (print) { try { Json.writePretty(doc, System.out); System.out.flush(); } catch (IOException err) { throw new AssertionError(err); } } } protected void print( Document doc, boolean deep ) { if (print) { try { Json.writePretty(doc, System.out); System.out.flush(); if (deep) { Document info = doc.getDocument(DocumentTranslator.CHILDREN_INFO); if (info != null) { String nextBlockKey = info.getString(DocumentTranslator.NEXT_BLOCK); if (nextBlockKey != null) { Document next = schematicDb.get(nextBlockKey); if (next != null) { print(next, true); } } } } } catch (IOException err) { throw new AssertionError(err); } } } protected void print( Object object ) { if (print) System.out.println(object); } protected PropertyFactory propertyFactory() { return context.getPropertyFactory(); } protected Property property( Name name, Object... values ) { return propertyFactory().create(name, values); } protected Property property( Name name, Iterable<Object> values ) { return propertyFactory().create(name, values); } protected Property property( Name name, Iterator<Object> values ) { return propertyFactory().create(name, values); } protected Property property( Name name, PropertyType type, Object... values ) { return propertyFactory().create(name, type, values); } protected Property property( Name name, PropertyType type, Iterable<Object> values ) { return propertyFactory().create(name, type, values); } protected Property property( Name name, PropertyType type, Iterator<Object> values ) { return propertyFactory().create(name, type, values); } protected Property property( Name name, Path pathValue ) { return propertyFactory().create(name, pathValue); } protected Property property( String name, Object... values ) { return propertyFactory().create(name(name), values); } protected Property property( String name, Iterable<Object> values ) { return propertyFactory().create(name(name), values); } protected Property property( String name, Iterator<Object> values ) { return propertyFactory().create(name(name), values); } protected Property property( String name, PropertyType type, Object... values ) { return propertyFactory().create(name(name), type, values); } protected Property property( String name, PropertyType type, Iterable<Object> values ) { return propertyFactory().create(name(name), type, values); } protected Property property( String name, PropertyType type, Iterator<Object> values ) { return propertyFactory().create(name(name), type, values); } protected Property property( String name, Path pathValue ) { return propertyFactory().create(name(name), pathValue); } protected CacheCheck check( NodeCache cache ) { return new CacheCheck(cache); } public class CacheCheck { private final NodeCache cache; public CacheCheck( NodeCache cache ) { this.cache = cache; } public NodeKey newNodeKey() { return ((SessionCache)cache).createNodeKey(); } public CachedNode rootNode() { return node(cache.getRootKey()); } public CachedNode node( Path path ) { CachedNode node = rootNode(); for (Segment segment : path) { ChildReferences children = node.getChildReferences(cache); ChildReference child = children.getChild(segment); if (child == null) { throw new PathNotFoundException(path, node.getKey(), node.getPath(cache)); } NodeKey childKey = child.getKey(); CachedNode childNode = cache.getNode(childKey); if (childNode == null) { throw new PathNotFoundException(path, node.getKey(), node.getPath(cache)); } node = childNode; } assertThat(node, is(notNullValue())); return node; } public CachedNode node( String path ) { return node(path(path)); } public CachedNode node( NodeKey key, String path ) { return node(key, path(path)); } public CachedNode node( NodeKey key, Path path ) { CachedNode byKey = node(key); CachedNode byPath = node(path); assertThat(byKey, is(byPath)); return byKey; } public CachedNode node( NodeKey key ) { CachedNode node = cache.getNode(key); if (node == null) { throw new NodeNotFoundException(key); } return node; } public MutableCachedNode mutableNode( NodeKey key, String path ) { return mutableNode(key, path(path)); } public MutableCachedNode mutableNode( NodeKey key, Path path ) { CachedNode byKey = node(key); CachedNode byPath = node(path); assertThat(byKey, is(byPath)); return mutableNode(byKey); } public MutableCachedNode mutableNode( CachedNode node ) { if (node instanceof MutableCachedNode) return (MutableCachedNode)node; return ((SessionCache)cache).mutable(node.getKey()); } public MutableCachedNode mutableNode( String path ) { return mutableNode(path(path)); } public MutableCachedNode mutableNode( Path path ) { CachedNode node = node(path); return mutableNode(node); } public void noNode( String path ) { noNode(path(path)); } public void noNode( Path path ) { try { node(path); fail("Found node at \"" + string(path) + "\" when it was not expected"); } catch (PathNotFoundException e) { // expected ... } } public Property property( String path, Property expectedProperty ) { return property(node(path).getKey(), expectedProperty); } public Property property( Path path, Property expectedProperty ) { return property(node(path).getKey(), expectedProperty); } public Property property( CachedNode node, Property expectedProperty ) { return property(node.getKey(), expectedProperty); } public Property property( NodeKey key, Property expectedProperty ) { CachedNode node = node(key); Property actual = node.getProperty(expectedProperty.getName(), cache); assertThat(actual, is(notNullValue())); assertThat(actual, is(expectedProperty)); return actual; } public void noProperty( String path, Name propertyName ) { noProperty(node(path).getKey(), propertyName); } public void noProperty( Path path, Name propertyName ) { noProperty(node(path).getKey(), propertyName); } public void noProperty( CachedNode node, Name propertyName ) { noProperty(node.getKey(), propertyName); } public void noProperty( NodeKey key, Name propertyName ) { CachedNode node = node(key); Property actual = node.getProperty(propertyName, cache); assertThat(actual, is(nullValue())); } public void noProperty( String path, String propertyName ) { noProperty(node(path).getKey(), propertyName); } public void noProperty( Path path, String propertyName ) { noProperty(node(path).getKey(), propertyName); } public void noProperty( CachedNode node, String propertyName ) { noProperty(node.getKey(), propertyName); } public void noProperty( NodeKey key, String propertyName ) { noProperty(key, name(propertyName)); } public void children( CachedNode node, String... childSegments ) { children(node.getKey(), childSegments); } public void children( NodeKey key, String... childSegments ) { CachedNode node = node(key); List<Segment> segments = new ArrayList<Segment>(); for (String childSegment : childSegments) { segments.add(segment(childSegment)); } Iterator<Segment> expectedIter = segments.iterator(); for (ChildReference childRef : node.getChildReferences(cache)) { Segment actual = childRef.getSegment(); if (!expectedIter.hasNext()) { fail("Found child \"" + string(actual) + "\" but expected no more children"); } Segment expected = expectedIter.next(); if (!actual.equals(expected)) { Path path = node.getPath(cache); String msg = "Expected \"" + string(expected) + "\" but found \"" + string(actual) + "\" in children of \"" + string(path) + "\""; assertThat(msg, actual, is(expected)); } } if (expectedIter.hasNext()) { Path path = node.getPath(cache); StringBuilder str = new StringBuilder("Found extra children of \"" + string(path) + "\": "); str.append('"'); str.append(string(expectedIter.next())); str.append('"'); while (expectedIter.hasNext()) { str.append("\", \""); str.append(string(expectedIter.next())); str.append('"'); } fail(str.toString()); } } } /** * The name of the JSON document which has a single "data" array field with Document instances for each node. * * @return the string name of the resource file accessible via the class loader (e.g., "data/simple.json"); may not be null */ protected String resourceNameForWorkspaceContentDocument() { return "data/simple.json"; } @Test public void shouldLoadSimpleData() { assertThat(schematicDb.get(ROOT_KEY_WS1.toString()), is(notNullValue())); assertThat(schematicDb.get("source1system-jcrsystem"), is(notNullValue())); assertThat(schematicDb.get("source1system-jcrnamespaces"), is(notNullValue())); assertThat(schematicDb.get("source1works1-childA"), is(notNullValue())); assertThat(schematicDb.get("source1works1-childB"), is(notNullValue())); assertThat(schematicDb.get("source1works1-childC"), is(notNullValue())); assertThat(schematicDb.get(ROOT_KEY_WS2.toString()), is(notNullValue())); assertThat(schematicDb.get("source1works2-childX"), is(notNullValue())); } @Test public void shouldLoadRootNodeIntoCache() { CachedNode node = cache.getNode(cache.getRootKey()); assertThat(node, is(notNullValue())); assertThat(node.getKey(), is(cache.getRootKey())); assertThat(node.getPath(cache).isRoot(), is(true)); assertThat(node.getName(cache).getLocalName().length(), is(0)); assertThat(node.getName(cache).getNamespaceUri().length(), is(0)); // Check the properties ... assertThat(node.getProperty(JcrLexicon.UUID, cache).getFirstValue().toString(), is(node.getKey().getIdentifier())); assertThat(node.getProperty(JcrLexicon.PRIMARY_TYPE, cache).getFirstValue(), is((Object)ModeShapeLexicon.ROOT)); // Check the child references ... ChildReferences refs = node.getChildReferences(cache); assertThat(refs, is(notNullValue())); assertThat(refs.size(), is(3L)); Iterator<ChildReference> iter = refs.iterator(); ChildReference system = refs.getChild(JcrLexicon.SYSTEM); ChildReference childA = refs.getChild(name("childA")); ChildReference childB = refs.getChild(name("childB")); assertThat(system.getKey().toString(), is("source1system-jcrsystem")); assertThat(childA.getKey().toString(), is("source1works1-childA")); assertThat(childB.getKey().toString(), is("source1works1-childB")); assertThat(system.getName(), is(JcrLexicon.SYSTEM)); assertThat(childA.getName(), is(name("childA"))); assertThat(childB.getName(), is(name("childB"))); assertThat(childA.getSnsIndex(), is(1)); assertThat(childB.getSnsIndex(), is(1)); assertThat(iter.next(), is(system)); assertThat(iter.next(), is(childA)); assertThat(iter.next(), is(childB)); assertThat(iter.hasNext(), is(false)); } @Test public void shouldNavigateFromRootToSystemNode() { CachedNode node = cache.getNode(cache.getRootKey()); ChildReference systemRef = node.getChildReferences(cache).getChild(JcrLexicon.SYSTEM); // print = true; long nanos = System.nanoTime(); CachedNode system = cache.getNode(systemRef); print("Time (load): " + millis(Math.abs(System.nanoTime() - nanos)) + " ms"); for (int i = 0; i != 10; ++i) { cache.clear(); nanos = System.nanoTime(); system.getKey(); system.getPath(cache); system.getName(cache); system.getProperty(JcrLexicon.UUID, cache); system.getProperty(JcrLexicon.PRIMARY_TYPE, cache); print("Time (read): " + millis(Math.abs(System.nanoTime() - nanos)) + " ms"); } nanos = System.nanoTime(); system.getKey(); system.getPath(cache); system.getName(cache); system.getProperty(JcrLexicon.UUID, cache); system.getProperty(JcrLexicon.PRIMARY_TYPE, cache); print("Time (read): " + millis(Math.abs(System.nanoTime() - nanos)) + " ms"); assertThat(system, is(notNullValue())); assertThat(system.getKey(), is(systemRef.getKey())); assertThat(system.getPath(cache).isRoot(), is(false)); assertThat(system.getName(cache), is(JcrLexicon.SYSTEM)); assertThat(system.getProperty(JcrLexicon.UUID, cache).getFirstValue().toString(), is("56b3feae-3def-44f7-a433-586413f312e4")); assertThat(system.getProperty(JcrLexicon.PRIMARY_TYPE, cache).getFirstValue(), is((Object)ModeShapeLexicon.SYSTEM)); // Check the child references ... ChildReferences refs = system.getChildReferences(cache); assertThat(refs, is(notNullValue())); assertThat(refs.size(), is(1L)); Iterator<ChildReference> iter = refs.iterator(); ChildReference namespaces = refs.getChild(ModeShapeLexicon.NAMESPACES); assertThat(namespaces.getKey().toString(), is("source1system-jcrnamespaces")); assertThat(namespaces.getName(), is(ModeShapeLexicon.NAMESPACES)); assertThat(iter.next(), is(namespaces)); assertThat(iter.hasNext(), is(false)); nanos = System.nanoTime(); CachedNode namespacesNode = cache.getNode(namespaces); print("Time (load): " + millis(Math.abs(System.nanoTime() - nanos)) + " ms"); nanos = System.nanoTime(); assertThat(namespacesNode.getPath(cache), is(path("/jcr:system/mode:namespaces"))); assertThat(namespacesNode.getChildReferences(cache).isEmpty(), is(true)); print("Time (read): " + millis(Math.abs(System.nanoTime() - nanos)) + " ms"); } @Test public void shouldLoadChildrenReferencesWhenBackedByMultipleBlocks() { CachedNode root = cache.getNode(cache.getRootKey()); CachedNode childB = cache.getNode(root.getChildReferences(cache).getChild(name("childB"))); ChildReferences refs = childB.getChildReferences(cache); assertThat(refs, is(notNullValue())); assertThat(refs.size(), is(2L)); Iterator<ChildReference> iter = refs.iterator(); ChildReference childC = refs.getChild(name("childC")); ChildReference childD = refs.getChild(name("childD")); assertThat(childC.getKey().toString(), is("source1works1-childC")); assertThat(childD.getKey().toString(), is("source1works1-childD")); assertThat(iter.next(), is(childC)); // from first block assertThat(iter.next(), is(childD)); // from second block assertThat(iter.hasNext(), is(false)); } @Test public void shouldGetNodesByPath() { CacheCheck check = check(cache); check.rootNode(); check.node("/jcr:system"); check.node("/childA"); check.node("/childB"); check.node("/childB/childC"); check.node("/childB/childD"); } }