You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by da...@apache.org on 2015/02/02 17:07:35 UTC
svn commit: r1656506 - in /jackrabbit/oak/trunk:
oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/atomic/
oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/
oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/...
Author: davide
Date: Mon Feb 2 16:07:35 2015
New Revision: 1656506
URL: http://svn.apache.org/r1656506
Log:
OAK-2220: Support for atomic counters
- Atomic counter support for non-cluster solution
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/atomic/
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditor.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditorProvider.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/atomic/
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditorTest.java
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/AtomicCounterIT.java
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/AtomicCounterTest.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/NodeTypeConstants.java
jackrabbit/oak/trunk/oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd
jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditor.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditor.java?rev=1656506&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditor.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditor.java Mon Feb 2 16:07:35 2015
@@ -0,0 +1,200 @@
+/*
+ * 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.atomic;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
+import static org.apache.jackrabbit.oak.api.Type.LONG;
+import static org.apache.jackrabbit.oak.api.Type.NAMES;
+import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.MIX_ATOMIC_COUNTER;
+
+import java.util.UUID;
+
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.commit.DefaultEditor;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Iterators;
+
+/**
+ * <p>
+ * Manages a node as <em>Atomic Counter</em>: a node which will handle at low level a protected
+ * property ({@link #PROP_COUNTER}) in an atomic way. This will represent an increment or decrement
+ * of a counter in the case, for example, of <em>Likes</em> or <em>Voting</em>.
+ * </p>
+ *
+ * <p>
+ * Whenever you add a {@link NodeTypeConstants#MIX_ATOMIC_COUNTER} mixin to a node it will turn it
+ * into an atomic counter. Then in order to increment or decrement the {@code oak:counter} property
+ * you'll need to set the {@code oak:increment} one ({@link #PROP_INCREMENT). Please note that the
+ * <strong>{@code oak:incremement} will never be saved</strong>, only the {@code oak:counter} will
+ * be amended accordingly.
+ * </p>
+ *
+ * <p>
+ * So in order to deal with the counter from a JCR point of view you'll do something as follows
+ * </p>
+ *
+ * <pre>
+ * Session session = ...
+ *
+ * // creating a counter node
+ * Node counter = session.getRootNode().addNode("mycounter");
+ * counter.addMixin("mix:atomicCounter"); // or use the NodeTypeConstants
+ * session.save();
+ *
+ * // Will output 0. the default value
+ * System.out.println("counter now: " + counter.getProperty("oak:counter").getLong());
+ *
+ * // incrementing by 5 the counter
+ * counter.setProperty("oak:increment", 5);
+ * session.save();
+ *
+ * // Will output 5
+ * System.out.println("counter now: " + counter.getProperty("oak:counter").getLong());
+ *
+ * // decreasing by 1
+ * counter.setProperty("oak:increment", -1);
+ * session.save();
+ *
+ * // Will output 4
+ * System.out.println("counter now: " + counter.getProperty("oak:counter").getLong());
+ *
+ * session.logout();
+ * </pre>
+ */
+public class AtomicCounterEditor extends DefaultEditor {
+ /**
+ * property to be set for incrementing/decrementing the counter
+ */
+ public static final String PROP_INCREMENT = "oak:increment";
+
+ /**
+ * property with the consolidated counter
+ */
+ public static final String PROP_COUNTER = "oak:counter";
+
+ /**
+ * prefix used internally for tracking the counting requests
+ */
+ public static final String PREFIX_PROP_COUNTER = ":oak-counter-";
+
+ private static final Logger LOG = LoggerFactory.getLogger(AtomicCounterEditor.class);
+ private final NodeBuilder builder;
+ private final String path;
+
+ /**
+ * instruct whether to update the node on leave.
+ */
+ private boolean update;
+
+ public AtomicCounterEditor(@Nonnull final NodeBuilder builder) {
+ this("", checkNotNull(builder));
+ }
+
+ private AtomicCounterEditor(final String path, final NodeBuilder builder) {
+ this.builder = checkNotNull(builder);
+ this.path = path;
+ }
+
+ private static boolean shallWeProcessProperty(final PropertyState property,
+ final String path,
+ final NodeBuilder builder) {
+ boolean process = false;
+ PropertyState mixin = checkNotNull(builder).getProperty(JCR_MIXINTYPES);
+ if (mixin != null && PROP_INCREMENT.equals(property.getName()) &&
+ Iterators.contains(mixin.getValue(NAMES).iterator(), MIX_ATOMIC_COUNTER)) {
+ if (LONG.equals(property.getType())) {
+ process = true;
+ } else {
+ LOG.warn(
+ "although the {} property is set is not of the right value: LONG. Not processing node: {}.",
+ PROP_INCREMENT, path);
+ }
+ }
+ return process;
+ }
+
+ /**
+ * <p>
+ * consolidate the {@link #PREFIX_PROP_COUNTER} properties and sum them into the
+ * {@link #PROP_COUNTER}
+ * </p>
+ *
+ * <p>
+ * The passed in {@code NodeBuilder} must have
+ * {@link org.apache.jackrabbit.JcrConstants#JCR_MIXINTYPES JCR_MIXINTYPES} with
+ * {@link org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants#MIX_ATOMIC_COUNTER MIX_ATOMIC_COUNTER}.
+ * If not it will be silently ignored.
+ * </p>
+ *
+ * @param builder the builder to work on. Cannot be null.
+ */
+ public static void consolidateCount(@Nonnull final NodeBuilder builder) {
+ long count = builder.hasProperty(PROP_COUNTER)
+ ? builder.getProperty(PROP_COUNTER).getValue(LONG)
+ : 0;
+ for (PropertyState p : builder.getProperties()) {
+ if (p.getName().startsWith(PREFIX_PROP_COUNTER)) {
+ count += p.getValue(LONG);
+ builder.removeProperty(p.getName());
+ }
+ }
+
+ builder.setProperty(PROP_COUNTER, count);
+ }
+
+ private void setUniqueCounter(final long value) {
+ update = true;
+ builder.setProperty(PREFIX_PROP_COUNTER + UUID.randomUUID(), value, LONG);
+ }
+
+ @Override
+ public void propertyAdded(final PropertyState after) throws CommitFailedException {
+ if (shallWeProcessProperty(after, path, builder)) {
+ setUniqueCounter(after.getValue(LONG));
+ builder.removeProperty(PROP_INCREMENT);
+ }
+ }
+
+ @Override
+ public Editor childNodeAdded(final String name, final NodeState after) throws CommitFailedException {
+ return new AtomicCounterEditor(path + '/' + name, builder.getChildNode(name));
+ }
+
+ @Override
+ public Editor childNodeChanged(final String name,
+ final NodeState before,
+ final NodeState after) throws CommitFailedException {
+ return new AtomicCounterEditor(path + '/' + name, builder.getChildNode(name));
+ }
+
+ @Override
+ public void leave(final NodeState before, final NodeState after) throws CommitFailedException {
+ if (update) {
+ // TODO here is where the Async check could be done
+ consolidateCount(builder);
+ }
+ }
+}
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditorProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditorProvider.java?rev=1656506&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditorProvider.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditorProvider.java Mon Feb 2 16:07:35 2015
@@ -0,0 +1,41 @@
+/*
+ * 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.atomic;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * Provide an instance of {@link AtomicCounterEditor}
+ */
+@Component
+@Service
+public class AtomicCounterEditorProvider implements EditorProvider {
+
+ @Override
+ public Editor getRootEditor(final NodeState before, final NodeState after,
+ final NodeBuilder builder, final CommitInfo info)
+ throws CommitFailedException {
+ return new AtomicCounterEditor(builder);
+ }
+}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/NodeTypeConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/NodeTypeConstants.java?rev=1656506&r1=1656505&r2=1656506&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/NodeTypeConstants.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/NodeTypeConstants.java Mon Feb 2 16:07:35 2015
@@ -90,5 +90,9 @@ public interface NodeTypeConstants exten
String REP_PRIMARY_TYPE = "rep:primaryType";
String REP_MIXIN_TYPES = "rep:mixinTypes";
String REP_UUID = "rep:uuid";
-
+
+ /**
+ * mixin to enable the AtomicCounterEditor.
+ */
+ String MIX_ATOMIC_COUNTER = "mix:atomicCounter";
}
Modified: jackrabbit/oak/trunk/oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd?rev=1656506&r1=1656505&r2=1656506&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/resources/org/apache/jackrabbit/oak/plugins/nodetype/write/builtin_nodetypes.cnd Mon Feb 2 16:07:35 2015
@@ -244,6 +244,14 @@
[mix:etag]
mixin
- jcr:etag (STRING) protected autocreated
+
+/**
+ * mix:atomicCounter will define a node as a counter which will keep tracks of increasing/decreasing
+ * consistently across a cluster of oak instances. https://issues.apache.org/jira/browse/OAK-2220
+ */
+[mix:atomicCounter]
+ mixin
+ - oak:counter (LONG) = '0' protected autocreated
//------------------------------------------------------------------------------
// U N S T R U C T U R E D C O N T E N T
Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditorTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditorTest.java?rev=1656506&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditorTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/atomic/AtomicCounterEditorTest.java Mon Feb 2 16:07:35 2015
@@ -0,0 +1,140 @@
+/*
+ * 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.atomic;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableList.of;
+import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
+import static org.apache.jackrabbit.oak.api.Type.LONG;
+import static org.apache.jackrabbit.oak.api.Type.NAMES;
+import static org.apache.jackrabbit.oak.plugins.atomic.AtomicCounterEditor.PREFIX_PROP_COUNTER;
+import static org.apache.jackrabbit.oak.plugins.atomic.AtomicCounterEditor.PROP_COUNTER;
+import static org.apache.jackrabbit.oak.plugins.atomic.AtomicCounterEditor.PROP_INCREMENT;
+import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.MIX_ATOMIC_COUNTER;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class AtomicCounterEditorTest {
+ @Test
+ @Ignore // FIXME fix test expectations
+ public void childNodeAdded() throws CommitFailedException {
+ NodeBuilder builder = EMPTY_NODE.builder();
+
+ Editor editor = new AtomicCounterEditor(EMPTY_NODE.builder());
+
+ assertNull("without the mixin we should not process",
+ editor.childNodeAdded("foo", builder.getNodeState()));
+
+ builder = EMPTY_NODE.builder();
+ builder = setMixin(builder);
+ assertTrue("with the mixin set we should get a proper Editor",
+ editor.childNodeAdded("foo", builder.getNodeState()) instanceof AtomicCounterEditor);
+ }
+
+ @Test
+ public void increment() throws CommitFailedException {
+ NodeBuilder builder;
+ Editor editor;
+ PropertyState property;
+
+ builder = EMPTY_NODE.builder();
+ editor = new AtomicCounterEditor(builder);
+ property = PropertyStates.createProperty(PROP_INCREMENT, 1L, Type.LONG);
+ editor.propertyAdded(property);
+ assertNoCounters(builder.getProperties());
+
+ builder = EMPTY_NODE.builder();
+ builder = setMixin(builder);
+ editor = new AtomicCounterEditor(builder);
+ property = PropertyStates.createProperty(PROP_INCREMENT, 1L, Type.LONG);
+ editor.propertyAdded(property);
+ assertNull("the oak:increment should never be set", builder.getProperty(PROP_INCREMENT));
+ assertTotalCounters(builder.getProperties(), 1);
+ }
+
+ @Test
+ public void consolidate() throws CommitFailedException {
+ NodeBuilder builder;
+ Editor editor;
+ PropertyState property;
+
+ builder = EMPTY_NODE.builder();
+ builder = setMixin(builder);
+ editor = new AtomicCounterEditor(builder);
+ property = PropertyStates.createProperty(PROP_INCREMENT, 1L, Type.LONG);
+
+ editor.propertyAdded(property);
+ assertTotalCounters(builder.getProperties(), 1);
+ editor.propertyAdded(property);
+ assertTotalCounters(builder.getProperties(), 2);
+ AtomicCounterEditor.consolidateCount(builder);
+ assertNotNull(builder.getProperty(PROP_COUNTER));
+ assertEquals(2, builder.getProperty(PROP_COUNTER).getValue(LONG).longValue());
+ assertNoCounters(builder.getProperties());
+ }
+
+ /**
+ * that a list of properties does not contains any property with name starting with
+ * {@link AtomicCounterEditor#PREFIX_PROP_COUNTER}
+ *
+ * @param properties
+ */
+ private static void assertNoCounters(@Nonnull final Iterable<? extends PropertyState> properties) {
+ checkNotNull(properties);
+
+ for (PropertyState p : properties) {
+ assertFalse("there should be no counter property",
+ p.getName().startsWith(PREFIX_PROP_COUNTER));
+ }
+ }
+
+ /**
+ * assert the total amount of {@link AtomicCounterEditor#PREFIX_PROP_COUNTER}
+ *
+ * @param properties
+ */
+ private static void assertTotalCounters(@Nonnull final Iterable<? extends PropertyState> properties,
+ int expected) {
+ int total = 0;
+ for (PropertyState p : checkNotNull(properties)) {
+ if (p.getName().startsWith(PREFIX_PROP_COUNTER)) {
+ total += p.getValue(LONG);
+ }
+ }
+
+ assertEquals("the total amount of :oak-counter properties does not match", expected, total);
+ }
+
+ private static NodeBuilder setMixin(@Nonnull final NodeBuilder builder) {
+ return checkNotNull(builder).setProperty(JCR_MIXINTYPES, of(MIX_ATOMIC_COUNTER), NAMES);
+ }
+}
Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java?rev=1656506&r1=1656505&r2=1656506&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java Mon Feb 2 16:07:35 2015
@@ -46,6 +46,7 @@ import org.apache.jackrabbit.oak.plugins
import org.apache.jackrabbit.oak.plugins.version.VersionEditorProvider;
import org.apache.jackrabbit.oak.query.QueryEngineSettings;
import org.apache.jackrabbit.oak.security.SecurityProviderImpl;
+import org.apache.jackrabbit.oak.plugins.atomic.AtomicCounterEditorProvider;
import org.apache.jackrabbit.oak.spi.commit.CommitHook;
import org.apache.jackrabbit.oak.spi.commit.CompositeConflictHandler;
import org.apache.jackrabbit.oak.spi.commit.Editor;
@@ -83,6 +84,7 @@ public class Jcr {
with(new NamespaceEditorProvider());
with(new TypeEditorProvider());
with(new ConflictValidatorProvider());
+ with(new AtomicCounterEditorProvider());
with(new ReferenceEditorProvider());
with(new ReferenceIndexProvider());
Added: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/AtomicCounterIT.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/AtomicCounterIT.java?rev=1656506&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/AtomicCounterIT.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/AtomicCounterIT.java Mon Feb 2 16:07:35 2015
@@ -0,0 +1,120 @@
+/*
+ * 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.jcr;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.apache.jackrabbit.oak.plugins.atomic.AtomicCounterEditor.PROP_COUNTER;
+import static org.apache.jackrabbit.oak.plugins.atomic.AtomicCounterEditor.PROP_INCREMENT;
+import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.MIX_ATOMIC_COUNTER;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.annotation.Nonnull;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.oak.commons.FixturesHelper;
+import org.apache.jackrabbit.oak.commons.FixturesHelper.Fixture;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFutureTask;
+
+public class AtomicCounterIT extends AbstractRepositoryTest {
+ private static final Set<Fixture> FIXTURES = FixturesHelper.getFixtures();
+
+ public AtomicCounterIT(NodeStoreFixture fixture) {
+ super(fixture);
+ }
+
+ @BeforeClass
+ public static void assumptions() {
+ assumeTrue(FIXTURES.contains(Fixture.SEGMENT_MK));
+ }
+
+ @Test
+ public void concurrentSegmentIncrements() throws RepositoryException, InterruptedException,
+ ExecutionException {
+ // ensuring the run only on allowed fix
+ assumeTrue(NodeStoreFixture.SEGMENT_MK.equals(fixture));
+
+ // setting-up
+ Session session = getAdminSession();
+
+ try {
+ Node counter = session.getRootNode().addNode("counter");
+ counter.addMixin(MIX_ATOMIC_COUNTER);
+ session.save();
+
+ final AtomicLong expected = new AtomicLong(0);
+ final String counterPath = counter.getPath();
+ final Random rnd = new Random(11);
+
+ // ensuring initial state
+ assertEquals(expected.get(), counter.getProperty(PROP_COUNTER).getLong());
+
+ List<ListenableFutureTask<Void>> tasks = Lists.newArrayList();
+ for (int t = 0; t < 100; t++) {
+ tasks.add(updateCounter(counterPath, rnd.nextInt(10) + 1, expected));
+ }
+ Futures.allAsList(tasks).get();
+
+ session.refresh(false);
+ assertEquals(expected.get(),
+ session.getNode(counterPath).getProperty(PROP_COUNTER).getLong());
+ } finally {
+ session.logout();
+ }
+ }
+
+ private ListenableFutureTask<Void> updateCounter(@Nonnull final String counterPath,
+ final long delta,
+ @Nonnull final AtomicLong expected) {
+ checkNotNull(counterPath);
+ checkNotNull(expected);
+
+ ListenableFutureTask<Void> task = ListenableFutureTask.create(new Callable<Void>() {
+
+ @Override
+ public Void call() throws Exception {
+ Session session = createAdminSession();
+ try {
+ Node c = session.getNode(counterPath);
+ c.setProperty(PROP_INCREMENT, delta);
+ expected.addAndGet(delta);
+ session.save();
+ } finally {
+ session.logout();
+ }
+ return null;
+ }
+ });
+
+ new Thread(task).start();
+ return task;
+ }
+}
Added: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/AtomicCounterTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/AtomicCounterTest.java?rev=1656506&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/AtomicCounterTest.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/AtomicCounterTest.java Mon Feb 2 16:07:35 2015
@@ -0,0 +1,127 @@
+/*
+ * 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.jcr;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.apache.jackrabbit.oak.plugins.atomic.AtomicCounterEditor.PROP_COUNTER;
+import static org.apache.jackrabbit.oak.plugins.atomic.AtomicCounterEditor.PROP_INCREMENT;
+import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.MIX_ATOMIC_COUNTER;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import javax.annotation.Nonnull;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.junit.Test;
+
+public class AtomicCounterTest extends AbstractRepositoryTest {
+ public AtomicCounterTest(NodeStoreFixture fixture) {
+ super(fixture);
+ }
+
+ @Test
+ public void incrementRootNode() throws RepositoryException {
+
+ Session session = getAdminSession();
+
+ try {
+ Node root = session.getRootNode();
+ Node node = root.addNode("normal node");
+ session.save();
+
+ node.setProperty(PROP_INCREMENT, 1L);
+ session.save();
+
+ assertTrue("for normal nodes we expect the increment property to be treated as normal",
+ node.hasProperty(PROP_INCREMENT));
+
+ node = root.addNode("counterNode");
+ node.addMixin(MIX_ATOMIC_COUNTER);
+ session.save();
+
+ assertCounter(node, 0);
+
+ node.setProperty(PROP_INCREMENT, 1L);
+ session.save();
+ assertCounter(node, 1);
+
+ // increment again the same node
+ node.setProperty(PROP_INCREMENT, 1L);
+ session.save();
+ assertCounter(node, 2);
+
+ // decrease the counter by 2
+ node.setProperty(PROP_INCREMENT, -2L);
+ session.save();
+ assertCounter(node, 0);
+
+ // increase by 5
+ node.setProperty(PROP_INCREMENT, 5L);
+ session.save();
+ assertCounter(node, 5);
+ } finally {
+ session.logout();
+ }
+ }
+
+ private static void assertCounter(@Nonnull final Node counter, final long expectedCount)
+ throws RepositoryException {
+ checkNotNull(counter);
+
+ assertTrue(counter.hasProperty(PROP_COUNTER));
+ assertEquals(expectedCount, counter.getProperty(PROP_COUNTER).getLong());
+ assertFalse(counter.hasProperty(PROP_INCREMENT));
+ }
+
+ @Test
+ public void incrementNonRootNode() throws RepositoryException {
+ Session session = getAdminSession();
+
+ try {
+ Node counter = session.getRootNode().addNode("foo").addNode("bar").addNode("counter");
+ counter.addMixin(MIX_ATOMIC_COUNTER);
+ session.save();
+
+ assertCounter(counter, 0);
+
+ counter.setProperty(PROP_INCREMENT, 1L);
+ session.save();
+ assertCounter(counter, 1);
+
+ // increment again the same node
+ counter.setProperty(PROP_INCREMENT, 1L);
+ session.save();
+ assertCounter(counter, 2);
+
+ // decrease the counter by 2
+ counter.setProperty(PROP_INCREMENT, -2L);
+ session.save();
+ assertCounter(counter, 0);
+
+ // increase by 5
+ counter.setProperty(PROP_INCREMENT, 5L);
+ session.save();
+ assertCounter(counter, 5);
+ } finally {
+ session.logout();
+ }
+ }
+
+}