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..1757759 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; /** @@ -69,4 +70,29 @@ public interface ContentSession extends Closeable { */ @Nonnull Root getLatestRoot(); + + /** + * Return the root associated with the given revision. + * + * @param revision The revision that will be used to retrieve the associated + * root. + * @return The root associated with the given revision, or {@code null} if + * the revision is not valid anymore. + */ + @CheckForNull + Root getRoot(Revision revision); + + /** + * Return the root associated with the given string representation of a + * revision. This is the same as invoking {@link #getRoot(Revision)} with + * the value returned by {@link org.apache.jackrabbit.oak.api.Revision#asString()}. + * + * @param revision The string representation of the revision that will be + * used to retrieve the associated root. + * @return The root associated with the given revision, or {@code null} if + * the revision is not valid anymore. + */ + @CheckForNull + Root getRoot(String revision); + } \ No newline at end of file diff --git oak-core/src/main/java/org/apache/jackrabbit/oak/api/Revision.java oak-core/src/main/java/org/apache/jackrabbit/oak/api/Revision.java new file mode 100644 index 0000000..d75f307 --- /dev/null +++ oak-core/src/main/java/org/apache/jackrabbit/oak/api/Revision.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.jackrabbit.oak.api; + +/** + * Represents a handle to a specific state of the repository. It can be used to + * retrieve the root and to access the state this revision represents. + */ +public interface Revision { + + /** + * Returns a string representation of this revision. How this revision is + * represented is a detail of this implementation. The string should be + * treated as an opaque value. + * + * @return String representation of this revision. + */ + String asString(); + +} diff --git oak-core/src/main/java/org/apache/jackrabbit/oak/api/Root.java oak-core/src/main/java/org/apache/jackrabbit/oak/api/Root.java index d8bec37..0c0d342 100644 --- oak-core/src/main/java/org/apache/jackrabbit/oak/api/Root.java +++ oak-core/src/main/java/org/apache/jackrabbit/oak/api/Root.java @@ -176,4 +176,13 @@ public interface Root { @Nonnull ContentSession getContentSession(); + /** + * Return a revision that can be used to retrieve non-transient state + * represented by this root. + * + * @return A revision associated to the non-transient state of this root. + */ + @Nonnull + Revision getRevision(); + } diff --git oak-core/src/main/java/org/apache/jackrabbit/oak/core/CheckpointRevision.java oak-core/src/main/java/org/apache/jackrabbit/oak/core/CheckpointRevision.java new file mode 100644 index 0000000..dc65580 --- /dev/null +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/CheckpointRevision.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.jackrabbit.oak.core; + +import org.apache.jackrabbit.oak.api.Revision; + +/** + * A revision implementation based on a {@link org.apache.jackrabbit.oak.spi.state.NodeStore} + * checkpoint. + */ +class CheckpointRevision implements Revision { + + final String checkpoint; + + public CheckpointRevision(String checkpoint) { + this.checkpoint = checkpoint; + } + + @Override + public String asString() { + return checkpoint; + } + +} 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..98d40c4 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 @@ -27,12 +27,14 @@ import javax.security.auth.login.LoginException; import org.apache.jackrabbit.oak.api.AuthInfo; import org.apache.jackrabbit.oak.api.ContentSession; +import org.apache.jackrabbit.oak.api.Revision; import org.apache.jackrabbit.oak.api.Root; import org.apache.jackrabbit.oak.query.QueryEngineSettings; 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 +129,28 @@ class ContentSessionImpl implements ContentSession { return sessionName; } + @Override + public Root getRoot(Revision revision) { + checkLive(); + + if (revision instanceof CheckpointRevision) { + return getRoot(revision.asString()); + } + + throw new IllegalArgumentException("invalid revision"); + } + + @Override + public Root getRoot(String revision) { + NodeState state = store.retrieve(revision); + + if (state == null) { + return null; + } + + return new MutableRoot(store, state, hook, workspaceName, + loginContext.getSubject(), securityProvider, + queryEngineSettings, indexProvider, this); + } + } diff --git oak-core/src/main/java/org/apache/jackrabbit/oak/core/ImmutableRoot.java oak-core/src/main/java/org/apache/jackrabbit/oak/core/ImmutableRoot.java index 8c9e0a8..5460511 100644 --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/ImmutableRoot.java +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/ImmutableRoot.java @@ -29,6 +29,7 @@ import javax.annotation.Nonnull; import org.apache.jackrabbit.oak.api.Blob; import org.apache.jackrabbit.oak.api.ContentSession; import org.apache.jackrabbit.oak.api.QueryEngine; +import org.apache.jackrabbit.oak.api.Revision; import org.apache.jackrabbit.oak.api.Root; import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexProvider; @@ -148,4 +149,9 @@ public final class ImmutableRoot implements Root { throw new UnsupportedOperationException(); } + @Override + public Revision getRevision() { + throw new UnsupportedOperationException(); + } + } 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..b0e49a9 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 @@ -38,6 +38,7 @@ import org.apache.jackrabbit.oak.api.Blob; import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.api.ContentSession; import org.apache.jackrabbit.oak.api.QueryEngine; +import org.apache.jackrabbit.oak.api.Revision; import org.apache.jackrabbit.oak.api.Root; import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.index.diffindex.UUIDDiffIndexProviderWrapper; @@ -65,6 +66,8 @@ import org.apache.jackrabbit.oak.spi.state.NodeStore; class MutableRoot implements Root { + private static final long DEFAULT_CHECKPOINT_LIFETIME = 1000; + /** * The underlying store to which this root belongs */ @@ -148,6 +151,19 @@ class MutableRoot implements Root { QueryEngineSettings queryEngineSettings, QueryIndexProvider indexProvider, ContentSessionImpl session) { + this(store, store.getRoot(), hook, workspaceName, subject, + securityProvider, queryEngineSettings, indexProvider, session); + } + + MutableRoot(NodeStore store, + NodeState state, + 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 +173,7 @@ class MutableRoot implements Root { this.indexProvider = indexProvider; this.session = checkNotNull(session); - builder = store.getRoot().builder(); + builder = state.builder(); secureBuilder = new SecureNodeBuilder(builder, permissionProvider, getAcContext()); rootTree = new MutableTree(this, secureBuilder, lastMove); } @@ -322,6 +338,11 @@ class MutableRoot implements Root { return store.getBlob(reference); } + @Override + public Revision getRevision() { + return new CheckpointRevision(store.checkpoint(DEFAULT_CHECKPOINT_LIFETIME)); + } + //-----------------------------------------------------------< internal >--- /** diff --git oak-core/src/test/java/org/apache/jackrabbit/oak/api/RevisionTest.java oak-core/src/test/java/org/apache/jackrabbit/oak/api/RevisionTest.java new file mode 100644 index 0000000..2664a85 --- /dev/null +++ oak-core/src/test/java/org/apache/jackrabbit/oak/api/RevisionTest.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.jackrabbit.oak.api; + +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; + +public class RevisionTest extends OakBaseTest { + + private ContentSession session; + + public RevisionTest(NodeStoreFixture fixture) { + super(fixture); + } + + @Before + public void setUp() { + this.session = createContentSession(); + } + + @After + public void tearDown() { + this.session = null; + } + + @Test + public void testValidRevision() throws Exception { + Assert.assertNotNull(session.getLatestRoot().getRevision()); + } + + @Test + public void testValidRevisionString() throws Exception { + Assert.assertNotNull(session.getLatestRoot().getRevision().asString()); + } + + @Test + public void testReadRootFromRevision() throws Exception { + Root root = session.getLatestRoot(); + root.getTree("/").addChild("test"); + root.commit(); + + Revision revision = root.getRevision(); + + ContentSession anotherSession = createContentSession(); + Root anotherRoot = anotherSession.getRoot(revision); + Assert.assertTrue(anotherRoot.getTree("/test").exists()); + } + + @Test + public void testReadRootFromRevisionString() throws Exception { + Root root = session.getLatestRoot(); + root.getTree("/").addChild("test"); + root.commit(); + + String revision = root.getRevision().asString(); + + ContentSession anotherSession = createContentSession(); + Root anotherRoot = anotherSession.getRoot(revision); + Assert.assertTrue(anotherRoot.getTree("/test").exists()); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidRevision() throws Exception { + session.getRoot(new Revision() { + + @Override + public String asString() { + return null; + } + + }); + } + +} 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..98b4976 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 @@ -31,6 +31,7 @@ import org.apache.jackrabbit.oak.api.Blob; import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.api.ContentSession; import org.apache.jackrabbit.oak.api.QueryEngine; +import org.apache.jackrabbit.oak.api.Revision; import org.apache.jackrabbit.oak.api.Root; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.plugins.tree.impl.NodeBuilderTree; @@ -131,7 +132,23 @@ class UpgradeRoot implements Root { public void close() throws IOException { // nothing to do } + + @Override + public Root getRoot(Revision revision) { + throw new UnsupportedOperationException(); + } + + @Override + public Root getRoot(String revision) { + throw new UnsupportedOperationException(); + } + }; } + @Override + public Revision getRevision() { + throw new UnsupportedOperationException(); + } + }