/* * 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.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.modeshape.common.statistic.Stopwatch; import org.modeshape.jcr.ExecutionContext; import org.modeshape.jcr.RepositoryEnvironment; import org.modeshape.jcr.cache.CachedNode; import org.modeshape.jcr.cache.MutableCachedNode; import org.modeshape.jcr.cache.NodeKey; import org.modeshape.jcr.cache.SessionCache; /** * Tests that operate against a {@link WritableSessionCache}. Each test method starts with a clean slate of content */ public class WritableSessionCacheTest extends AbstractSessionCacheTest { private DocumentOptimizer optimizer; @Before @Override public void beforeEach() { super.beforeEach(); this.optimizer = new DocumentOptimizer(workspaceCache.documentStore()); } @Override protected SessionCache createSessionCache( ExecutionContext context, WorkspaceCache cache, TransactionalWorkspaceCaches txWsCaches, RepositoryEnvironment repositoryEnvironment ) { return new WritableSessionCache(context, workspaceCache, txWsCaches, repositoryEnvironment); } @Test public void shouldWarmUpSystem() { } @Test public void shouldAllowSessionToCreateAndAccessToNewPropertyOnExistingNodeBeforeSave() { // Make sure the property does not exist ... check(cache).noProperty("/childB", "p1"); // Set property on existing node ... MutableCachedNode nodeB = check(cache).mutableNode("/childB"); nodeB.setProperty(session(), property("p1", "value1")); check(cache).property(nodeB, property("p1", "value1")); // Make sure the other session doesn't see the new property ... check(session2).noProperty("/childB", "p1"); } @Test public void shouldAllowSessionToCreateAndAccessNewChildNodeOnExistingNodeBeforeSave() { // Make sure the property does not exist ... check(cache).noNode("/childB/newChild"); print(false); // Set property on existing node ... MutableCachedNode nodeB = check(session1).mutableNode("/childB"); NodeKey newKey = session1.createNodeKeyWithIdentifier("newChild"); long nanos = System.nanoTime(); MutableCachedNode newChild = nodeB.createChild(session(), newKey, name("newChild"), property("p1a", 344), property("p2", false)); print("Time (createChild): " + millis(Math.abs(System.nanoTime() - nanos)) + " ms"); assertThat(newChild.getPath(session1), is(path("/childB/newChild"))); check(session1).children(nodeB, "childC", "childD", "newChild"); check(session1).property("/childB/newChild", property("p1a", 344)); check(session1).property("/childB/newChild", property("p2", false)); // Make sure the other session doesn't see the new child ... check(session2).children(nodeB.getKey(), "childC", "childD"); print(false); nanos = System.nanoTime(); session1.save(); print(false); print("Time (save): " + millis(Math.abs(System.nanoTime() - nanos)) + " ms"); // Both sessions should see all 3 children ... check(session1).children(nodeB, "childC", "childD", "newChild"); check(session2).children(nodeB, "childC", "childD", "newChild"); check(session2).property("/childB/newChild", property("p1a", 344)); check(session2).property("/childB/newChild", property("p2", false)); } @Test public void shouldAllowSessionToCreateManyChildrenWithSameNameAndThenSave() { // Make sure the property does not exist ... check(cache).noNode("/childB/newChild"); print(false); // Set property on existing node ... MutableCachedNode nodeB = check(session1).mutableNode("/childB"); Stopwatch create = new Stopwatch(); Stopwatch total = new Stopwatch(); Stopwatch save = new Stopwatch(); total.start(); for (int i = 0; i != 1000; ++i) { create.start(); NodeKey newKey = session1.createNodeKey(); nodeB.createChild(session(), newKey, name("newChild"), property("p1a", 344), property("p2", false)); create.stop(); } // And save ... save.start(); session1.save(); save.stop(); total.stop(); // Find node B again after the save ... nodeB = check(session1).mutableNode("/childB"); print(true); print("Number of children: " + nodeB.getChildReferences(session1).size()); print("Time (create): " + create.getSimpleStatistics()); print("Time (save): " + save.getSimpleStatistics()); print("Time (total): " + total.getTotalDuration()); session1.clear(); total.reset(); total.start(); nodeB.getChildReferences(session1).getChild(name("newChild"), 9450); total.stop(); print("Time (getchild#9450): " + total.getTotalDuration()); session1.clear(); total.reset(); total.start(); nodeB.getChildReferences(session1).getChild(name("newChild"), 10); total.stop(); print("Time (getchild#10): " + total.getTotalDuration()); } @Test public void shouldAllowSessionToCreateChildrenWithSameNameWithMultipleSaves() throws Exception { // Make sure the property does not exist ... check(cache).noNode("/childB/newChild"); print(false); // Set property on existing node ... MutableCachedNode nodeB = check(session1).mutableNode("/childB"); NodeKey key = nodeB.getKey(); Stopwatch create = new Stopwatch(); Stopwatch total = new Stopwatch(); Stopwatch save = new Stopwatch(); Stopwatch opt = new Stopwatch(); runInTransaction(() -> optimizer.optimizeChildrenBlocks(key, null, 1000, 500)); // will merge two into a single block ... print(true); print("Creating nodes ..."); total.start(); for (int i = 0; i != 10000; ++i) { create.start(); NodeKey newKey = key.withId("child" + i); // NodeKey newKey = session1.createNodeKey(); nodeB.createChild(session1, newKey, name("newChild"), property("p1a", 344), property("p2", false)); create.stop(); if (i != 0 && i % 1000 == 0) { print(false); print("Saving..."); // print(false); save.start(); session1.save(); save.stop(); print(false); print("Optimizing..."); print(false); opt.start(); runInTransaction(() ->optimizer.optimizeChildrenBlocks(key, null, 1000, 500)); opt.stop(); // Find node B again after the save ... nodeB = check(session1).mutableNode("/childB"); } } total.stop(); print(true); print("Time (create): " + create.getSimpleStatistics()); print("Time (save): " + save.getSimpleStatistics()); print("Time (optimize): " + opt.getTotalDuration()); print("Time (total): " + total.getTotalDuration()); session1.clear(); total.reset(); total.start(); nodeB.getChildReferences(session1).getChild(name("newChild"), 9450); total.stop(); print("Time (getchild#9450): " + total.getTotalDuration()); session1.clear(); total.reset(); total.start(); nodeB.getChildReferences(session1).getChild(name("newChild"), 10); total.stop(); print("Time (getchild#10): " + total.getTotalDuration()); } @Ignore( "Usually ignored because of memory requirements" ) @Test public void shouldAllowSessionToCreate100KChildrenWithSameNameWithMultipleSaves() throws Exception { // Make sure the property does not exist ... check(cache).noNode("/childB/newChild"); print(false); // Set property on existing node ... MutableCachedNode nodeB = check(session1).mutableNode("/childB"); NodeKey key = nodeB.getKey(); Stopwatch create = new Stopwatch(); Stopwatch total = new Stopwatch(); Stopwatch save = new Stopwatch(); Stopwatch opt = new Stopwatch(); runInTransaction(() -> optimizer.optimizeChildrenBlocks(key, null, 1000, 500)); // will merge two into a single block ... print(true); print("Creating nodes ..."); total.start(); for (int i = 0; i != 100000; ++i) { create.start(); NodeKey newKey = key.withId("child" + i); // NodeKey newKey = session1.createNodeKey(); nodeB.createChild(session1, newKey, name("newChild"), property("p1a", 344), property("p2", false)); create.stop(); if (i != 0 && i % 1000 == 0) { print(false); print("Saving..."); // print(false); save.start(); session1.save(); save.stop(); print(false); print("Optimizing..."); print(false); opt.start(); runInTransaction(() -> optimizer.optimizeChildrenBlocks(key, null, 1000, 500)); // will split into blocks ...) opt.stop(); // Find node B again after the save ... nodeB = check(session1).mutableNode("/childB"); } } total.stop(); print(true); print("Time (create): " + create.getSimpleStatistics()); print("Time (save): " + save.getSimpleStatistics()); print("Time (optimize): " + opt.getTotalDuration()); print("Time (total): " + total.getTotalDuration()); session1.clear(); total.reset(); total.start(); nodeB.getChildReferences(session1).getChild(name("newChild"), 49450); total.stop(); print("Time (getchild#49450): " + total.getTotalDuration()); session1.clear(); total.reset(); total.start(); nodeB.getChildReferences(session1).getChild(name("newChild"), 10); total.stop(); print("Time (getchild#10): " + total.getTotalDuration()); } @Test public void shouldAllowTransientlyRenamingChildNode() { MutableCachedNode root = session1.mutable(session1.getRootKey()); MutableCachedNode node = root.createChild(session(), newKey("node"), name("node"), property("p1", "value")); NodeKey childAKey = node.createChild(session(), newKey("x-childA"), name("childA"), property("p1", "value A")).getKey(); NodeKey childBKey = node.createChild(session(), newKey("x-childB"), name("childB"), property("p1", "value B")).getKey(); NodeKey childCKey = node.createChild(session(), newKey("x-childC"), name("childC"), property("p1", "value C")).getKey(); session1.save(); // Check the children ... node = check(session1).mutableNode(node.getKey(), "/node"); check(session1).node(childAKey, "/node/childA"); check(session1).node(childBKey, "/node/childB"); check(session1).node(childCKey, "/node/childC"); check(session1).children(node.getKey(), "childA", "childB", "childC"); // Now transiently rename child b ... node.renameChild(session1, childBKey, name("childD")); // Check that the session uses the new name ... CachedNode renamed = session1.getNode(childBKey); assertThat(renamed.getSegment(session1), is(segment("childD"))); check(session1).node("/node"); check(session1).node("/node/childA"); check(session1).node("/node/childC"); check(session1).node("/node/childD"); check(session1).noNode("/node/childB"); check(session1).children(node.getKey(), "childA", "childD", "childC"); } @Test public void shouldAllowAccessingRenamedChildNodeAfterPersisting() { MutableCachedNode root = session1.mutable(session1.getRootKey()); MutableCachedNode node = root.createChild(session(), newKey("node"), name("node"), property("p1", "value")); NodeKey childAKey = node.createChild(session(), newKey("x-childA"), name("childA"), property("p1", "value A")).getKey(); NodeKey childBKey = node.createChild(session(), newKey("x-childB"), name("childB"), property("p1", "value B")).getKey(); NodeKey childCKey = node.createChild(session(), newKey("x-childC"), name("childC"), property("p1", "value C")).getKey(); session1.save(); // Check the children ... node = check(session1).mutableNode(node.getKey(), "/node"); check(session1).node(childAKey, "/node/childA"); check(session1).node(childBKey, "/node/childB"); check(session1).node(childCKey, "/node/childC"); check(session1).children(node.getKey(), "childA", "childB", "childC"); // Now transiently rename child b ... node.renameChild(session1, childBKey, name("childD")); // Now save ... session1.save(); check(session1).node("/node"); check(session1).node("/node/childA"); check(session1).node("/node/childC"); check(session1).node("/node/childD"); check(session1).noNode("/node/childB"); check(session1).children(node.getKey(), "childA", "childD", "childC"); } @Test public void shouldReturnAllTransientNodeKeys() { NodeKey rootKey = session1.getRootKey(); MutableCachedNode root = session1.mutable(rootKey); NodeKey childAKey = root.createChild(session(), newKey("x-childA"), name("childA"), property("p1", "value A")).getKey(); NodeKey childBKey = root.createChild(session(), newKey("x-childB"), name("childB"), property("p1", "value B")).getKey(); Set<NodeKey> transientNodeKeys = session1.getChangedNodeKeys(); assertEquals(new HashSet<NodeKey>(Arrays.asList(rootKey, childAKey, childBKey)), transientNodeKeys); } @Test public void shouldReturnTransientKeysAtOrBelowNode() { NodeKey rootKey = session1.getRootKey(); MutableCachedNode root = session1.mutable(rootKey); // root/childA MutableCachedNode childA = root.createChild(session(), newKey("x-childA"), name("childA"), property("p1", "value A")); // root/childA/childB MutableCachedNode childB = childA.createChild(session(), newKey("x-childB"), name("childB"), property("p1", "value B")); // root/childC MutableCachedNode childC = root.createChild(session(), newKey("x-childC"), name("childC"), property("p1", "value C")); assertEquals(new HashSet<NodeKey>(Arrays.asList(childA.getKey(), childB.getKey())), session1.getChangedNodeKeysAtOrBelow(childA)); assertEquals(new HashSet<NodeKey>(Arrays.asList(rootKey, childA.getKey(), childB.getKey(), childC.getKey())), session1.getChangedNodeKeysAtOrBelow(root)); assertEquals(new HashSet<NodeKey>(Arrays.asList(childC.getKey())), session1.getChangedNodeKeysAtOrBelow(childC)); } @Test public void shouldReturnTransientKeysAtOrBelowNodeWithRemovedChild() { NodeKey rootKey = session1.getRootKey(); MutableCachedNode root = session1.mutable(rootKey); SessionCache sessionCache = session(); NodeKey childKey = newKey("x-childA"); MutableCachedNode child = root.createChild(sessionCache, childKey, name("childA"), property("p1", "value A")); session1.destroy(child.getKey()); assertEquals(new HashSet<NodeKey>(Arrays.asList(rootKey, childKey)), session1.getChangedNodeKeysAtOrBelow(root)); } }