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();
+        }
+    }
+    
+}


Reply via email to