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


Reply via email to