diff --git a/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/CopyOnWriteStoreMBean.java b/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/CopyOnWriteStoreMBean.java new file mode 100644 index 0000000..01ef01d --- /dev/null +++ b/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/CopyOnWriteStoreMBean.java @@ -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(); +} diff --git a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/FixturesHelper.java b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/FixturesHelper.java index c54c18c..4ffbc98 100644 --- a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/FixturesHelper.java +++ b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/FixturesHelper.java @@ -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 FIXTURES; diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/BranchNodeStore.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/BranchNodeStore.java new file mode 100644 index 0000000..8dd4d2c --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/BranchNodeStore.java @@ -0,0 +1,158 @@ +/* + * 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.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.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static com.google.common.collect.Iterables.addAll; +import static com.google.common.collect.Lists.newArrayList; +import static java.util.Collections.singletonMap; + +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 inheritedCheckpoints; + + public BranchNodeStore(NodeStore nodeStore) throws CommitFailedException { + this.nodeStore = nodeStore; + this.inheritedCheckpoints = newArrayList(nodeStore.checkpoints()); + + 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 properties) { + return memoryNodeStore.checkpoint(lifetime, properties); + } + + @Nonnull + @Override + public String checkpoint(long lifetime) { + return memoryNodeStore.checkpoint(lifetime); + } + + + @Nonnull + @Override + public Iterable checkpoints() { + List result = newArrayList(inheritedCheckpoints); + result.retainAll(newArrayList(nodeStore.checkpoints())); + addAll(result, memoryNodeStore.checkpoints()); + return result; + } + + @Nonnull + @Override + public Map checkpointInfo(@Nonnull String checkpoint) { + if (inheritedCheckpoints.contains(checkpoint)) { + return nodeStore.checkpointInfo(checkpoint); + } else { + return memoryNodeStore.checkpointInfo(checkpoint); + } + } + + @Override + public NodeState retrieve(@Nonnull String checkpoint) { + if (inheritedCheckpoints.contains(checkpoint)) { + return nodeStore.retrieve(checkpoint); + } else { + return memoryNodeStore.retrieve(checkpoint); + } + } + + @Override + public boolean release(@Nonnull String checkpoint) { + if (inheritedCheckpoints.contains(checkpoint)) { + return nodeStore.release(checkpoint); + } else { + return memoryNodeStore.release(checkpoint); + } + } + + @Override + public Closeable addObserver(Observer observer) { + return memoryNodeStore.addObserver(observer); + } +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/CowNodeStore.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/CowNodeStore.java new file mode 100644 index 0000000..7b7e7d6 --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/CowNodeStore.java @@ -0,0 +1,189 @@ +/* + * 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; + +public class CowNodeStore implements NodeStore, Observable { + + private final List 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 properties) { + return getNodeStore().checkpoint(lifetime, properties); + } + + @Nonnull + @Override + public String checkpoint(long lifetime) { + return getNodeStore().checkpoint(lifetime); + } + + @Nonnull + @Override + public Map checkpointInfo(@Nonnull String checkpoint) { + return getNodeStore().checkpointInfo(checkpoint); + } + + @Nonnull + @Override + public Iterable 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"; + } + } + + +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/CowNodeStoreService.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/CowNodeStoreService.java new file mode 100644 index 0000000..e395184 --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/cow/CowNodeStoreService.java @@ -0,0 +1,177 @@ +/* + * 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.CacheStatsMBean; +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 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 props = new Hashtable(); + 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 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 diff --git a/oak-it/src/test/java/org/apache/jackrabbit/oak/NodeStoreFixtures.java b/oak-it/src/test/java/org/apache/jackrabbit/oak/NodeStoreFixtures.java index 59a7edc..fb93697 100644 --- a/oak-it/src/test/java/org/apache/jackrabbit/oak/NodeStoreFixtures.java +++ b/oak-it/src/test/java/org/apache/jackrabbit/oak/NodeStoreFixtures.java @@ -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 asJunitParameters(Set fixtures) { List configuredFixtures = new ArrayList(); 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 result = new ArrayList(); for (NodeStoreFixture f : configuredFixtures) { diff --git a/oak-it/src/test/java/org/apache/jackrabbit/oak/cow/CowStoreFixture.java b/oak-it/src/test/java/org/apache/jackrabbit/oak/cow/CowStoreFixture.java new file mode 100644 index 0000000..474992b --- /dev/null +++ b/oak-it/src/test/java/org/apache/jackrabbit/oak/cow/CowStoreFixture.java @@ -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 diff --git a/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStateTest.java b/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStateTest.java index 9bae26d..c25e6d0 100644 --- a/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStateTest.java +++ b/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStateTest.java @@ -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 OakBaseTest { @Test public void testGetPropertyCount() { - assertEquals(3, state.getPropertyCount()); + assertEquals(3 + initialCount, state.getPropertyCount()); } @Test @@ -87,6 +92,9 @@ public class NodeStateTest extends OakBaseTest { List names = new ArrayList(); List values = new ArrayList(); for (PropertyState property : state.getProperties()) { + if (property.getName().startsWith(":")) { + continue; + } names.add(property.getName()); values.add(property.getValue(LONG)); } diff --git a/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java b/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java index 0cbf0cc..79d8325 100644 --- a/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java +++ b/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java @@ -457,7 +457,8 @@ public class NodeStoreTest extends OakBaseTest { 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")); diff --git a/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreService.java b/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreService.java index 0a3783a..2360bde 100644 --- a/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreService.java +++ b/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreService.java @@ -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 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; }