Index: oak-core/src/main/java/org/apache/jackrabbit/oak/core/SecureNodeBuilder.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/SecureNodeBuilder.java (revision 43df81df1a54f1b6f5a9907f3fa02d9c8186397a) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/SecureNodeBuilder.java (revision ) @@ -92,6 +92,17 @@ return exists() && builder.remove(); } + + @Override + public boolean moveTo(NodeBuilder newParent, String newName) { + return exists() && builder.moveTo(newParent, newName); + } + + @Override + public boolean copyTo(NodeBuilder newParent, String newName) { + return exists() && builder.copyTo(newParent, newName); + } + @Override @CheckForNull public PropertyState getProperty(String name) { PropertyState property = builder.getProperty(name); Index: oak-core/src/test/java/org/apache/jackrabbit/oak/query/index/TraversingIndexTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/query/index/TraversingIndexTest.java (revision 43df81df1a54f1b6f5a9907f3fa02d9c8186397a) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/query/index/TraversingIndexTest.java (revision ) @@ -20,26 +20,28 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import org.apache.jackrabbit.mk.api.MicroKernel; import org.apache.jackrabbit.mk.core.MicroKernelImpl; import org.apache.jackrabbit.oak.kernel.KernelNodeState; +import org.apache.jackrabbit.oak.kernel.KernelNodeStore; import org.apache.jackrabbit.oak.spi.query.Cursor; import org.junit.Test; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; - /** * Tests the TraversingCursor. */ public class TraversingIndexTest { - private final MicroKernel mk = new MicroKernelImpl(); + private final KernelNodeStore store = new KernelNodeStore(mk); private final LoadingCache cache = CacheBuilder.newBuilder().build(new CacheLoader() { @@ -50,14 +52,10 @@ String path = key.substring(slash); // this method is strictly called _after_ the cache is initialized, // when the fields are set - return new KernelNodeState(getMicroKernel(), path, revision, getCache()); + return new KernelNodeState(store, path, revision, getCache()); } }); - + - MicroKernel getMicroKernel() { - return mk; - } - LoadingCache getCache() { return cache; } @@ -73,7 +71,7 @@ f.setPath("/"); List paths = new ArrayList(); - Cursor c = t.query(f, new KernelNodeState(mk, "/", head, cache)); + Cursor c = t.query(f, new KernelNodeState(store, "/", head, cache)); while (c.hasNext()) { paths.add(c.next().getPath()); } @@ -88,7 +86,7 @@ assertFalse(c.hasNext()); f.setPath("/nowhere"); - c = t.query(f, new KernelNodeState(mk, "/", head, cache)); + c = t.query(f, new KernelNodeState(store, "/", head, cache)); assertFalse(c.hasNext()); // endure it stays false assertFalse(c.hasNext()); Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/NodeStore.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/NodeStore.java (revision 43df81df1a54f1b6f5a9907f3fa02d9c8186397a) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/NodeStore.java (revision ) @@ -23,6 +23,8 @@ import javax.annotation.Nonnull; import org.apache.jackrabbit.oak.api.Blob; +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.spi.commit.CommitHook; /** * Storage abstraction for trees. At any given point in time the stored @@ -41,6 +43,33 @@ */ @Nonnull NodeState getRoot(); + + /** + * Merges the changes from the passed {@code builder} into + * the store. + * @param builder the builder whose changes to apply + * @param commitHook the commit hook to apply while merging changes + * @return the node state resulting from the merge. + * @throws CommitFailedException if the merge failed + */ + @Nonnull + NodeState merge(@Nonnull NodeBuilder builder, @Nonnull CommitHook commitHook) throws CommitFailedException; + + /** + * Rebase the changes in the passed {@code builder} on top of the current root state. + * @param builder the builder to rebase + * @return the node state resulting from the rebase. + */ + @Nonnull + NodeState rebase(@Nonnull NodeBuilder builder); + + /** + * Reset the passed {@code builder} by throwing away all its changes and + * setting its base state to the current root state. + * @param builder the builder to reset + * @return the node state resulting from the reset. + */ + NodeState reset(@Nonnull NodeBuilder builder); /** * Creates a new branch of the tree to which transient changes can be applied. Index: oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java (revision 43df81df1a54f1b6f5a9907f3fa02d9c8186397a) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java (revision ) @@ -24,9 +24,6 @@ import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.MISSING_NODE; import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; @@ -94,16 +91,10 @@ * path and revision. This object is only used internally and never leaves * this {@link KernelNodeState}. */ - private static final KernelNodeState NULL = new KernelNodeState( - (MicroKernel) Proxy.newProxyInstance(MicroKernel.class.getClassLoader(), - new Class[]{MicroKernel.class}, new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) - throws Throwable { - throw new UnsupportedOperationException(); - } - }), "null", "null", DUMMY_CACHE); + private static final KernelNodeState NULL = new KernelNodeState(); + private final KernelNodeStore store; + private final MicroKernel kernel; private final String path; @@ -127,20 +118,29 @@ * given {@code path} and {@code revision}. It is an error if the * underlying Microkernel does not contain such a node. * - * @param kernel the underlying MicroKernel + * @param store the underlying KernelNodeStore * @param path the path of this KernelNodeState * @param revision the revision of the node to read from the kernel. * @param cache the KernelNodeState cache */ public KernelNodeState( - MicroKernel kernel, String path, String revision, + KernelNodeStore store, String path, String revision, LoadingCache cache) { - this.kernel = checkNotNull(kernel); + this.store = store; + this.kernel = store.getKernel(); this.path = checkNotNull(path); this.revision = checkNotNull(revision); this.cache = checkNotNull(cache); } + private KernelNodeState() { + this.store = null; + this.kernel = null; + this.path = "null"; + this.revision = "null"; + this.cache = DUMMY_CACHE; + } + private void init() { boolean initialized = false; synchronized (this) { @@ -390,7 +390,12 @@ @Override public NodeBuilder builder() { + if ("/".equals(path)) { + // michid incorrect when we are on a branch!? + return new KernelRootBuilder(this, store); + } else { - return new MemoryNodeBuilder(this); + return new MemoryNodeBuilder(this); + } } /** Index: oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeBuilder.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeBuilder.java (revision ) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeBuilder.java (revision ) @@ -0,0 +1,72 @@ +package org.apache.jackrabbit.oak.kernel; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; + +/** + * This class refines move and copy operations by delegating + * them to the underlying store if possible. + * @see KernelRootBuilder + */ +public class KernelNodeBuilder extends MemoryNodeBuilder { + + private final KernelRootBuilder root; + + public KernelNodeBuilder(MemoryNodeBuilder parent, String name, KernelRootBuilder root) { + super(parent, name); + this.root = checkNotNull(root); + } + + //--------------------------------------------------< MemoryNodeBuilder >--- + + @Override + protected MemoryNodeBuilder createChildBuilder(String name) { + return new KernelNodeBuilder(this, name, root); + } + + @Override + protected void updated() { + root.updated(); + } + + /** + * If {@code newParent} is a {@link KernelNodeBuilder} this implementation + * purges all pending changes before applying the move operation. This allows the + * underlying store to better optimise move operations instead of just seeing + * them as an added and a removed node. + * If {@code newParent} is not a {@code KernelNodeBuilder} the implementation + * falls back to the super class. + */ + @Override + public boolean moveTo(NodeBuilder newParent, String newName) { + if (newParent instanceof KernelNodeBuilder) { + String source = getPath(); + String target = PathUtils.concat(((KernelNodeBuilder) newParent).getPath(), checkNotNull(newName)); + return root.move(source, target); + } else { + return super.moveTo(newParent, newName); + } + } + + /** + * If {@code newParent} is a {@link KernelNodeBuilder} this implementation + * purges all pending changes before applying the copy operation. This allows the + * underlying store to better optimise copy operations instead of just seeing + * them as an added node. + * If {@code newParent} is not a {@code KernelNodeBuilder} the implementation + * falls back to the super class. + */ + @Override + public boolean copyTo(NodeBuilder newParent, String newName) { + if (newParent instanceof KernelNodeBuilder) { + String source = getPath(); + String target = PathUtils.concat(((KernelNodeBuilder) newParent).getPath(), checkNotNull(newName)); + return root.copy(source, target); + } else { + return super.copyTo(newParent, newName); + } + } +} Index: oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeStore.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeStore.java (revision 43df81df1a54f1b6f5a9907f3fa02d9c8186397a) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeStore.java (revision ) @@ -16,6 +16,9 @@ */ package org.apache.jackrabbit.oak.kernel; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + import java.io.IOException; import java.io.InputStream; import java.util.concurrent.ExecutionException; @@ -29,18 +32,17 @@ import com.google.common.cache.Weigher; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; - import org.apache.jackrabbit.mk.api.MicroKernel; import org.apache.jackrabbit.mk.api.MicroKernelException; +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.spi.commit.CommitHook; import org.apache.jackrabbit.oak.spi.commit.EmptyObserver; import org.apache.jackrabbit.oak.spi.commit.Observer; import org.apache.jackrabbit.oak.spi.state.AbstractNodeStore; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; import org.apache.jackrabbit.oak.spi.state.NodeStoreBranch; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - /** * {@code NodeStore} implementations against {@link MicroKernel}. */ @@ -81,7 +83,7 @@ int slash = key.indexOf('/'); String revision = key.substring(0, slash); String path = key.substring(slash); - return new KernelNodeState(kernel, path, revision, cache); + return new KernelNodeState(KernelNodeStore.this, path, revision, cache); } @Override public ListenableFuture reload( @@ -126,6 +128,52 @@ observer.contentChanged(before, root); } return root; + } + + /** + * This implementation delegates to {@link KernelRootBuilder#merge(CommitHook)} + * if {@code builder} is a {@link KernelNodeBuilder} instance. Otherwise it falls + * back to the default implementation of its super class. + */ + @Override + public NodeState merge(@Nonnull NodeBuilder builder, @Nonnull CommitHook commitHook) throws CommitFailedException { + if (builder instanceof KernelRootBuilder) { + return ((KernelRootBuilder) builder).merge(commitHook); + } else { + return super.merge(builder, commitHook); + } + } + + /** + * This implementation delegates to {@link KernelRootBuilder#rebase()} if {@code builder} + * is a {@link KernelNodeBuilder} instance. Otherwise it falls back to the default + * implementation of its super class. + * @param builder the builder to rebase + * @return + */ + @Override + public NodeState rebase(@Nonnull NodeBuilder builder) { + if (builder instanceof KernelRootBuilder) { + return ((KernelRootBuilder) builder).rebase(); + } else { + return super.rebase(builder); + } + } + + /** + * This implementation delegates to {@link KernelRootBuilder#reset()} if {@code builder} + * is a {@link KernelNodeBuilder} instance. Otherwise it falls back to the default + * implementation of its super class. + * @param builder the builder to rebase + * @return + */ + @Override + public NodeState reset(@Nonnull NodeBuilder builder) { + if (builder instanceof KernelRootBuilder) { + return ((KernelRootBuilder) builder).reset(); + } else { + return super.reset(builder); + } } @Override Index: oak-parent/pom.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-parent/pom.xml (revision 43df81df1a54f1b6f5a9907f3fa02d9c8186397a) +++ oak-parent/pom.xml (revision ) @@ -34,7 +34,7 @@ pom - -Xmx512m -XX:MaxPermSize=32m -Doak.root.purgeLimit=100 + -Xmx512m -XX:MaxPermSize=32m -Dupdate.limit=100 false Index: oak-core/src/test/java/org/apache/jackrabbit/oak/core/RootImplTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/core/RootImplTest.java (revision 43df81df1a54f1b6f5a9907f3fa02d9c8186397a) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/core/RootImplTest.java (revision ) @@ -158,8 +158,8 @@ root.move("/parent", "/moved"); - assertEquals(Status.EXISTING, parent.getStatus()); - assertEquals(Status.EXISTING, n.getStatus()); + assertEquals(Status.NEW, parent.getStatus()); + assertEquals(Status.NEW, n.getStatus()); assertEquals("/moved", parent.getPath()); assertEquals("/moved/new", n.getPath()); Index: oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelRootBuilder.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelRootBuilder.java (revision ) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelRootBuilder.java (revision ) @@ -0,0 +1,122 @@ +package org.apache.jackrabbit.oak.kernel; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeBuilder; +import org.apache.jackrabbit.oak.spi.commit.CommitHook; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.spi.state.NodeStore; +import org.apache.jackrabbit.oak.spi.state.NodeStoreBranch; + +/** + * This implementation tracks the number of pending changes and purges them to + * a private branch of the underlying store if a certain threshold is met. + */ +class KernelRootBuilder extends MemoryNodeBuilder { + + /** + * Number of content updates that need to happen before the updates + * are automatically purged to the private branch. + */ + private static final int UPDATE_LIMIT = Integer.getInteger("update.limit", 1000); + + /** + * The underlying store + */ + private final NodeStore store; + + /** + * Private branch used to hold pending changes exceeding {@link #UPDATE_LIMIT} + */ + private NodeStoreBranch branch; + + /** + * Number of updated not yet persisted to the private {@link #branch} + */ + private int updates = 0; + + public KernelRootBuilder(NodeState base, NodeStore store) { + super(checkNotNull(base)); + this.store = store; + this.branch = store.branch(); // michid consider to make this lazy + } + + //--------------------------------------------------< MemoryNodeBuilder >--- + + @Override + protected MemoryNodeBuilder createChildBuilder(String name) { + return new KernelNodeBuilder(this, name, this); + } + + @Override + protected void updated() { + if (updates++ > UPDATE_LIMIT) { + purge(); + } + } + + /** + * Rebase this builder on top of the head of the underlying store + */ + NodeState rebase() { + purge(); + branch.rebase(); + NodeState head = branch.getHead(); + reset(head); + return head; + } + + /** + * Reset this builder by creating a new branch and setting the head + * state of that branch as the new base state of this builder. + */ + NodeState reset() { + branch = store.branch(); + NodeState head = branch.getHead(); + reset(head); + return head; + } + + /** + * Merge all changes tracked in this builder into the underlying store. + */ + NodeState merge(CommitHook hook) throws CommitFailedException { + purge(); + branch.merge(hook); + branch = store.branch(); // michid consider to make this lazy + return branch.getHead(); + } + + /** + * Applied all pending changes to the underlying branch and then + * move the node as a separate operation on the underlying store. + * This allows stores to optimise move operations instead of + * seeing them as an added node followed by a deleted node. + */ + boolean move(String source, String target) { + purge(); + boolean success = branch.move(source, target); + reset(branch.getHead()); + return success; + } + + /** + * Applied all pending changes to the underlying branch and then + * copy the node as a separate operation on the underlying store. + * This allows stores to optimise copy operations instead of + * seeing them as an added node. + */ + boolean copy(String source, String target) { + purge(); + boolean success = branch.copy(source, target); + reset(branch.getHead()); + return success; + } + + private void purge() { + branch.setRoot(getNodeState()); + reset(branch.getHead()); + updates = 0; + } +} Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java (revision 43df81df1a54f1b6f5a9907f3fa02d9c8186397a) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java (revision ) @@ -113,7 +113,7 @@ * @param parent parent builder * @param name name of this node */ - private MemoryNodeBuilder(MemoryNodeBuilder parent, String name) { + protected MemoryNodeBuilder(MemoryNodeBuilder parent, String name) { this.parent = parent; this.name = name; this.rootBuilder = parent.rootBuilder; @@ -279,6 +279,27 @@ } @Override + public boolean moveTo(NodeBuilder newParent, String newName) { + if (isRoot()) { + return false; + } else { + checkNotNull(newParent).setChildNode(checkNotNull(newName), getNodeState()); + remove(); + return true; + } + } + + @Override + public boolean copyTo(NodeBuilder newParent, String newName) { + if (isRoot()) { + return false; + } else { + checkNotNull(newParent).setChildNode(checkNotNull(newName), getNodeState()); + return true; + } + } + + @Override public long getPropertyCount() { return head().getCurrentNodeState().getPropertyCount(); } @@ -358,7 +379,7 @@ /** * @return path of this builder. For debugging purposes only */ - private String getPath() { + protected final String getPath() { return parent == null ? "/" : getPath(new StringBuilder()).toString(); } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/NodeBuilder.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/NodeBuilder.java (revision 43df81df1a54f1b6f5a9907f3fa02d9c8186397a) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/NodeBuilder.java (revision ) @@ -214,6 +214,22 @@ boolean remove(); /** + * Move this child to a new parent with a new name. + * @param newParent builder for the new parent. + * @param newName name of this child at the new parent + * @return {@code true} on success, {@code false} otherwise + */ + boolean moveTo(@Nonnull NodeBuilder newParent, @Nonnull String newName); + + /** + * Copy this child to a new parent with a new name. + * @param newParent builder for the new parent. + * @param newName name of this child at the new parent + * @return {@code true} on success, {@code false} otherwise + */ + boolean copyTo(@Nonnull NodeBuilder newParent, @Nonnull String newName); + + /** * Returns the current number of properties. * * @return number of properties Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/AbstractNodeStore.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/AbstractNodeStore.java (revision 43df81df1a54f1b6f5a9907f3fa02d9c8186397a) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/AbstractNodeStore.java (revision ) @@ -17,12 +17,65 @@ package org.apache.jackrabbit.oak.spi.state; +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.annotation.Nonnull; + +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.spi.commit.CommitHook; + /** * Abstract base class for {@link NodeStore} implementations. */ public abstract class AbstractNodeStore implements NodeStore { + + /** + * This default implementation is equal to applying the builder to + * a new branch and immediately merging it back. + * @param builder the builder whose changes to apply + * @param commitHook the commit hook to apply while merging changes + * @return the node state resulting from the merge. + * @throws CommitFailedException + */ + @Override + public NodeState merge(@Nonnull NodeBuilder builder, @Nonnull CommitHook commitHook) throws CommitFailedException { + checkNotNull(builder); + checkNotNull(commitHook); + NodeStoreBranch branch = branch(); + branch.setRoot(builder.getNodeState()); + return branch.merge(commitHook); + } + + /** + * This default implementation is equal to applying the differences between + * the builders base state and its head state to a fresh builder on the + * stores root state using {@link ConflictAnnotatingRebaseDiff} for resolving + * conflicts. + * @param builder the builder to rebase + * @return the node state resulting from the rebase. + */ + @Override + public NodeState rebase(@Nonnull NodeBuilder builder) { + NodeState head = checkNotNull(builder).getNodeState(); + NodeState base = builder.getBaseState(); + builder.reset(getRoot()); + head.compareAgainstBaseState(base, new ConflictAnnotatingRebaseDiff(builder)); + return builder.getNodeState(); + } + + /** + * This default implementation is equal resetting the builder to the root of + * the store and returning the resulting node state from the builder. + * @param builder the builder to reset + * @return the node state resulting from the reset. + */ + @Override + public NodeState reset(@Nonnull NodeBuilder builder) { + builder.reset(getRoot()); + return builder.getNodeState(); + } - //------------------------------------------------------------< Object >-- +//------------------------------------------------------------< Object >-- /** * Returns a string representation the head state of this node store. Index: oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java (revision 43df81df1a54f1b6f5a9907f3fa02d9c8186397a) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java (revision ) @@ -21,6 +21,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static org.apache.jackrabbit.oak.commons.PathUtils.getName; import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath; +import static org.apache.jackrabbit.oak.commons.PathUtils.isAncestor; import java.io.IOException; import java.io.InputStream; @@ -63,16 +64,10 @@ import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; import org.apache.jackrabbit.oak.spi.state.NodeStore; -import org.apache.jackrabbit.oak.spi.state.NodeStoreBranch; public class RootImpl implements Root { /** - * Number of {@link #updated} calls for which changes are kept in memory. - */ - private static final int PURGE_LIMIT = Integer.getInteger("oak.root.purgeLimit", 1000); - - /** * The underlying store to which this root belongs */ private final NodeStore store; @@ -95,14 +90,14 @@ private final TreeImpl rootTree; /** - * Current branch this root operates on + * Unsecured builder for the root tree */ - private NodeStoreBranch branch; + private final NodeBuilder builder; /** - * Unsecured builder for the root tree + * Base state of the root tree */ - private NodeBuilder builder; + private NodeState base; /** Sentinel for the next move operation to take place on the this root */ private Move lastMove = new Move(); @@ -140,10 +135,9 @@ this.securityProvider = checkNotNull(securityProvider); this.indexProvider = indexProvider; - branch = this.store.branch(); - NodeState root = branch.getHead(); - builder = root.builder(); - NodeBuilder secureBuilder = new SecureNodeBuilder(builder, getRootContext(root)); + base = store.getRoot(); + builder = base.builder(); + NodeBuilder secureBuilder = new SecureNodeBuilder(builder, getRootContext(base)); rootTree = new TreeImpl(this, secureBuilder, lastMove); } @@ -178,22 +172,27 @@ @Override public boolean move(String sourcePath, String destPath) { - if (PathUtils.isAncestor(sourcePath, destPath)) { + if (isAncestor(sourcePath, destPath)) { return false; } checkLive(); - TreeImpl destParent = rootTree.getTree(getParentPath(destPath)); - if (!destParent.exists()) { + TreeImpl source = rootTree.getTree(sourcePath); + if (!source.exists()) { return false; } - purgePendingChanges(); - boolean success = branch.move(sourcePath, destPath); - reset(); + + String newName = getName(destPath); + TreeImpl newParent = rootTree.getTree(getParentPath(destPath)); + if (!newParent.exists() || newParent.hasChild(newName)) { + return false; + } + + boolean success = source.moveTo(newParent, newName); if (success) { getTree(getParentPath(sourcePath)).updateChildOrder(); getTree(getParentPath(destPath)).updateChildOrder(); - lastMove = lastMove.setMove(sourcePath, destParent, getName(destPath)); + lastMove = lastMove.setMove(sourcePath, newParent, newName); } return success; } @@ -201,9 +200,18 @@ @Override public boolean copy(String sourcePath, String destPath) { checkLive(); - purgePendingChanges(); - boolean success = branch.copy(sourcePath, destPath); - reset(); + TreeImpl source = rootTree.getTree(sourcePath); + if (!source.exists()) { + return false; + } + + String newName = getName(destPath); + TreeImpl newParent = rootTree.getTree(getParentPath(destPath)); + if (!newParent.exists() || newParent.hasChild(newName)) { + return false; + } + + boolean success = source.copyTo(newParent, newName); if (success) { getTree(getParentPath(destPath)).updateChildOrder(); } @@ -220,9 +228,7 @@ public void rebase() { checkLive(); if (!store.getRoot().equals(getBaseState())) { - purgePendingChanges(); - branch.rebase(); - reset(); + base = store.rebase(builder); if (permissionProvider != null) { permissionProvider.refresh(); } @@ -232,8 +238,8 @@ @Override public final void refresh() { checkLive(); - branch = store.branch(); - reset(); + base = store.getRoot(); + store.reset(builder); modCount = 0; if (permissionProvider != null) { permissionProvider.refresh(); @@ -244,15 +250,14 @@ public void commit() throws CommitFailedException { checkLive(); rebase(); - purgePendingChanges(); CommitFailedException exception = Subject.doAs( getCommitSubject(), new PrivilegedAction() { @Override public CommitFailedException run() { try { - NodeState base = branch.getBase(); - NodeState newHead = branch.merge(getCommitHook()); - postHook.contentChanged(base, newHead); + NodeState oldBase = getBaseState(); + base = store.merge(builder, getCommitHook()); + postHook.contentChanged(oldBase, base); return null; } catch (CommitFailedException e) { return e; @@ -361,7 +366,7 @@ */ @Nonnull NodeState getBaseState() { - return branch.getBase(); + return base; } /** @@ -370,16 +375,12 @@ * @return secure base node state */ NodeState getSecureBase() { - NodeState root = branch.getBase(); - return new SecureNodeState(root, getRootContext(root)); + return new SecureNodeState(base, getRootContext(base)); } - // TODO better way to determine purge limit. See OAK-175 void updated() { - if (++modCount % PURGE_LIMIT == 0) { - purgePendingChanges(); + modCount++; - } + } - } //------------------------------------------------------------< private >--- @@ -418,22 +419,6 @@ return permissionProvider; } - /** - * Purge all pending changes to the underlying {@link NodeStoreBranch}. - */ - private void purgePendingChanges() { - branch.setRoot(getRootState()); - reset(); - } - - /** - * Reset the root builder to the branch's current root state - */ - private void reset() { - NodeState root = branch.getHead(); - builder.reset(root); - } - @Nonnull private PermissionProvider createPermissionProvider() { return getAcConfig().getPermissionProvider(this, subject.getPrincipals()); @@ -493,7 +478,7 @@ Move move = this; while (move.next != null) { if (move.source.equals(tree.getPathInternal())) { - tree.moveTo(move.destParent, move.destName); + tree.setParentAndName(move.destParent, move.destName); } move = move.next; } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/ReadOnlyBuilder.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/ReadOnlyBuilder.java (revision 43df81df1a54f1b6f5a9907f3fa02d9c8186397a) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/ReadOnlyBuilder.java (revision ) @@ -94,6 +94,16 @@ } @Override + public boolean moveTo(NodeBuilder newParent, String newName) { + throw unsupported(); + } + + @Override + public boolean copyTo(NodeBuilder newParent, String newName) { + throw unsupported(); + } + + @Override public long getPropertyCount() { return state.getPropertyCount(); } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/core/TreeImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/TreeImpl.java (revision 43df81df1a54f1b6f5a9907f3fa02d9c8186397a) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/TreeImpl.java (revision ) @@ -368,15 +368,27 @@ return nodeBuilder.getNodeState(); } + void setParentAndName(TreeImpl parent, String name) { + this.name = name; + this.parent = parent; + } + /** * Move this tree to the parent at {@code destParent} with the new name * {@code destName}. - * @param destParent new parent for this tree - * @param destName new name for this tree + * @param newParent new parent for this tree + * @param newName new name for this tree */ - void moveTo(TreeImpl destParent, String destName) { - name = destName; - parent = destParent; + boolean moveTo(TreeImpl newParent, String newName) { + name = newName; + parent = newParent; + // michid this falls back to MemoryNodeBuilder#moveTo if newParent is a SecureNodeState + return nodeBuilder.moveTo(newParent.nodeBuilder, newName); + } + + boolean copyTo(TreeImpl newParent, String newName) { + // michid this falls back to MemoryNodeBuilder#copyTo if newParent is a SecureNodeState + return nodeBuilder.copyTo(newParent.nodeBuilder, newName); } /**