diff --git oak-core/src/main/java/org/apache/jackrabbit/oak/api/ContentSession.java oak-core/src/main/java/org/apache/jackrabbit/oak/api/ContentSession.java index 2ce1a93..ac24e85 100644 --- oak-core/src/main/java/org/apache/jackrabbit/oak/api/ContentSession.java +++ oak-core/src/main/java/org/apache/jackrabbit/oak/api/ContentSession.java @@ -17,6 +17,7 @@ package org.apache.jackrabbit.oak.api; import java.io.Closeable; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; /** @@ -64,9 +65,39 @@ public interface ContentSession extends Closeable { * call.
* Please note this method is possibly expensive because it internally reads * from the backend to detect if there were any changes (from any session). - * + * * @return the current head root */ @Nonnull Root getLatestRoot(); + + /** + * Create a checkpoint to reference the current state of the repository. The + * checkpoint will be valid for the specified number of milliseconds. + * + * @param lifetime The number of milliseconds the checkpoint has to remain + * valid. + * @return A string representing the checkpoint. The content of the string + * is an implementation detail. + */ + @Nonnull + String createCheckpoint(long lifetime); + + /** + * Read a checkpoint that was previously created. + * + * @param checkpoint A string representing the checkpoint to create. + * @return The root associated with the checkpoint. If the checkpoint is + * invalid or outdated, {@code null} is returned. + */ + @CheckForNull + Root readCheckpoint(@Nonnull String checkpoint); + + /** + * Release a checkpoint that was previously created. + * + * @param checkpoint A string representing the checkpoint to release. + */ + void releaseCheckpoint(@Nonnull String checkpoint); + } \ No newline at end of file diff --git oak-core/src/main/java/org/apache/jackrabbit/oak/core/ContentSessionImpl.java oak-core/src/main/java/org/apache/jackrabbit/oak/core/ContentSessionImpl.java index 709c737..b904f89 100644 --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/ContentSessionImpl.java +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/ContentSessionImpl.java @@ -33,6 +33,7 @@ import org.apache.jackrabbit.oak.spi.commit.CommitHook; import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider; import org.apache.jackrabbit.oak.spi.security.SecurityProvider; import org.apache.jackrabbit.oak.spi.security.authentication.LoginContext; +import org.apache.jackrabbit.oak.spi.state.NodeState; import org.apache.jackrabbit.oak.spi.state.NodeStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -127,4 +128,33 @@ class ContentSessionImpl implements ContentSession { return sessionName; } + @Override + public String createCheckpoint(long lifetime) { + checkLive(); + + return store.checkpoint(lifetime); + } + + @Override + public Root readCheckpoint(String checkpoint) { + checkLive(); + + NodeState state = store.retrieve(checkpoint); + + if (state == null) { + return null; + } + + return new MutableRoot(store, state, hook, workspaceName, + loginContext.getSubject(), securityProvider, + queryEngineSettings, indexProvider, this); + } + + @Override + public void releaseCheckpoint(String checkpoint) { + checkLive(); + + store.release(checkpoint); + } + } diff --git oak-core/src/main/java/org/apache/jackrabbit/oak/core/MutableRoot.java oak-core/src/main/java/org/apache/jackrabbit/oak/core/MutableRoot.java index 8f27c4a..81a5961 100644 --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/MutableRoot.java +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/MutableRoot.java @@ -77,7 +77,7 @@ class MutableRoot implements Root { private final Subject subject; private final SecurityProvider securityProvider; - + private final QueryEngineSettings queryEngineSettings; private final QueryIndexProvider indexProvider; @@ -131,9 +131,34 @@ class MutableRoot implements Root { }; /** - * New instance bases on a given {@link NodeStore} and a workspace + * New instance bases on a given {@link NodeStore} and a workspace. The + * latest state of the root node is used. + * + * @param store node store + * @param hook the commit hook + * @param workspaceName name of the workspace + * @param subject the subject. + * @param securityProvider the security configuration. + * @param indexProvider the query index provider. + */ + MutableRoot(NodeStore store, + CommitHook hook, + String workspaceName, + Subject subject, + SecurityProvider securityProvider, + QueryEngineSettings queryEngineSettings, + QueryIndexProvider indexProvider, + ContentSessionImpl session) { + this(store, store.getRoot(), hook, workspaceName, subject, + securityProvider, queryEngineSettings, indexProvider, session); + } + + /** + * New instance bases on a given {@link NodeStore}, the state of the root + * node, and a workspace. * * @param store node store + * @param root node state of the root node * @param hook the commit hook * @param workspaceName name of the workspace * @param subject the subject. @@ -141,13 +166,14 @@ class MutableRoot implements Root { * @param indexProvider the query index provider. */ MutableRoot(NodeStore store, - CommitHook hook, - String workspaceName, - Subject subject, - SecurityProvider securityProvider, - QueryEngineSettings queryEngineSettings, - QueryIndexProvider indexProvider, - ContentSessionImpl session) { + NodeState root, + CommitHook hook, + String workspaceName, + Subject subject, + SecurityProvider securityProvider, + QueryEngineSettings queryEngineSettings, + QueryIndexProvider indexProvider, + ContentSessionImpl session) { this.store = checkNotNull(store); this.hook = checkNotNull(hook); this.workspaceName = checkNotNull(workspaceName); @@ -157,7 +183,7 @@ class MutableRoot implements Root { this.indexProvider = indexProvider; this.session = checkNotNull(session); - builder = store.getRoot().builder(); + builder = root.builder(); secureBuilder = new SecureNodeBuilder(builder, permissionProvider, getAcContext()); rootTree = new MutableTree(this, secureBuilder, lastMove); } diff --git oak-core/src/test/java/org/apache/jackrabbit/oak/api/ContentSessionTest.java oak-core/src/test/java/org/apache/jackrabbit/oak/api/ContentSessionTest.java index f6ed5dd..688b7b5 100644 --- oak-core/src/test/java/org/apache/jackrabbit/oak/api/ContentSessionTest.java +++ oak-core/src/test/java/org/apache/jackrabbit/oak/api/ContentSessionTest.java @@ -26,6 +26,7 @@ import javax.security.auth.login.LoginException; import org.apache.jackrabbit.oak.NodeStoreFixture; import org.apache.jackrabbit.oak.OakBaseTest; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -70,4 +71,72 @@ public class ContentSessionTest extends OakBaseTest { session.close(); tree.getChild("any"); } + + @Test + public void testCreateCheckpoint() throws Exception { + ContentSession session = repository.login(null, null); + String checkpoint = session.createCheckpoint(500); + Assert.assertNotNull(checkpoint); + } + + @Test(expected = IllegalStateException.class) + public void testCreateCheckpointFromClosedSession() throws Exception { + ContentSession session = repository.login(null, null); + session.close(); + session.createCheckpoint(500); + } + + @Test + public void testAccessCheckpoint() throws Exception { + ContentSession session = repository.login(null, null); + + Root root = session.getLatestRoot(); + root.getTree("/").addChild("test"); + root.commit(); + + String checkpoint = session.createCheckpoint(500); + + Root checkpointRoot = session.readCheckpoint(checkpoint); + Assert.assertTrue(checkpointRoot.getTree("/test").exists()); + } + + @Test(expected = IllegalStateException.class) + public void testAccessCheckpointFromClosedSession() throws Exception { + ContentSession session = repository.login(null, null); + String checkpoint = session.createCheckpoint(500); + session.close(); + session.readCheckpoint(checkpoint); + } + + @Test + public void testAccessCheckpointFromDifferentSession() throws Exception { + ContentSession session = repository.login(null, null); + + Root root = session.getLatestRoot(); + root.getTree("/").addChild("test"); + root.commit(); + + String checkpoint = session.createCheckpoint(500); + + ContentSession another = repository.login(null, null); + Root checkpointRoot = another.readCheckpoint(checkpoint); + Assert.assertTrue(checkpointRoot.getTree("/test").exists()); + } + + @Test + public void testClearCheckpoint() throws Exception { + ContentSession session = repository.login(null, null); + String checkpoint = session.createCheckpoint(500); + session.releaseCheckpoint(checkpoint); + Assert.assertNull(session.readCheckpoint(checkpoint)); + } + + @Test(expected = IllegalStateException.class) + public void testClearCheckpointFromClosedSession() throws Exception { + ContentSession session = repository.login(null, null); + String checkpoint = session.createCheckpoint(500); + session.close(); + session.releaseCheckpoint(checkpoint); + } + } diff --git oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/UpgradeRoot.java oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/UpgradeRoot.java index f0293b7..e6f5699 100644 --- oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/UpgradeRoot.java +++ oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/UpgradeRoot.java @@ -131,6 +131,22 @@ class UpgradeRoot implements Root { public void close() throws IOException { // nothing to do } + + @Nonnull + @Override + public String createCheckpoint(long lifetime) { + throw new UnsupportedOperationException(); + } + + @Override + public Root readCheckpoint(@Nonnull String checkpoint) { + throw new UnsupportedOperationException(); + } + + @Override + public void releaseCheckpoint(@Nonnull String checkpoint) { + throw new UnsupportedOperationException(); + } }; }