Author: tomekr
Date: Tue Jun 6 11:30:38 2017
New Revision: 1797777
URL: http://svn.apache.org/viewvc?rev=1797777&view=rev
Log:
OAK-6220: Copy on write node store implementation
Added:
jackrabbit/oak/trunk/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/CopyOnWriteStoreMBean.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/BranchNodeStore.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStore.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStoreService.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/cow/
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStoreTest.java
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/cow/
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/cow/COWStoreFixture.java
Modified:
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/FixturesHelper.java
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/NodeStoreFixtures.java
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStateTest.java
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreService.java
Added:
jackrabbit/oak/trunk/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/CopyOnWriteStoreMBean.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/CopyOnWriteStoreMBean.java?rev=1797777&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/CopyOnWriteStoreMBean.java
(added)
+++
jackrabbit/oak/trunk/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/CopyOnWriteStoreMBean.java
Tue Jun 6 11:30:38 2017
@@ -0,0 +1,50 @@
+/*
+ * 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.jmx;
+
+import aQute.bnd.annotation.ProviderType;
+
+import javax.management.openmbean.TabularData;
+
+/**
+ * MBean for managing the copy-on-write node store
+ */
+@ProviderType
+public interface CopyOnWriteStoreMBean {
+ String TYPE = "CopyOnWriteStoreManager";
+
+ /**
+ * Enabled the temporary, copy-on-write store
+ * @return the operation status
+ */
+ String enableCopyOnWrite();
+
+ /**
+ * Disables the temporary store and switched the repository back to the
"normal" mode.
+ * @return the operation status
+ */
+ String disableCopyOnWrite();
+
+ /**
+ * Returns the copy-on-write status
+ * @return status of the copy-on-write mode
+ */
+ String getStatus();
+}
Modified:
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/FixturesHelper.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/FixturesHelper.java?rev=1797777&r1=1797776&r2=1797777&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/FixturesHelper.java
(original)
+++
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/FixturesHelper.java
Tue Jun 6 11:30:38 2017
@@ -43,7 +43,7 @@ public final class FixturesHelper {
* default fixtures when no {@code nsfixtures} is provided
*/
public enum Fixture {
- DOCUMENT_NS, @Deprecated SEGMENT_MK, DOCUMENT_RDB, MEMORY_NS,
DOCUMENT_MEM, SEGMENT_TAR, COMPOSITE_SEGMENT, COMPOSITE_MEM
+ DOCUMENT_NS, @Deprecated SEGMENT_MK, DOCUMENT_RDB, MEMORY_NS,
DOCUMENT_MEM, SEGMENT_TAR, COMPOSITE_SEGMENT, COMPOSITE_MEM, COW_DOCUMENT
}
private static final Set<Fixture> FIXTURES;
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/BranchNodeStore.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/BranchNodeStore.java?rev=1797777&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/BranchNodeStore.java
(added)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/BranchNodeStore.java
Tue Jun 6 11:30:38 2017
@@ -0,0 +1,184 @@
+/*
+ * 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.cow;
+
+import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.spi.commit.CommitHook;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.Observable;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+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 javax.annotation.Nonnull;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import static com.google.common.collect.Iterables.addAll;
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Lists.newCopyOnWriteArrayList;
+import static com.google.common.collect.Maps.newConcurrentMap;
+import static com.google.common.collect.Maps.newHashMap;
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonMap;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.StreamSupport.stream;
+
+public class BranchNodeStore implements NodeStore, Observable {
+
+ private static final long CHECKPOINT_LIFETIME =
TimeUnit.HOURS.toMillis(24);
+
+ private final NodeStore nodeStore;
+
+ private final MemoryNodeStore memoryNodeStore;
+
+ private final Collection<String> inheritedCheckpoints;
+
+ private final Map<String, String> checkpointMapping;
+
+ public BranchNodeStore(NodeStore nodeStore) throws CommitFailedException {
+ this.nodeStore = nodeStore;
+ this.inheritedCheckpoints = newArrayList(nodeStore.checkpoints());
+ this.checkpointMapping = newConcurrentMap();
+
+ String cp = nodeStore.checkpoint(CHECKPOINT_LIFETIME,
singletonMap("type", "copy-on-write"));
+ memoryNodeStore = new MemoryNodeStore(nodeStore.retrieve(cp));
+ }
+
+ public void dispose() {
+ for (String cp : nodeStore.checkpoints()) {
+ if
("copy-on-write".equals(nodeStore.checkpointInfo(cp).get("type"))) {
+ nodeStore.release(cp);
+ }
+ }
+ }
+
+ @Nonnull
+ @Override
+ public NodeState getRoot() {
+ return memoryNodeStore.getRoot();
+ }
+
+ @Nonnull
+ @Override
+ public synchronized NodeState merge(@Nonnull NodeBuilder builder, @Nonnull
CommitHook commitHook, @Nonnull CommitInfo info) throws CommitFailedException {
+ return memoryNodeStore.merge(builder, commitHook, info);
+ }
+
+ @Nonnull
+ @Override
+ public NodeState rebase(@Nonnull NodeBuilder builder) {
+ return memoryNodeStore.rebase(builder);
+ }
+
+ @Override
+ public NodeState reset(@Nonnull NodeBuilder builder) {
+ return memoryNodeStore.reset(builder);
+ }
+
+ @Nonnull
+ @Override
+ public Blob createBlob(InputStream inputStream) throws IOException {
+ return memoryNodeStore.createBlob(inputStream);
+ }
+
+ @Override
+ public Blob getBlob(@Nonnull String reference) {
+ return memoryNodeStore.getBlob(reference);
+ }
+
+ @Nonnull
+ @Override
+ public String checkpoint(long lifetime, @Nonnull Map<String, String>
properties) {
+ String checkpoint = memoryNodeStore.checkpoint(lifetime, properties);
+ String uuid = UUID.randomUUID().toString();
+ checkpointMapping.put(uuid, checkpoint);
+ return uuid;
+ }
+
+ @Nonnull
+ @Override
+ public String checkpoint(long lifetime) {
+ return checkpoint(lifetime, emptyMap());
+ }
+
+
+ @Nonnull
+ @Override
+ public Iterable<String> checkpoints() {
+ List<String> result = newArrayList(inheritedCheckpoints);
+ result.retainAll(newArrayList(nodeStore.checkpoints()));
+
+ checkpointMapping.entrySet().stream()
+ .filter(e ->
memoryNodeStore.listCheckpoints().contains(e.getValue()))
+ .map(Map.Entry::getKey)
+ .forEach(result::add);
+
+ return result;
+ }
+
+ @Nonnull
+ @Override
+ public Map<String, String> checkpointInfo(@Nonnull String checkpoint) {
+ if (inheritedCheckpoints.contains(checkpoint)) {
+ return nodeStore.checkpointInfo(checkpoint);
+ } else if (checkpointMapping.containsKey(checkpoint)) {
+ return
memoryNodeStore.checkpointInfo(checkpointMapping.get(checkpoint));
+ } else {
+ return emptyMap();
+ }
+ }
+
+ @Override
+ public NodeState retrieve(@Nonnull String checkpoint) {
+ if (inheritedCheckpoints.contains(checkpoint)) {
+ return nodeStore.retrieve(checkpoint);
+ } else if (checkpointMapping.containsKey(checkpoint)) {
+ return memoryNodeStore.retrieve(checkpointMapping.get(checkpoint));
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean release(@Nonnull String checkpoint) {
+ if (inheritedCheckpoints.contains(checkpoint)) {
+ return nodeStore.release(checkpoint);
+ } else if (checkpointMapping.containsKey(checkpoint)) {
+ return
memoryNodeStore.release(checkpointMapping.remove(checkpoint));
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Closeable addObserver(Observer observer) {
+ return memoryNodeStore.addObserver(observer);
+ }
+}
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStore.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStore.java?rev=1797777&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStore.java
(added)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStore.java
Tue Jun 6 11:30:38 2017
@@ -0,0 +1,216 @@
+/*
+ * 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.cow;
+
+import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.jmx.CopyOnWriteStoreMBean;
+import org.apache.jackrabbit.oak.spi.commit.CommitHook;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
+import org.apache.jackrabbit.oak.spi.commit.Observable;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+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 javax.annotation.Nonnull;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * <p>The copy-on-write (COW) node store implementation allows to temporarily
+ * switch the repository into the "testing" mode, in which all the changes are
+ * stored in a volatile storage, namely the MemoryNodeStore. After switching
+ * back to the "production" mode, the test changes should be dropped.</p>
+ *
+ * <p>If the CoW is enabled, a special :cow=true property will be set on the
+ * root node returned by getRoot(). It's being used in the merge() to decide
+ * which store be modified. Removing this property will result in merging
+ * changes to the main node store, even in the CoW mode.</p>
+ *
+ * <p>The checkpoint support is provided by the {@link BranchNodeStore} class.
+ * All the existing checkpoints are still available in the CoW mode (until they
+ * expire). New checkpoints are only created in the MemoryNodeStore.</p>
+ *
+ * <p>Known limitations:</p>
+ *
+ * <ul>
+ * <li>turning the CoW mode on and off requires cleaning up the
+ * <a
href="https://jackrabbit.apache.org/oak/docs/query/lucene.html#copy-on-read">lucene
+ * indexing cache</a>,</li>
+ * <li>switching the CoW mode may result in repository inconsistencies
+ * (eg. if two merges belongs to the same logical commit sequence),</li>
+ * <li>in the CoW mode the changes are stored in MemoryNodeStore, so it
+ * shouldn't be enabled for too long (otherwise it may exhaust the
heap).</li>
+ * </ul>
+ */
+public class COWNodeStore implements NodeStore, Observable {
+
+ private final List<Observer> observers = new CopyOnWriteArrayList<>();
+
+ private final NodeStore store;
+
+ private volatile BranchNodeStore branchStore;
+
+ public COWNodeStore(NodeStore store) {
+ this.store = store;
+ }
+
+ public void enableCopyOnWrite() throws CommitFailedException {
+ BranchNodeStore branchStore = new BranchNodeStore(store);
+
+ NodeBuilder b = branchStore.getRoot().builder();
+ b.setProperty(":cow", true);
+ branchStore.merge(b, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+ branchStore.addObserver((root, info) -> observers.stream().forEach(o
-> o.contentChanged(root, info)));
+ this.branchStore = branchStore;
+ }
+
+ public void disableCopyOnWrite() {
+ BranchNodeStore branchStore = this.branchStore;
+ this.branchStore = null;
+ branchStore.dispose();
+ }
+
+ private NodeStore getNodeStore() {
+ NodeStore s = branchStore;
+ if (s == null) {
+ s = store;
+ }
+ return s;
+ }
+
+ private NodeStore getNodeStore(NodeBuilder builder) {
+ if (builder.hasProperty(":cow")) {
+ NodeStore s = branchStore;
+ if (s == null) {
+ throw new IllegalStateException("Node store for this builder
is no longer available");
+ } else {
+ return s;
+ }
+ } else {
+ return store;
+ }
+ }
+
+ @Override
+ public Closeable addObserver(Observer observer) {
+ observer.contentChanged(getRoot(), CommitInfo.EMPTY_EXTERNAL);
+ observers.add(observer);
+ return () -> observers.remove(observer);
+ }
+
+ @Nonnull
+ @Override
+ public NodeState getRoot() {
+ return getNodeStore().getRoot();
+ }
+
+ @Nonnull
+ @Override
+ public NodeState merge(@Nonnull NodeBuilder builder, @Nonnull CommitHook
commitHook, @Nonnull CommitInfo info) throws CommitFailedException {
+ return getNodeStore(builder).merge(builder, commitHook, info);
+ }
+
+ @Nonnull
+ @Override
+ public NodeState rebase(@Nonnull NodeBuilder builder) {
+ return getNodeStore(builder).rebase(builder);
+ }
+
+ @Override
+ public NodeState reset(@Nonnull NodeBuilder builder) {
+ return getNodeStore(builder).reset(builder);
+ }
+
+ @Nonnull
+ @Override
+ public Blob createBlob(InputStream inputStream) throws IOException {
+ return getNodeStore().createBlob(inputStream);
+ }
+
+ @Override
+ public Blob getBlob(@Nonnull String reference) {
+ return getNodeStore().getBlob(reference);
+ }
+
+ @Nonnull
+ @Override
+ public String checkpoint(long lifetime, @Nonnull Map<String, String>
properties) {
+ return getNodeStore().checkpoint(lifetime, properties);
+ }
+
+ @Nonnull
+ @Override
+ public String checkpoint(long lifetime) {
+ return getNodeStore().checkpoint(lifetime);
+ }
+
+ @Nonnull
+ @Override
+ public Map<String, String> checkpointInfo(@Nonnull String checkpoint) {
+ return getNodeStore().checkpointInfo(checkpoint);
+ }
+
+ @Nonnull
+ @Override
+ public Iterable<String> checkpoints() {
+ return getNodeStore().checkpoints();
+ }
+
+ @Override
+ public NodeState retrieve(@Nonnull String checkpoint) {
+ return getNodeStore().retrieve(checkpoint);
+ }
+
+ @Override
+ public boolean release(@Nonnull String checkpoint) {
+ return getNodeStore().release(checkpoint);
+ }
+
+ class MBeanImpl implements CopyOnWriteStoreMBean {
+
+ @Override
+ public String enableCopyOnWrite() {
+ try {
+ COWNodeStore.this.enableCopyOnWrite();
+ } catch (CommitFailedException e) {
+ return "can't enable the copy on write: " + e.getMessage();
+ }
+ return "success";
+ }
+
+ @Override
+ public String disableCopyOnWrite() {
+ COWNodeStore.this.disableCopyOnWrite();
+ return "success";
+ }
+
+ @Override
+ public String getStatus() {
+ return branchStore == null ? "disabled" : "enabled";
+ }
+ }
+
+
+}
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStoreService.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStoreService.java?rev=1797777&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStoreService.java
(added)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStoreService.java
Tue Jun 6 11:30:38 2017
@@ -0,0 +1,176 @@
+/*
+ * 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.cow;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.jackrabbit.oak.api.jmx.CopyOnWriteStoreMBean;
+import org.apache.jackrabbit.oak.commons.PropertiesUtil;
+import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard;
+import org.apache.jackrabbit.oak.spi.commit.ObserverTracker;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.spi.state.NodeStoreProvider;
+import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardExecutor;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import static
org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.registerMBean;
+
+@Component(policy = ConfigurationPolicy.REQUIRE)
+public class COWNodeStoreService {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(COWNodeStoreService.class);
+
+ @Property(
+ label = "NodeStoreProvider role",
+ description = "Property indicating that this component will not
register as a NodeStore but as a NodeStoreProvider with given role"
+ )
+ public static final String PROP_ROLE = "role";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY, policy =
ReferencePolicy.DYNAMIC, target = "(role=copy-on-write)", bind =
"bindNodeStoreProvider", unbind = "unbindNodeStoreProvider")
+ private NodeStoreProvider nodeStoreProvider;
+
+ private String nodeStoreDescription;
+
+ private ComponentContext context;
+
+ private ServiceRegistration nsReg;
+
+ private Registration mbeanReg;
+
+ private ObserverTracker observerTracker;
+
+ private Whiteboard whiteboard;
+
+ private WhiteboardExecutor executor;
+
+ private String role;
+
+ @Activate
+ protected void activate(ComponentContext context, Map<String, ?> config) {
+ this.role = PropertiesUtil.toString(config.get(PROP_ROLE), null);
+ this.context = context;
+ registerNodeStore();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ unregisterNodeStore();
+ }
+
+ private void registerNodeStore() {
+ if (nsReg != null) {
+ return;
+ }
+ if (nodeStoreProvider == null) {
+ LOG.info("Waiting for the NodeStoreProvider with
role=copy-on-write");
+ return;
+ }
+ if (context == null) {
+ LOG.info("Waiting for the component activation");
+ return;
+ }
+ COWNodeStore store = new
COWNodeStore(nodeStoreProvider.getNodeStore());
+
+ whiteboard = new OsgiWhiteboard(context.getBundleContext());
+ executor = new WhiteboardExecutor();
+ executor.start(whiteboard);
+
+ mbeanReg = registerMBean(whiteboard,
+ CopyOnWriteStoreMBean.class,
+ store.new MBeanImpl(),
+ CopyOnWriteStoreMBean.TYPE,
+ "Copy-on-write: " + nodeStoreDescription);
+
+ Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(Constants.SERVICE_PID, COWNodeStore.class.getName());
+ props.put("oak.nodestore.description", new
String[]{"nodeStoreType=cowStore"});
+
+ if (role == null) {
+ LOG.info("Registering the COW node store");
+
+ observerTracker = new ObserverTracker(store);
+ observerTracker.start(context.getBundleContext());
+
+ nsReg = context.getBundleContext().registerService(
+ new String[]{NodeStore.class.getName()},
+ store,
+ props
+ );
+ } else {
+ LOG.info("Registering the COW node store provider");
+
+ props.put("role", role);
+
+ nsReg = context.getBundleContext().registerService(
+ new String[]{NodeStoreProvider.class.getName()},
+ (NodeStoreProvider) () -> store,
+ props
+ );
+ }
+ }
+
+ private void unregisterNodeStore() {
+ if (mbeanReg != null) {
+ mbeanReg.unregister();
+ mbeanReg = null;
+ }
+
+ if (executor != null) {
+ executor.stop();
+ executor = null;
+ }
+
+ if (observerTracker != null) {
+ observerTracker.stop();
+ observerTracker = null;
+ }
+
+ if (nsReg != null) {
+ LOG.info("Unregistering the COW node store");
+ nsReg.unregister();
+ nsReg = null;
+ }
+ }
+
+ protected void bindNodeStoreProvider(NodeStoreProvider ns, Map<String, ?>
config) {
+ this.nodeStoreProvider = ns;
+ this.nodeStoreDescription =
PropertiesUtil.toString(config.get("oak.nodestore.description"),
ns.getClass().getName());
+ registerNodeStore();
+ }
+
+ protected void unbindNodeStoreProvider(NodeStoreProvider ns) {
+ this.nodeStoreProvider = null;
+ this.nodeStoreDescription = null;
+ unregisterNodeStore();
+ }
+}
\ No newline at end of file
Added:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStoreTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStoreTest.java?rev=1797777&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStoreTest.java
(added)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/cow/COWNodeStoreTest.java
Tue Jun 6 11:30:38 2017
@@ -0,0 +1,107 @@
+/*
+ * 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.cow;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+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.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static com.google.common.collect.ImmutableMap.of;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class COWNodeStoreTest {
+
+ private NodeStore store;
+
+ private COWNodeStore cowNodeStore;
+
+ @Before
+ public void createCowNodeStore() {
+ store = new MemoryNodeStore();
+ cowNodeStore = new COWNodeStore(store);
+ }
+
+ @Test
+ public void changesInCowMode() throws CommitFailedException {
+ NodeState root = cowNodeStore.getRoot();
+ NodeBuilder builder = root.builder();
+ builder.child("abc");
+ cowNodeStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+ cowNodeStore.enableCopyOnWrite();
+
+ root = cowNodeStore.getRoot();
+ builder = root.builder();
+ builder.child("foo");
+ cowNodeStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+ root = store.getRoot();
+ builder = root.builder();
+ builder.child("bar");
+ store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+ assertTrue("Change introduced before enabling the CoW mode is not
available", cowNodeStore.getRoot().hasChildNode("abc"));
+ assertTrue("Change introduced after enabling the CoW mode is not
available", cowNodeStore.getRoot().hasChildNode("foo"));
+ assertFalse("Changed introduced to the main store after enabling the
CoW mode shouldn't be visible", cowNodeStore.getRoot().hasChildNode("bar"));
+
+ assertTrue("Change introduced before enabling the CoW mode should be
visible is the main store", store.getRoot().hasChildNode("abc"));
+ assertFalse("Change introduced after enabling the CoW mode shouldn't
be visible in the main store", store.getRoot().hasChildNode("foo"));
+ assertTrue("Change introduced to the main store should be visible",
store.getRoot().hasChildNode("bar"));
+
+ cowNodeStore.disableCopyOnWrite();
+
+ assertTrue("Change introduced before enabling the CoW mode is not
available", cowNodeStore.getRoot().hasChildNode("abc"));
+ assertFalse("Change introduced in the CoW mode should be dropped after
disabling it", cowNodeStore.getRoot().hasChildNode("foo"));
+ assertTrue("Change introduced to the main store should be visible",
cowNodeStore.getRoot().hasChildNode("bar"));
+ }
+
+ @Test
+ public void checkpointsInCowMode() throws CommitFailedException {
+ String checkpoint1 = cowNodeStore.checkpoint(Long.MAX_VALUE, of("k",
"v"));
+ cowNodeStore.enableCopyOnWrite();
+
+ Map<String, String> info = cowNodeStore.checkpointInfo(checkpoint1);
+ assertEquals("The checkpoint is not inherited", of("k", "v"), info);
+
+ NodeState root = cowNodeStore.getRoot();
+ NodeBuilder builder = root.builder();
+ builder.child("foo");
+ cowNodeStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+ String checkpoint2 = cowNodeStore.checkpoint(Long.MAX_VALUE, of("k",
"v2"));
+ info = cowNodeStore.checkpointInfo(checkpoint2);
+ assertEquals("The new checkpoint is not available", of("k", "v2"),
info);
+ assertTrue("The retrieve() doesn't work for the new checkpoint",
cowNodeStore.retrieve(checkpoint2).hasChildNode("foo"));
+ assertEquals(ImmutableList.of(checkpoint1, checkpoint2),
cowNodeStore.checkpoints());
+
+ assertTrue("The new checkpoint shouldn't be stored in the main store",
store.checkpointInfo(checkpoint2).isEmpty());
+
+ cowNodeStore.disableCopyOnWrite();
+ assertTrue("The new checkpoint should be dropped after disabling the
CoW mode", cowNodeStore.checkpointInfo(checkpoint2).isEmpty());
+ }
+}
Modified:
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/NodeStoreFixtures.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/NodeStoreFixtures.java?rev=1797777&r1=1797776&r2=1797777&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/NodeStoreFixtures.java
(original)
+++
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/NodeStoreFixtures.java
Tue Jun 6 11:30:38 2017
@@ -23,6 +23,7 @@ import java.util.List;
import java.util.Set;
import org.apache.jackrabbit.oak.commons.FixturesHelper;
+import org.apache.jackrabbit.oak.cow.COWStoreFixture;
import org.apache.jackrabbit.oak.fixture.DocumentMemoryFixture;
import org.apache.jackrabbit.oak.fixture.DocumentMongoFixture;
import org.apache.jackrabbit.oak.fixture.DocumentRdbFixture;
@@ -48,6 +49,8 @@ public class NodeStoreFixtures {
public static final NodeStoreFixture COMPOSITE_MEM = new
CompositeMemoryStoreFixture();
+ public static final NodeStoreFixture COW_DOCUMENT = new COWStoreFixture();
+
public static Collection<Object[]>
asJunitParameters(Set<FixturesHelper.Fixture> fixtures) {
List<NodeStoreFixture> configuredFixtures = new
ArrayList<NodeStoreFixture>();
if (fixtures.contains(FixturesHelper.Fixture.DOCUMENT_NS)) {
@@ -71,6 +74,9 @@ public class NodeStoreFixtures {
if (fixtures.contains(FixturesHelper.Fixture.COMPOSITE_MEM)) {
configuredFixtures.add(COMPOSITE_MEM);
}
+ if (fixtures.contains(FixturesHelper.Fixture.COW_DOCUMENT)) {
+ configuredFixtures.add(COW_DOCUMENT);
+ }
Collection<Object[]> result = new ArrayList<Object[]>();
for (NodeStoreFixture f : configuredFixtures) {
Added:
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/cow/COWStoreFixture.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/cow/COWStoreFixture.java?rev=1797777&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/cow/COWStoreFixture.java
(added)
+++
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/cow/COWStoreFixture.java
Tue Jun 6 11:30:38 2017
@@ -0,0 +1,45 @@
+/*
+ * 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.cow;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.fixture.NodeStoreFixture;
+import org.apache.jackrabbit.oak.plugins.cow.COWNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.DocumentMK;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+
+public class COWStoreFixture extends NodeStoreFixture {
+
+ @Override
+ public NodeStore createNodeStore() {
+ COWNodeStore COWNodeStore = new COWNodeStore(new
DocumentMK.Builder().getNodeStore());
+ try {
+ COWNodeStore.enableCopyOnWrite();
+ } catch (CommitFailedException e) {
+ throw new RuntimeException(e);
+ }
+ return COWNodeStore;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+}
\ No newline at end of file
Modified:
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStateTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStateTest.java?rev=1797777&r1=1797776&r2=1797777&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStateTest.java
(original)
+++
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStateTest.java
Tue Jun 6 11:30:38 2017
@@ -42,12 +42,17 @@ import org.junit.Test;
public class NodeStateTest extends OakBaseTest {
private NodeState state;
+ private long initialCount;
+
public NodeStateTest(NodeStoreFixture fixture) {
super(fixture);
}
@Before
public void setUp() throws CommitFailedException {
+ NodeState root = store.getRoot();
+ initialCount = root.getPropertyCount();
+
NodeBuilder builder = store.getRoot().builder();
builder.setProperty("a", 1);
builder.setProperty("b", 2);
@@ -67,7 +72,7 @@ public class NodeStateTest extends OakBa
@Test
public void testGetPropertyCount() {
- assertEquals(3, state.getPropertyCount());
+ assertEquals(3 + initialCount, state.getPropertyCount());
}
@Test
@@ -87,6 +92,9 @@ public class NodeStateTest extends OakBa
List<String> names = new ArrayList<String>();
List<Long> values = new ArrayList<Long>();
for (PropertyState property : state.getProperties()) {
+ if (property.getName().startsWith(":")) {
+ continue;
+ }
names.add(property.getName());
values.add(property.getValue(LONG));
}
Modified:
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java?rev=1797777&r1=1797776&r2=1797777&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java
(original)
+++
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java
Tue Jun 6 11:30:38 2017
@@ -457,7 +457,8 @@ public class NodeStoreTest extends OakBa
NodeBuilder test = store.getRoot().builder().getChildNode("test");
NodeBuilder x = test.getChildNode("x");
if (fixture == NodeStoreFixtures.SEGMENT_TAR || fixture ==
NodeStoreFixtures.MEMORY_NS
- || fixture == NodeStoreFixtures.COMPOSITE_MEM || fixture ==
NodeStoreFixtures.COMPOSITE_SEGMENT) {
+ || fixture == NodeStoreFixtures.COMPOSITE_MEM || fixture ==
NodeStoreFixtures.COMPOSITE_SEGMENT
+ || fixture == NodeStoreFixtures.COW_DOCUMENT) {
assertTrue(x.moveTo(x, "xx"));
assertFalse(x.exists());
assertFalse(test.hasChildNode("x"));
Modified:
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreService.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreService.java?rev=1797777&r1=1797776&r2=1797777&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreService.java
(original)
+++
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreService.java
Tue Jun 6 11:30:38 2017
@@ -58,7 +58,7 @@ public class CompositeNodeStoreService {
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY, policy =
ReferencePolicy.STATIC)
private MountInfoProvider mountInfoProvider;
- @Reference(cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, policy =
ReferencePolicy.DYNAMIC, bind = "bindNodeStore", unbind = "unbindNodeStore",
referenceInterface = NodeStoreProvider.class)
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, policy =
ReferencePolicy.DYNAMIC, bind = "bindNodeStore", unbind = "unbindNodeStore",
referenceInterface = NodeStoreProvider.class,
target="(!(service.pid=org.apache.jackrabbit.oak.composite.CompositeNodeStore))")
private List<NodeStoreWithProps> nodeStores = new ArrayList<>();
@Property(label = "Ignore read only writes",
@@ -158,6 +158,9 @@ public class CompositeNodeStoreService {
private String getMountName(NodeStoreWithProps ns) {
String role = ns.getRole();
+ if (role == null) {
+ return null;
+ }
if (!role.startsWith(MOUNT_ROLE_PREFIX)) {
return null;
}