Author: catholicon
Date: Tue Apr 12 16:07:02 2016
New Revision: 1738823
URL: http://svn.apache.org/viewvc?rev=1738823&view=rev
Log:
OAK-4184: DocumentNodeStore and DocumentMK.Builder should allow read-only mode
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/ReadOnlyDocumentStoreWrapperFactory.java
(with props)
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/ReadOnlyDocumentStoreWrapperTest.java
(with props)
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java?rev=1738823&r1=1738822&r2=1738823&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java
Tue Apr 12 16:07:02 2016
@@ -355,6 +355,40 @@ public class ClusterNodeInfo {
}
/**
+ * Create a dummy cluster node info instance to be utilized for read only
access to underlying store.
+ * @param store
+ * @return the cluster node info
+ */
+ public static ClusterNodeInfo getReadOnlyInstance(DocumentStore store) {
+ return new ClusterNodeInfo(0, store, MACHINE_ID, WORKING_DIR,
ClusterNodeState.ACTIVE,
+ RecoverLockState.NONE, null, true) {
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public long getLeaseTime() {
+ return Long.MAX_VALUE;
+ }
+
+ @Override
+ public void performLeaseCheck() {
+ }
+
+ @Override
+ public boolean renewLease() {
+ return false;
+ }
+
+ @Override
+ public void setInfo(Map<String, String> info) {}
+
+ @Override
+ public void setLeaseFailureHandler(LeaseFailureHandler
leaseFailureHandler) {}
+ };
+ }
+
+ /**
* Create a cluster node info instance for the store, with the
*
* @param store the document store (for the lease)
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java?rev=1738823&r1=1738822&r2=1738823&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
Tue Apr 12 16:07:02 2016
@@ -511,6 +511,7 @@ public class DocumentMK {
private boolean timing;
private boolean logging;
private boolean leaseCheck = true; // OAK-2739 is enabled by default
also for non-osgi
+ private boolean isReadOnlyMode = false;
private Weigher<CacheValue, CacheValue> weigher = new
EmpiricalWeigher();
private long memoryCacheSize = DEFAULT_MEMORY_CACHE_SIZE;
private int nodeCachePercentage = DEFAULT_NODE_CACHE_PERCENTAGE;
@@ -686,6 +687,15 @@ public class DocumentMK {
return leaseCheck;
}
+ public Builder setReadOnlyMode() {
+ this.isReadOnlyMode = true;
+ return this;
+ }
+
+ public boolean getReadOnlyMode() {
+ return isReadOnlyMode;
+ }
+
public Builder setLeaseFailureHandler(LeaseFailureHandler
leaseFailureHandler) {
this.leaseFailureHandler = leaseFailureHandler;
return this;
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java?rev=1738823&r1=1738822&r2=1738823&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
Tue Apr 12 16:07:02 2016
@@ -87,6 +87,7 @@ import org.apache.jackrabbit.oak.plugins
import org.apache.jackrabbit.oak.plugins.document.cache.CacheInvalidationStats;
import
org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCache;
import
org.apache.jackrabbit.oak.plugins.document.persistentCache.broadcast.DynamicBroadcastConfig;
+import
org.apache.jackrabbit.oak.plugins.document.util.ReadOnlyDocumentStoreWrapperFactory;
import org.apache.jackrabbit.oak.spi.blob.BlobStore;
import org.apache.jackrabbit.oak.commons.json.JsopStream;
import org.apache.jackrabbit.oak.commons.json.JsopWriter;
@@ -398,6 +399,8 @@ public final class DocumentNodeStore
private final DocumentNodeStoreMBean mbean;
+ private final boolean readOnlyMode;
+
public DocumentNodeStore(DocumentMK.Builder builder) {
this.blobStore = builder.getBlobStore();
if (builder.isUseSimpleRevision()) {
@@ -410,13 +413,23 @@ public final class DocumentNodeStore
if (builder.getLogging()) {
s = new LoggingDocumentStoreWrapper(s);
}
+ if (builder.getReadOnlyMode()) {
+ s = ReadOnlyDocumentStoreWrapperFactory.getInstance(s);
+ readOnlyMode = true;
+ } else {
+ readOnlyMode = false;
+ }
this.changes = Collection.JOURNAL.newDocument(s);
this.executor = builder.getExecutor();
this.clock = builder.getClock();
int cid = builder.getClusterId();
cid = Integer.getInteger("oak.documentMK.clusterId", cid);
- clusterNodeInfo = ClusterNodeInfo.getInstance(s, cid);
+ if (readOnlyMode) {
+ clusterNodeInfo = ClusterNodeInfo.getReadOnlyInstance(s);
+ } else {
+ clusterNodeInfo = ClusterNodeInfo.getInstance(s, cid);
+ }
// TODO we should ensure revisions generated from now on
// are never "older" than revisions already in the repository for
// this cluster id
@@ -426,6 +439,7 @@ public final class DocumentNodeStore
s = new LeaseCheckDocumentStoreWrapper(s, clusterNodeInfo);
clusterNodeInfo.setLeaseFailureHandler(builder.getLeaseFailureHandler());
}
+
this.store = s;
this.clusterId = cid;
this.branches = new UnmergedBranches();
@@ -484,12 +498,16 @@ public final class DocumentNodeStore
throw new IllegalStateException("Root document does not
exist");
}
} else {
- checkLastRevRecovery();
+ if (!readOnlyMode) {
+ checkLastRevRecovery();
+ }
initializeRootState(rootDoc);
// check if _lastRev for our clusterId exists
if (!rootDoc.getLastRev().containsKey(clusterId)) {
unsavedLastRevisions.put("/",
getRoot().getRevision().getRevision(clusterId));
- backgroundWrite();
+ if (!readOnlyMode) {
+ backgroundWrite();
+ }
}
}
@@ -513,7 +531,9 @@ public final class DocumentNodeStore
backgroundUpdateThread.setDaemon(true);
backgroundReadThread.start();
- backgroundUpdateThread.start();
+ if (!readOnlyMode) {
+ backgroundUpdateThread.start();
+ }
leaseUpdateThread = new Thread(new BackgroundLeaseUpdate(this,
isDisposed),
"DocumentNodeStore lease update thread " + threadNamePostfix);
@@ -522,10 +542,12 @@ public final class DocumentNodeStore
// has higher likelihood of succeeding than other threads
// on a very busy machine - so as to prevent lease timeout.
leaseUpdateThread.setPriority(Thread.MAX_PRIORITY);
- leaseUpdateThread.start();
+ if (!readOnlyMode) {
+ leaseUpdateThread.start();
+ }
PersistentCache pc = builder.getPersistentCache();
- if (pc != null) {
+ if (!readOnlyMode && pc != null) {
DynamicBroadcastConfig broadcastConfig = new
DocumentBroadcastConfig(this);
pc.setBroadcastConfig(broadcastConfig);
}
@@ -567,16 +589,18 @@ public final class DocumentNodeStore
// do a final round of background operations after
// the background thread stopped
- try{
- internalRunBackgroundUpdateOperations();
- } catch(AssertionError ae) {
- // OAK-3250 : when a lease check fails, subsequent modifying
requests
- // to the DocumentStore will throw an AssertionError. Since as a
result
- // of a failing lease check a bundle.stop is done and thus a
dispose of the
- // DocumentNodeStore happens, it is very likely that in that case
- // you run into an AssertionError. We should still continue with
disposing
- // though - thus catching and logging..
- LOG.error("dispose: an AssertionError happened during dispose's
last background ops: "+ae, ae);
+ if (!readOnlyMode) {
+ try {
+ internalRunBackgroundUpdateOperations();
+ } catch (AssertionError ae) {
+ // OAK-3250 : when a lease check fails, subsequent modifying
requests
+ // to the DocumentStore will throw an AssertionError. Since as
a result
+ // of a failing lease check a bundle.stop is done and thus a
dispose of the
+ // DocumentNodeStore happens, it is very likely that in that
case
+ // you run into an AssertionError. We should still continue
with disposing
+ // though - thus catching and logging..
+ LOG.error("dispose: an AssertionError happened during
dispose's last background ops: " + ae, ae);
+ }
}
try {
@@ -604,7 +628,7 @@ public final class DocumentNodeStore
}
private String getClusterNodeInfoDisplayString() {
- return clusterNodeInfo.toString().replaceAll("[\r\n\t]", " ").trim();
+ return (readOnlyMode?"readOnly:true, ":"") +
clusterNodeInfo.toString().replaceAll("[\r\n\t]", " ").trim();
}
void setRoot(@Nonnull RevisionVector newHead) {
@@ -1654,7 +1678,7 @@ public final class DocumentNodeStore
/** Note: made package-protected for testing purpose, would otherwise be
private **/
void runBackgroundUpdateOperations() {
- if (isDisposed.get()) {
+ if (readOnlyMode || isDisposed.get()) {
return;
}
try {
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/ReadOnlyDocumentStoreWrapperFactory.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/ReadOnlyDocumentStoreWrapperFactory.java?rev=1738823&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/ReadOnlyDocumentStoreWrapperFactory.java
(added)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/ReadOnlyDocumentStoreWrapperFactory.java
Tue Apr 12 16:07:02 2016
@@ -0,0 +1,46 @@
+/*
+ * 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.plugins.document.util;
+
+import com.google.common.collect.Sets;
+import org.apache.jackrabbit.oak.plugins.document.DocumentStore;
+
+import javax.annotation.Nonnull;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Set;
+
+public class ReadOnlyDocumentStoreWrapperFactory {
+ private static final Set<String> unsupportedMethods = Sets.newHashSet(
+ "remove", "create", "update", "createOrUpdate", "findAndUpdate");
+ public static DocumentStore getInstance(@Nonnull final DocumentStore
delegate) {
+ return
(DocumentStore)Proxy.newProxyInstance(DocumentStore.class.getClassLoader(),
+ new Class[]{DocumentStore.class},
+ new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[]
args) throws Throwable {
+ if (unsupportedMethods.contains(method.getName())) {
+ throw new
UnsupportedOperationException(String.format("Method - %s. Params: %s",
+ method.getName(), Arrays.toString(args)));
+ }
+ return method.invoke(delegate, args);
+ }
+ });
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/ReadOnlyDocumentStoreWrapperFactory.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/ReadOnlyDocumentStoreWrapperTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/ReadOnlyDocumentStoreWrapperTest.java?rev=1738823&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/ReadOnlyDocumentStoreWrapperTest.java
(added)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/ReadOnlyDocumentStoreWrapperTest.java
Tue Apr 12 16:07:02 2016
@@ -0,0 +1,195 @@
+/*
+ * 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.plugins.document.util;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.jackrabbit.oak.plugins.document.Collection;
+import org.apache.jackrabbit.oak.plugins.document.Document;
+import org.apache.jackrabbit.oak.plugins.document.DocumentMKBuilderProvider;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.DocumentStore;
+import org.apache.jackrabbit.oak.plugins.document.UpdateOp;
+import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class ReadOnlyDocumentStoreWrapperTest {
+ @Rule
+ public DocumentMKBuilderProvider builderProvider = new
DocumentMKBuilderProvider();
+
+ @Test
+ public void testPassthrough() throws NoSuchMethodException,
InvocationTargetException, IllegalAccessException {
+ final List<String> disallowedMethods = Lists.newArrayList(
+ "create", "update", "remove", "createOrUpdate",
"findAndUpdate");
+ InvocationHandler handler = new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
+ String methodName = method.getName();
+
+ if (disallowedMethods.contains(methodName)) {
+ Assert.fail(String.format("Invalid passthrough of method
(%s) with params %s", method, Arrays.toString(args)));
+ }
+
+ if ("determineServerTimeDifferenceMillis".equals(methodName)) {
+ return new Long(0);
+ } else {
+ return null;
+ }
+ }
+ };
+ DocumentStore proxyStore =
(DocumentStore)Proxy.newProxyInstance(DocumentStore.class.getClassLoader(),
+ new Class[]{DocumentStore.class},
+ handler);
+
+ DocumentStore readOnlyStore =
ReadOnlyDocumentStoreWrapperFactory.getInstance(proxyStore);
+
+ Collection<? extends Document> []collections = new Collection[] {
+ Collection.CLUSTER_NODES, Collection.JOURNAL,
Collection.NODES, Collection.SETTINGS
+ };
+ for (Collection collection : collections) {
+ readOnlyStore.find(collection, null);
+ readOnlyStore.find(collection, null, 0);
+
+ readOnlyStore.query(collection, null, null, 0);
+ readOnlyStore.query(collection, null, null, null, 0, 0);
+
+ boolean uoeThrown = false;
+ try {
+ readOnlyStore.remove(collection, "");
+ } catch (UnsupportedOperationException uoe) {
+ //catch uoe thrown by read only wrapper
+ uoeThrown = true;
+ }
+ assertTrue("remove must throw UnsupportedOperationException",
uoeThrown);
+
+ uoeThrown = false;
+ try {
+ readOnlyStore.remove(collection, Lists.<String>newArrayList());
+ } catch (UnsupportedOperationException uoe) {
+ //catch uoe thrown by read only wrapper
+ uoeThrown = true;
+ }
+ assertTrue("remove must throw UnsupportedOperationException",
uoeThrown);
+
+ uoeThrown = false;
+ try {
+ readOnlyStore.remove(collection, Maps.<String,
Map<UpdateOp.Key, UpdateOp.Condition>>newHashMap());
+ } catch (UnsupportedOperationException uoe) {
+ //catch uoe thrown by read only wrapper
+ uoeThrown = true;
+ }
+ assertTrue("remove must throw UnsupportedOperationException",
uoeThrown);
+ uoeThrown = false;
+
+ try {
+ readOnlyStore.create(collection, null);
+ } catch (UnsupportedOperationException uoe) {
+ //catch uoe thrown by read only wrapper
+ uoeThrown = true;
+ }
+ assertTrue("create must throw UnsupportedOperationException",
uoeThrown);
+ uoeThrown = false;
+
+ try {
+ readOnlyStore.update(collection, null, null);
+ } catch (UnsupportedOperationException uoe) {
+ //catch uoe thrown by read only wrapper
+ uoeThrown = true;
+ }
+ assertTrue("update must throw UnsupportedOperationException",
uoeThrown);
+ uoeThrown = false;
+
+ try {
+ readOnlyStore.createOrUpdate(collection, (UpdateOp) null);
+ } catch (UnsupportedOperationException uoe) {
+ //catch uoe thrown by read only wrapper
+ uoeThrown = true;
+ }
+ assertTrue("createOrUpdate must throw
UnsupportedOperationException", uoeThrown);
+ uoeThrown = false;
+
+ try {
+ readOnlyStore.createOrUpdate(collection,
Lists.<UpdateOp>newArrayList());
+ } catch (UnsupportedOperationException uoe) {
+ //catch uoe thrown by read only wrapper
+ uoeThrown = true;
+ }
+ assertTrue("createOrUpdate must throw
UnsupportedOperationException", uoeThrown);
+ uoeThrown = false;
+
+ try {
+ readOnlyStore.findAndUpdate(collection, null);
+ } catch (UnsupportedOperationException uoe) {
+ //catch uoe thrown by read only wrapper
+ uoeThrown = true;
+ }
+ assertTrue("findAndUpdate must throw
UnsupportedOperationException", uoeThrown);
+
+ readOnlyStore.invalidateCache(collection, null);
+ readOnlyStore.getIfCached(collection, null);
+ }
+
+ readOnlyStore.invalidateCache();
+ readOnlyStore.invalidateCache(null);
+
+ readOnlyStore.dispose();
+ readOnlyStore.setReadWriteMode(null);
+ readOnlyStore.getCacheStats();
+ readOnlyStore.getMetadata();
+ readOnlyStore.determineServerTimeDifferenceMillis();
+ }
+
+ @Test
+ public void backgroundRead() throws Exception {
+ DocumentStore docStore = new MemoryDocumentStore();
+
+ DocumentNodeStore store = builderProvider.newBuilder().setAsyncDelay(0)
+ .setDocumentStore(docStore).setClusterId(2).getNodeStore();
+ DocumentNodeStore readOnlyStore =
builderProvider.newBuilder().setAsyncDelay(0)
+
.setDocumentStore(docStore).setClusterId(1).setReadOnlyMode().getNodeStore();
+
+ NodeBuilder builder = store.getRoot().builder();
+ builder.child("node");
+ store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+ store.runBackgroundOperations();
+
+ // at this point node must not be visible
+ assertFalse(readOnlyStore.getRoot().hasChildNode("node"));
+
+ readOnlyStore.runBackgroundOperations();
+
+ // at this point node should get visible
+ assertTrue(readOnlyStore.getRoot().hasChildNode("node"));
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/ReadOnlyDocumentStoreWrapperTest.java
------------------------------------------------------------------------------
svn:eol-style = native