BUG 6057: Rewrite ShardedDOMProducer to use new cursor api 32/40032/15
authorTomas Cere <tcere@cisco.com>
Thu, 2 Jun 2016 15:19:49 +0000 (17:19 +0200)
committerRobert Varga <nite@hq.sk>
Mon, 11 Jul 2016 08:25:53 +0000 (08:25 +0000)
Change ShardedDOMProducer, ShardedDOMDataTreeWriteTransaction to use
cursor api's.
Make the user facing cursor implementation enforce claimed subtrees.
Add binding adapters for the DOM cursor api's.

Change-Id: I21fa6533b8ce7da0e5cc864569f6f143a273ae86
Signed-off-by: Tomas Cere <tcere@cisco.com>
26 files changed:
binding/mdsal-binding-api/pom.xml
binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/CursorAwareWriteTransaction.java [new file with mode: 0644]
binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeCursor.java [new file with mode: 0644]
binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeCursorProvider.java [new file with mode: 0644]
binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeProducer.java
binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeWriteCursor.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMCursorAwareWriteTransactionAdapter.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeProducerAdapter.java
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeWriteCursorAdapter.java [new file with mode: 0644]
dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeCommitCohortRegistry.java
dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeCursorAwareTransaction.java [new file with mode: 0644]
dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeProducer.java
dom/mdsal-dom-api/src/test/java/org/opendaylight/controller/md/sal/dom/api/AbstractDOMDataTreeServiceTestSuite.java
dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTree.java
dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeProducer.java
dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeWriteTransaction.java [new file with mode: 0644]
dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataWriteTransaction.java [deleted file]
dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataWriteTransactionTest.java
dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeProducerMultiShardTest.java
dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeProducerSingleShardTest.java
dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeTest.java [new file with mode: 0644]
dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/AbstractDOMShardTreeChangePublisher.java
dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/DOMDataTreeShardWriteTransaction.java
dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InMemoryDOMDataTreeShard.java
dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InMemoryDOMDataTreeShardChangePublisher.java
dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InmemoryDOMDataTreeShardWriteTransaction.java

index 4b0c902eeccd425a646ccbbfde5ef6617439335f..c33c4a5ad15c3c5045004a62bb50d06530a1a4f7 100644 (file)
       <groupId>org.opendaylight.yangtools</groupId>
       <artifactId>yang-common</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>yang-data-api</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.osgi</groupId>
       <artifactId>org.osgi.core</artifactId>
diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/CursorAwareWriteTransaction.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/CursorAwareWriteTransaction.java
new file mode 100644 (file)
index 0000000..839c19c
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.api;
+
+import com.google.common.util.concurrent.CheckedFuture;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+
+/**
+ * Write transaction that provides cursor's with write access to the data tree.
+ */
+public interface CursorAwareWriteTransaction extends DataTreeCursorProvider {
+
+    /**
+     * Create a {@link DataTreeWriteCursor} anchored at the specified path.
+     * There can only be one cursor open at a time.
+     *
+     * @param path Path at which the cursor is to be anchored
+     * @return write cursor at the desired location.
+     * @throws IllegalStateException when there's an open cursor, or this transaction is closed already.
+     */
+    @Nullable
+    @Override
+    <T extends DataObject> DataTreeWriteCursor createCursor(@Nonnull DataTreeIdentifier<T> path);
+
+    /**
+     * Cancels the transaction.
+     *
+     * Transactions can only be cancelled if it was not yet submited.
+     *
+     * Invoking cancel() on failed or already canceled will have no effect, and transaction is
+     * considered cancelled.
+     *
+     * Invoking cancel() on finished transaction (future returned by {@link #submit()} already
+     * successfully completed) will always fail (return false).
+     *
+     * @return <tt>false</tt> if the task could not be cancelled, typically because it has already
+     *         completed normally; <tt>true</tt> otherwise
+     *
+     */
+    boolean cancel();
+
+    /**
+     * Submits this transaction to be asynchronously applied to update the logical data tree. The
+     * returned CheckedFuture conveys the result of applying the data changes.
+     * <p>
+     * <b>Note:</b> It is strongly recommended to process the CheckedFuture result in an
+     * asynchronous manner rather than using the blocking get() method.
+     *
+     * This call logically seals the transaction, which prevents the client from further changing
+     * data tree using this transaction's cursor. Any subsequent calls to
+     * <code>createCursorCursor(DOMDataTreeIdentifier</code>
+     * or any of the cursor's methods will fail with {@link IllegalStateException}.
+     *
+     * The transaction is marked as submitted and enqueued into the shard back-end for
+     * processing.
+     */
+    CheckedFuture<Void,TransactionCommitFailedException> submit();
+}
diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeCursor.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeCursor.java
new file mode 100644 (file)
index 0000000..84895ac
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.api;
+
+import com.google.common.annotations.Beta;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.opendaylight.yangtools.yang.binding.DataContainer;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
+
+
+/**
+ * A cursor holding a logical position within a conceptual data tree. It allows operations relative
+ * to that position, as well as moving the position up or down the tree.
+ */
+@Beta
+@NotThreadSafe
+public interface DataTreeCursor extends AutoCloseable {
+    /**
+     * Move the cursor to the specified child of the current position.
+     *
+     * @param child Child identifier
+     * @throws IllegalArgumentException when specified identifier does not identify a valid child,
+     *         or if that child is not an instance of {@link DataContainer}.
+     */
+    void enter(@Nonnull PathArgument child);
+
+    /**
+     * Move the cursor to the specified child of the current position. This is the equivalent of
+     * multiple invocations of {@link #enter(PathArgument)}, except the operation is performed all
+     * at once.
+     *
+     * @param path Nested child identifier
+     * @throws IllegalArgumentException when specified path does not identify a valid child, or if
+     *         that child is not an instance of {@link DataContainer}.
+     */
+    void enter(@Nonnull PathArgument... path);
+
+    /**
+     * Move the cursor to the specified child of the current position. This is equivalent to
+     * {@link #enter(PathArgument...)}, except it takes an {@link Iterable} argument.
+     *
+     * @param path Nested child identifier
+     * @throws IllegalArgumentException when specified path does not identify a valid child, or if
+     *         that child is not an instance of {@link DataContainer}.
+     */
+    void enter(@Nonnull Iterable<PathArgument> path);
+
+    /**
+     * Move the cursor up to the parent of current position. This is equivalent of invoking
+     * <code>exit(1)</code>.
+     *
+     * @throws IllegalStateException when exiting would violate containment, typically by attempting
+     *         to exit more levels than previously entered.
+     */
+    void exit();
+
+    /**
+     * Move the cursor up by specified amounts of steps from the current position. This is
+     * equivalent of invoking {@link #exit()} multiple times, except the operation is performed
+     * atomically.
+     *
+     * @param depth number of steps to exit
+     * @throws IllegalArgumentException when depth is negative.
+     * @throws IllegalStateException when exiting would violate containment, typically by attempting
+     *         to exit more levels than previously entered.
+     */
+    void exit(int depth);
+
+    /**
+     * Close this cursor. Attempting any further operations on the cursor will lead to undefined
+     * behavior.
+     */
+    @Override
+    void close();
+}
diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeCursorProvider.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeCursorProvider.java
new file mode 100644 (file)
index 0000000..003afcf
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.api;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+
+/**
+ * Provides access to {#link DataTreeCursor}'s anchored at the specified path.
+ */
+public interface DataTreeCursorProvider {
+
+    /**
+     * Create a new {@link DataTreeCursor} at specified path. May fail if specified path
+     * does not exist.
+     *
+     * @param path Path at which the cursor is to be anchored
+     * @return A new cursor, or null if the path does not exist.
+     * @throws IllegalStateException if there is another cursor currently open, or the transaction
+     *         is already closed (closed or submitted).
+     */
+    @Nullable
+    <T extends DataObject> DataTreeCursor createCursor(@Nonnull DataTreeIdentifier<T> path);
+}
index 07e2559269e8419e7a629b6e113409ed9630f699..0520b40e2112ccd0d0829351a028b67e4afaa0e5 100644 (file)
@@ -48,7 +48,7 @@ public interface DataTreeProducer extends DataTreeProducerFactory, AutoCloseable
      *        is processed separately from any preceding transactions. Non-barrier transactions may
      *        be merged and processed in a batch, such that any observers see the modifications
      *        contained in them as if the modifications were made in a single transaction.
-     * @return A new {@link WriteTransaction}
+     * @return A new {@link CursorAwareWriteTransaction}
      * @throws IllegalStateException if a previous transaction was not closed.
      * @throws IllegalThreadStateException if the calling thread context does not match the
      *         lifecycle rules enforced by the producer state (e.g. bound or unbound). This
@@ -56,7 +56,7 @@ public interface DataTreeProducer extends DataTreeProducerFactory, AutoCloseable
      *         correct operation.
      */
     @Nonnull
-    WriteTransaction createTransaction(boolean isolated);
+    CursorAwareWriteTransaction createTransaction(boolean isolated);
 
     /**
      * {@inheritDoc}
diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeWriteCursor.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeWriteCursor.java
new file mode 100644 (file)
index 0000000..653c6a4
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.api;
+
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.BackendFailedException;
+
+/**
+ * {@inheritDoc}
+ *
+ * In addition this cursor also provides write operations(delete, merge, write).
+ */
+public interface DataTreeWriteCursor extends DataTreeCursor {
+
+    /**
+     * Delete the specified child.
+     *
+     * @param child Child identifier
+     * @throws BackendFailedException when implementation-specific errors occurs while servicing the
+     *         request.
+     */
+    void delete(PathArgument child);
+
+    /**
+     * Merge the specified data with the currently-present data at specified path.
+     *
+     * @param child Child identifier
+     * @param data Data to be merged
+     * @throws BackendFailedException when implementation-specific errors occurs while servicing the
+     *         request.
+     */
+    <T extends DataObject> void merge(PathArgument child, T data);
+
+    /**
+     * Replace the data at specified path with supplied data.
+     *
+     * @param child Child identifier
+     * @param data New node data
+     * @throws BackendFailedException when implementation-specific errors occurs while servicing the
+     *         request.
+     */
+    <T extends DataObject> void write(PathArgument child, T data);
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMCursorAwareWriteTransactionAdapter.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMCursorAwareWriteTransactionAdapter.java
new file mode 100644 (file)
index 0000000..b01dd99
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.dom.adapter;
+
+import com.google.common.util.concurrent.CheckedFuture;
+import javax.annotation.Nullable;
+import org.opendaylight.mdsal.binding.api.CursorAwareWriteTransaction;
+import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
+import org.opendaylight.mdsal.binding.api.DataTreeWriteCursor;
+import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+public class BindingDOMCursorAwareWriteTransactionAdapter<T extends DOMDataTreeCursorAwareTransaction> implements CursorAwareWriteTransaction {
+
+    private T delegate;
+    private BindingToNormalizedNodeCodec codec;
+
+    public BindingDOMCursorAwareWriteTransactionAdapter(final T delegate, final BindingToNormalizedNodeCodec codec) {
+
+        this.delegate = delegate;
+        this.codec = codec;
+    }
+
+    @Nullable
+    @Override
+    public <T extends DataObject> DataTreeWriteCursor createCursor(final DataTreeIdentifier<T> path) {
+        final YangInstanceIdentifier yPath = codec.toNormalized(path.getRootIdentifier());
+        final DOMDataTreeWriteCursor cursor = delegate.createCursor(new DOMDataTreeIdentifier(path.getDatastoreType(), yPath));
+        return new BindingDOMDataTreeWriteCursorAdapter<>(path, cursor, codec);
+    }
+
+    @Override
+    public boolean cancel() {
+        return delegate.cancel();
+    }
+
+    @Override
+    public CheckedFuture<Void, TransactionCommitFailedException> submit() {
+        return delegate.submit();
+    }
+
+}
index 6cf6d02a5c16f31b7e7c2c9a307ce2bb7090e3be..dc74a0121b4108d3ae16ded42ddc32d5c43fc9e6 100644 (file)
@@ -9,10 +9,12 @@ package org.opendaylight.mdsal.binding.dom.adapter;
 
 import com.google.common.base.Preconditions;
 import java.util.Collection;
+import org.opendaylight.mdsal.binding.api.CursorAwareWriteTransaction;
 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
 import org.opendaylight.mdsal.binding.api.DataTreeProducer;
 import org.opendaylight.mdsal.binding.api.DataTreeProducerException;
 import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerBusyException;
@@ -32,9 +34,9 @@ class BindingDOMDataTreeProducerAdapter implements DataTreeProducer {
     }
 
     @Override
-    public WriteTransaction createTransaction(final boolean isolated) {
-        final DOMDataTreeWriteTransaction domTx = delegate.createTransaction(isolated);
-        return new BindingDOMWriteTransactionAdapter<DOMDataTreeWriteTransaction>(domTx, codec);
+    public CursorAwareWriteTransaction createTransaction(final boolean isolated) {
+        final DOMDataTreeCursorAwareTransaction domTx = delegate.createTransaction(isolated);
+        return new BindingDOMCursorAwareWriteTransactionAdapter<>(domTx, codec);
     }
 
     static DataTreeProducer create(final DOMDataTreeProducer domProducer,
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeWriteCursorAdapter.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeWriteCursorAdapter.java
new file mode 100644 (file)
index 0000000..448c0bb
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding.dom.adapter;
+
+import java.util.AbstractMap.SimpleEntry;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Map.Entry;
+import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
+import org.opendaylight.mdsal.binding.api.DataTreeWriteCursor;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class BindingDOMDataTreeWriteCursorAdapter<T extends DOMDataTreeWriteCursor> implements DataTreeWriteCursor {
+
+    private T delegate;
+    private BindingToNormalizedNodeCodec codec;
+    private final Deque<PathArgument> stack = new ArrayDeque<>();
+
+    public BindingDOMDataTreeWriteCursorAdapter(final DataTreeIdentifier<?> path, final T delegate, final BindingToNormalizedNodeCodec codec) {
+
+        this.delegate = delegate;
+        this.codec = codec;
+        path.getRootIdentifier().getPathArguments().forEach(stack::push);
+    }
+
+    private YangInstanceIdentifier.PathArgument convertToNormalized(final PathArgument child) {
+        stack.push(child);
+        final InstanceIdentifier<?> iid = InstanceIdentifier.create(stack);
+        final YangInstanceIdentifier ret = codec.toNormalized(iid);
+        stack.pop();
+        return ret.getLastPathArgument();
+    }
+
+    private <T extends DataObject> Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> convertToNormalized(final PathArgument child, final T data) {
+        stack.push(child);
+        final InstanceIdentifier<?> iid = InstanceIdentifier.create(stack);
+        final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry = codec.toNormalizedNode(new SimpleEntry<>(iid, data));
+        stack.pop();
+        return entry;
+    }
+
+    @Override
+    public void delete(final PathArgument child) {
+        delegate.delete(convertToNormalized(child));
+    }
+
+    @Override
+    public <T extends DataObject> void merge(final PathArgument child, final T data) {
+        final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry = convertToNormalized(child, data);
+        delegate.merge(entry.getKey().getLastPathArgument(), entry.getValue());
+    }
+
+    @Override
+    public <T extends DataObject> void write(PathArgument child, T data) {
+        final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry = convertToNormalized(child, data);
+        delegate.write(entry.getKey().getLastPathArgument(), entry.getValue());
+    }
+
+    @Override
+    public void enter(final PathArgument child) {
+        stack.push(child);
+    }
+
+    @Override
+    public void enter(final PathArgument... path) {
+        for (final PathArgument pathArgument : path) {
+            enter(pathArgument);
+        }
+    }
+
+    @Override
+    public void enter(final Iterable<PathArgument> path) {
+        path.forEach(this::enter);
+    }
+
+    @Override
+    public void exit() {
+        stack.pop();
+    }
+
+    @Override
+    public void exit(int depth) {
+        for (int i = 0; i < depth; i++) {
+            exit();
+        }
+    }
+
+    @Override
+    public void close() {
+        delegate.close();
+    }
+}
index 075dd9d04d6c5dde9914353c4da643e94a77f3cf..63cba172b7da9fcbc097f8882bd3e3bfeacc9c0a 100644 (file)
@@ -25,6 +25,7 @@ public interface DOMDataTreeCommitCohortRegistry extends DOMDataBrokerExtension
      *
      * @param path Subtree path on which commit cohort operates.
      * @param cohort Commit cohort
+     * @param <T> Cohort subclass
      * @return Registaration object for DOM Data Three Commit cohort.
      */
     <T extends DOMDataTreeCommitCohort> DOMDataTreeCommitCohortRegistration<T> registerCommitCohort(
diff --git a/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeCursorAwareTransaction.java b/dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeCursorAwareTransaction.java
new file mode 100644 (file)
index 0000000..b4abfb2
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.dom.api;
+
+import com.google.common.util.concurrent.CheckedFuture;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
+import org.opendaylight.yangtools.concepts.Identifiable;
+
+/**
+ * Write transaction that provides cursor's with write access to the data tree.
+ */
+public interface DOMDataTreeCursorAwareTransaction extends DOMDataTreeCursorProvider, Identifiable<Object> {
+
+    /**
+     * Create a {@link DOMDataTreeWriteCursor} anchored at the specified path.
+     * There can only be one cursor open at a time.
+     *
+     * @param path Path at which the cursor is to be anchored
+     * @return write cursor at the desired location.
+     * @throws IllegalStateException when there's an open cursor, or this transaction is closed already.
+     */
+    @Nullable
+    @Override
+    DOMDataTreeWriteCursor createCursor(@Nonnull DOMDataTreeIdentifier path);
+
+    /**
+     * Cancels the transaction.
+     *
+     * Transactions can only be cancelled if it was not yet submited.
+     *
+     * Invoking cancel() on failed or already canceled will have no effect, and transaction is
+     * considered cancelled.
+     *
+     * Invoking cancel() on finished transaction (future returned by {@link #submit()} already
+     * successfully completed) will always fail (return false).
+     *
+     * @return <tt>false</tt> if the task could not be cancelled, typically because it has already
+     *         completed normally; <tt>true</tt> otherwise
+     *
+     */
+    boolean cancel();
+
+    /**
+     * Submits this transaction to be asynchronously applied to update the logical data tree. The
+     * returned CheckedFuture conveys the result of applying the data changes.
+     * <p>
+     * <b>Note:</b> It is strongly recommended to process the CheckedFuture result in an
+     * asynchronous manner rather than using the blocking get() method.
+     *
+     * This call logically seals the transaction, which prevents the client from further changing
+     * data tree using this transaction's cursor. Any subsequent calls to
+     * <code>createCursorCursor(DOMDataTreeIdentifier</code>
+     * or any of the cursor's methods will fail with {@link IllegalStateException}.
+     *
+     * The transaction is marked as submitted and enqueued into the shard back-end for
+     * processing.
+     *
+     * @return Checked future informing of success/failure
+     */
+    CheckedFuture<Void,TransactionCommitFailedException> submit();
+
+}
index eb11d510cfa6f5672e17609e119a380c2fc3159d..7ff7d96487ddb66b53b16cff98e77bf44ff31679 100644 (file)
@@ -47,14 +47,14 @@ public interface DOMDataTreeProducer extends DOMDataTreeProducerFactory, AutoClo
      *        is processed separately from any preceding transactions. Non-barrier transactions may
      *        be merged and processed in a batch, such that any observers see the modifications
      *        contained in them as if the modifications were made in a single transaction.
-     * @return A new {@link DOMDataTreeWriteTransaction}
+     * @return A new {@link DOMDataTreeCursorAwareTransaction}
      * @throws IllegalStateException if a previous transaction was not closed.
      * @throws IllegalThreadStateException if the calling thread context does not match the
      *         lifecycle rules enforced by the producer state (e.g. bound or unbound). This
      *         exception is thrown on a best effort basis and programs should not rely on it for
      *         correct operation.
      */
-    @Nonnull DOMDataTreeWriteTransaction createTransaction(boolean isolated);
+    @Nonnull DOMDataTreeCursorAwareTransaction createTransaction(boolean isolated);
 
     /**
      * {@inheritDoc}
index c0448a12e812271b6f61b391cad0fcfac0ef9c2a..979cc533b086861d371fe67ea564c50ef47b602b 100644 (file)
@@ -12,10 +12,13 @@ import static org.junit.Assert.assertNotNull;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
 
+import org.opendaylight.mdsal.dom.api.DOMDataTreeCursor;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerException;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeService;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
 import com.google.common.util.concurrent.CheckedFuture;
 import java.net.URI;
@@ -55,16 +58,20 @@ public abstract class AbstractDOMDataTreeServiceTestSuite {
      * @throws DOMDataTreeProducerException
      * @throws TransactionCommitFailedException
      */
+
     @Test
     public final void testBasicProducer() throws DOMDataTreeProducerException, TransactionCommitFailedException {
         // Create a producer. It is an AutoCloseable resource, hence the try-with pattern
         try (final DOMDataTreeProducer prod = service().createProducer(Collections.singleton(UNORDERED_CONTAINER_TREE))) {
             assertNotNull(prod);
 
-            final DOMDataTreeWriteTransaction tx = prod.createTransaction(true);
+            final DOMDataTreeCursorAwareTransaction tx = prod.createTransaction(true);
             assertNotNull(tx);
 
-            tx.put(LogicalDatastoreType.OPERATIONAL, UNORDERED_CONTAINER_IID, ImmutableContainerNodeBuilder.create().build());
+            final DOMDataTreeWriteCursor cursor = tx.createCursor(new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, UNORDERED_CONTAINER_IID));
+            assertNotNull(cursor);
+            cursor.write(UNORDERED_CONTAINER_IID.getLastPathArgument(), ImmutableContainerNodeBuilder.create().build());
+            cursor.close();
 
             final CheckedFuture<Void, TransactionCommitFailedException> f = tx.submit();
             assertNotNull(f);
index 5a585dfe2a09eaaeef53e1fe3d6d77670ee231d8..764d7eb8ef3384aac335a4ff0c92be899cd50993 100644 (file)
@@ -129,12 +129,12 @@ public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTree
         Preconditions.checkArgument(!subtrees.isEmpty(), "Subtrees may not be empty");
 
         final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap = new HashMap<>();
-        for (final DOMDataTreeIdentifier s : subtrees) {
+        for (final DOMDataTreeIdentifier subtree : subtrees) {
             // Attempting to create a disconnected producer -- all subtrees have to be unclaimed
-            final DOMDataTreeProducer producer = findProducer(s);
-            Preconditions.checkArgument(producer == null, "Subtree %s is attached to producer %s", s, producer);
+            final DOMDataTreeProducer producer = findProducer(subtree);
+            Preconditions.checkArgument(producer == null, "Subtree %s is attached to producer %s", subtree, producer);
 
-            shardMap.put(s, shards.lookup(s).getValue().getInstance());
+            shardMap.put(subtree, shards.lookup(subtree).getValue().getInstance());
         }
 
         return createProducer(shardMap);
index 83a073d70149325a23bb4db67d300b55d4b5817e..f16f0ad1f3cb5422473c02af68c8baeafd5db440 100644 (file)
 package org.opendaylight.mdsal.dom.broker;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableBiMap.Builder;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.Queue;
 import java.util.Set;
 import javax.annotation.concurrent.GuardedBy;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerBusyException;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerException;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeShard;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
-import org.opendaylight.mdsal.dom.spi.store.DOMStore;
-import org.opendaylight.mdsal.dom.spi.store.DOMStoreTransactionChain;
-import org.opendaylight.mdsal.dom.spi.store.DOMStoreWriteTransaction;
+import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardProducer;
+import org.opendaylight.mdsal.dom.store.inmemory.WriteableDOMDataTreeShard;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 final class ShardedDOMDataTreeProducer implements DOMDataTreeProducer {
     private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTreeProducer.class);
-    private final BiMap<DOMDataTreeShard, DOMStoreTransactionChain> shardToChain;
     private final Map<DOMDataTreeIdentifier, DOMDataTreeShard> idToShard;
     private final ShardedDOMDataTree dataTree;
 
+    private final BiMap<DOMDataTreeShard, DOMDataTreeShardProducer> shardToProducer;
+    private final BiMap<DOMDataTreeIdentifier, DOMDataTreeShardProducer> idToProducer;
+
     @GuardedBy("this")
-    private Map<DOMDataTreeIdentifier, DOMDataTreeProducer> children = Collections.emptyMap();
+    private DOMDataTreeCursorAwareTransaction openTx;
     @GuardedBy("this")
-    private DOMDataTreeWriteTransaction openTx;
+    private Map<DOMDataTreeIdentifier, DOMDataTreeProducer> children = Collections.emptyMap();
     @GuardedBy("this")
     private boolean closed;
 
     @GuardedBy("this")
     private ShardedDOMDataTreeListenerContext<?> attachedListener;
 
-    ShardedDOMDataTreeProducer(final ShardedDOMDataTree dataTree, final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap, final Set<DOMDataTreeShard> shards) {
+    ShardedDOMDataTreeProducer(final ShardedDOMDataTree dataTree,
+                               final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap,
+                               final Multimap<DOMDataTreeShard, DOMDataTreeIdentifier> shardToId) {
         this.dataTree = Preconditions.checkNotNull(dataTree);
 
-        // Create shard -> chain map
-        final Builder<DOMDataTreeShard, DOMStoreTransactionChain> cb = ImmutableBiMap.builder();
-        final Queue<Exception> es = new LinkedList<>();
-
-        for (final DOMDataTreeShard s : shards) {
-            if (s instanceof DOMStore) {
-                try {
-                    final DOMStoreTransactionChain c = ((DOMStore)s).createTransactionChain();
-                    LOG.trace("Using DOMStore chain {} to access shard {}", c, s);
-                    cb.put(s, c);
-                } catch (final Exception e) {
-                    LOG.error("Failed to instantiate chain for shard {}", s, e);
-                    es.add(e);
+        final Builder<DOMDataTreeShard, DOMDataTreeShardProducer> builder = ImmutableBiMap.builder();
+        final Builder<DOMDataTreeIdentifier, DOMDataTreeShardProducer> idToProducerBuilder = ImmutableBiMap.builder();
+        for (final Entry<DOMDataTreeShard, Collection<DOMDataTreeIdentifier>> entry : shardToId.asMap().entrySet()) {
+            if (entry.getKey() instanceof WriteableDOMDataTreeShard) {
+                //create a single producer for all prefixes in a single shard
+                final DOMDataTreeShardProducer producer = ((WriteableDOMDataTreeShard) entry.getKey()).createProducer(entry.getValue());
+                builder.put(entry.getKey(), producer);
+
+                // id mapped to producers
+                for (final DOMDataTreeIdentifier id : entry.getValue()) {
+                    idToProducerBuilder.put(id, producer);
                 }
             } else {
-                LOG.error("Unhandled shard instance type {}", s.getClass());
+                LOG.error("Unable to create a producer for shard that's not a WriteableDOMDataTreeShard");
             }
         }
-        this.shardToChain = cb.build();
-
-        // An error was encountered, close chains and report the error
-        if (shardToChain.size() != shards.size()) {
-            for (final DOMStoreTransactionChain c : shardToChain.values()) {
-                try {
-                    c.close();
-                } catch (final Exception e) {
-                    LOG.warn("Exception raised while closing chain {}", c, e);
-                }
-            }
-
-            final IllegalStateException e = new IllegalStateException("Failed to completely allocate contexts", es.poll());
-            while (!es.isEmpty()) {
-                e.addSuppressed(es.poll());
-            }
+        this.shardToProducer = builder.build();
+        this.idToProducer = idToProducerBuilder.build();
+        idToShard = ImmutableMap.copyOf(shardMap);
+    }
 
-            throw e;
+    static DOMDataTreeProducer create(final ShardedDOMDataTree dataTree, final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
+        final Multimap<DOMDataTreeShard, DOMDataTreeIdentifier> shardToIdetifiers = ArrayListMultimap.create();
+        // map which identifier belongs to which shard
+        for (final Entry<DOMDataTreeIdentifier, DOMDataTreeShard> entry : shardMap.entrySet()) {
+            shardToIdetifiers.put(entry.getValue(), entry.getKey());
         }
 
-        idToShard = ImmutableMap.copyOf(shardMap);
+        return new ShardedDOMDataTreeProducer(dataTree, shardMap, shardToIdetifiers);
     }
 
     @Override
-    public synchronized DOMDataTreeWriteTransaction createTransaction(final boolean isolated) {
+    public synchronized DOMDataTreeCursorAwareTransaction createTransaction(final boolean isolated) {
         Preconditions.checkState(!closed, "Producer is already closed");
         Preconditions.checkState(openTx == null, "Transaction %s is still open", openTx);
 
-        // Allocate backing transactions
-        final Map<DOMDataTreeShard, DOMStoreWriteTransaction> shardToTx = new HashMap<>();
-        for (final Entry<DOMDataTreeShard, DOMStoreTransactionChain> e : shardToChain.entrySet()) {
-            shardToTx.put(e.getKey(), e.getValue().newWriteOnlyTransaction());
-        }
+        this.openTx = new ShardedDOMDataTreeWriteTransaction(this, idToProducer, children);
 
-        // Create the ID->transaction map
-        final ImmutableMap.Builder<DOMDataTreeIdentifier, DOMStoreWriteTransaction> b = ImmutableMap.builder();
-        for (final Entry<DOMDataTreeIdentifier, DOMDataTreeShard> e : idToShard.entrySet()) {
-            b.put(e.getKey(), shardToTx.get(e.getValue()));
-        }
-
-        final ShardedDOMDataWriteTransaction ret = new ShardedDOMDataWriteTransaction(this, b.build());
-        openTx = ret;
-        return ret;
+        return openTx;
     }
 
     @GuardedBy("this")
@@ -169,7 +148,7 @@ final class ShardedDOMDataTreeProducer implements DOMDataTreeProducer {
         return ret;
     }
 
-    boolean isDelegatedToChild(DOMDataTreeIdentifier path) {
+    boolean isDelegatedToChild(final DOMDataTreeIdentifier path) {
         for (final DOMDataTreeIdentifier c : children.keySet()) {
             if (c.contains(path)) {
                 return true;
@@ -191,24 +170,11 @@ final class ShardedDOMDataTreeProducer implements DOMDataTreeProducer {
         }
     }
 
-    static DOMDataTreeProducer create(final ShardedDOMDataTree dataTree, final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
-        /*
-         * FIXME: we do not allow multiple multiple shards in a producer because we do not implement the
-         *        synchronization primitives yet
-         */
-        final Set<DOMDataTreeShard> shards = ImmutableSet.copyOf(shardMap.values());
-        if (shards.size() > 1) {
-            throw new UnsupportedOperationException("Cross-shard producers are not supported yet");
-        }
-
-        return new ShardedDOMDataTreeProducer(dataTree, shardMap, shards);
-    }
-
     Set<DOMDataTreeIdentifier> getSubtrees() {
         return idToShard.keySet();
     }
 
-    synchronized void cancelTransaction(final ShardedDOMDataWriteTransaction transaction) {
+    synchronized void cancelTransaction(final ShardedDOMDataTreeWriteTransaction transaction) {
         if (!openTx.equals(transaction)) {
             LOG.warn("Transaction {} is not open in producer {}", transaction, this);
             return;
@@ -218,12 +184,12 @@ final class ShardedDOMDataTreeProducer implements DOMDataTreeProducer {
         openTx = null;
     }
 
-    synchronized void transactionSubmitted(ShardedDOMDataWriteTransaction transaction) {
+    synchronized void transactionSubmitted(final ShardedDOMDataTreeWriteTransaction transaction) {
         Preconditions.checkState(openTx.equals(transaction));
         openTx = null;
     }
 
-    synchronized void boundToListener(ShardedDOMDataTreeListenerContext<?> listener) {
+    synchronized void boundToListener(final ShardedDOMDataTreeListenerContext<?> listener) {
         // FIXME: Add option to dettach
         Preconditions.checkState(this.attachedListener == null,
                 "Producer %s is already attached to other listener.",
diff --git a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeWriteTransaction.java b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeWriteTransaction.java
new file mode 100644 (file)
index 0000000..d6f44ba
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.dom.broker;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Map.Entry;
+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.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor;
+import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardProducer;
+import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardWriteTransaction;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@NotThreadSafe
+final class ShardedDOMDataTreeWriteTransaction implements DOMDataTreeCursorAwareTransaction {
+    private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTreeWriteTransaction.class);
+    private static final AtomicLong COUNTER = new AtomicLong();
+    private final Map<DOMDataTreeIdentifier, DOMDataTreeShardWriteTransaction> idToTransaction;
+    private final ShardedDOMDataTreeProducer producer;
+    private final String identifier;
+    private final Set<YangInstanceIdentifier> childBoundaries = new HashSet<>();
+    @GuardedBy("this")
+    private boolean closed =  false;
+
+    @GuardedBy("this")
+    private DOMDataTreeWriteCursor openCursor;
+
+    ShardedDOMDataTreeWriteTransaction(final ShardedDOMDataTreeProducer producer,
+                                       final Map<DOMDataTreeIdentifier, DOMDataTreeShardProducer> idToProducer,
+                                       final Map<DOMDataTreeIdentifier, DOMDataTreeProducer> childProducers) {
+        this.producer = Preconditions.checkNotNull(producer);
+        idToTransaction = new HashMap<>();
+        Preconditions.checkNotNull(idToProducer).forEach((id, prod) -> idToTransaction.put(id, prod.createTransaction()));
+        this.identifier = "SHARDED-DOM-" + COUNTER.getAndIncrement();
+        childProducers.forEach((id, prod) -> childBoundaries.add(id.getRootIdentifier()));
+    }
+
+    // FIXME: use atomic operations
+    @GuardedBy("this")
+    private DOMDataTreeShardWriteTransaction lookup(final DOMDataTreeIdentifier prefix) {
+        for (final Entry<DOMDataTreeIdentifier, DOMDataTreeShardWriteTransaction> e : idToTransaction.entrySet()) {
+            if (e.getKey().contains(prefix)) {
+                Preconditions.checkArgument(!producer.isDelegatedToChild(prefix),
+                        "Path %s is delegated to child producer.",
+                        prefix);
+                return e.getValue();
+            }
+        }
+        throw new IllegalArgumentException(String.format("Path %s is not accessible from transaction %s", prefix, this));
+    }
+
+    @Override
+    public String getIdentifier() {
+        return identifier;
+    }
+
+    @Override
+    public synchronized boolean cancel() {
+        if (closed) {
+            return false;
+        }
+
+        LOG.debug("Cancelling transaction {}", identifier);
+        if (openCursor != null) {
+            openCursor.close();
+        }
+        for (final DOMDataTreeShardWriteTransaction tx : ImmutableSet.copyOf(idToTransaction.values())) {
+            tx.close();
+        }
+
+        closed = true;
+        producer.cancelTransaction(this);
+        return true;
+    }
+
+    @Override
+    public synchronized DOMDataTreeWriteCursor createCursor(final DOMDataTreeIdentifier prefix) {
+        Preconditions.checkState(!closed, "Transaction is closed already");
+        Preconditions.checkState(openCursor == null, "There is still a cursor open");
+        final DOMDataTreeShardWriteTransaction lookup = lookup(prefix);
+        openCursor = new DelegatingCursor(lookup.createCursor(prefix), prefix);
+        return openCursor;
+    }
+
+    @Override
+    public synchronized CheckedFuture<Void, TransactionCommitFailedException> submit() {
+        Preconditions.checkState(!closed, "Transaction %s is already closed", identifier);
+        Preconditions.checkState(openCursor == null, "Cannot submit transaction while there is a cursor open");
+
+        final Set<DOMDataTreeShardWriteTransaction> txns = ImmutableSet.copyOf(idToTransaction.values());
+        for (final DOMDataTreeShardWriteTransaction tx : txns) {
+            tx.ready();
+        }
+        producer.transactionSubmitted(this);
+        try {
+            return Futures.immediateCheckedFuture(new SubmitCoordinationTask(identifier, txns).call());
+        } catch (final TransactionCommitFailedException e) {
+            return Futures.immediateFailedCheckedFuture(e);
+        }
+    }
+
+    synchronized void cursorClosed() {
+        openCursor = null;
+    }
+
+    private class DelegatingCursor implements DOMDataTreeWriteCursor {
+
+        private final DOMDataTreeWriteCursor delegate;
+        private final Deque<PathArgument> path = new LinkedList<>();
+
+        public DelegatingCursor(final DOMDataTreeWriteCursor delegate, final DOMDataTreeIdentifier rootPosition) {
+            this.delegate = delegate;
+            path.addAll(rootPosition.getRootIdentifier().getPathArguments());
+        }
+
+        @Override
+        public void enter(@Nonnull final PathArgument child) {
+            checkAvailable(child);
+            path.push(child);
+            delegate.enter(child);
+        }
+
+        @Override
+        public void enter(@Nonnull final PathArgument... path) {
+            for (final PathArgument pathArgument : path) {
+                enter(pathArgument);
+            }
+        }
+
+        @Override
+        public void enter(@Nonnull final Iterable<PathArgument> path) {
+            for (final PathArgument pathArgument : path) {
+                enter(pathArgument);
+            }
+        }
+
+        @Override
+        public void exit() {
+            path.pop();
+            delegate.exit();
+        }
+
+        @Override
+        public void exit(final int depth) {
+            for (int i = 0; i < depth; i++) {
+                path.pop();
+            }
+            delegate.exit(depth);
+        }
+
+        @Override
+        public void close() {
+            delegate.close();
+            cursorClosed();
+        }
+
+        @Override
+        public void delete(final PathArgument child) {
+            checkAvailable(child);
+            delegate.delete(child);
+        }
+
+        @Override
+        public void merge(final PathArgument child, final NormalizedNode<?, ?> data) {
+            checkAvailable(child);
+            delegate.merge(child, data);
+        }
+
+        @Override
+        public void write(final PathArgument child, final NormalizedNode<?, ?> data) {
+            checkAvailable(child);
+            delegate.write(child, data);
+        }
+
+        void checkAvailable(final PathArgument child) {
+            path.add(child);
+            final YangInstanceIdentifier yid = YangInstanceIdentifier.create(path);
+            childBoundaries.forEach(id -> {
+                if (id.contains(yid)) {
+                    path.removeLast();
+                    throw new IllegalArgumentException("Path {" + yid + "} is not available to this cursor since it's already claimed by a child producer");
+                }
+            });
+            path.removeLast();
+        }
+    }
+
+    private static class SubmitCoordinationTask implements Callable<Void> {
+
+        private static final Logger LOG = LoggerFactory.getLogger(SubmitCoordinationTask.class);
+
+        private final String identifier;
+        private final Collection<DOMDataTreeShardWriteTransaction> transactions;
+
+        SubmitCoordinationTask(final String identifier,
+                                    final Collection<DOMDataTreeShardWriteTransaction> transactions) {
+            this.identifier = identifier;
+            this.transactions = transactions;
+        }
+
+        @Override
+        public Void call() throws TransactionCommitFailedException {
+
+            try {
+                LOG.debug("Producer {}, submit started", identifier);
+                submitBlocking();
+
+                return null;
+            } catch (final TransactionCommitFailedException e) {
+                LOG.warn("Failure while submitting transaction for producer {}", identifier, e);
+                //FIXME abort here
+                throw e;
+            }
+        }
+
+        void submitBlocking() throws TransactionCommitFailedException {
+            for (final ListenableFuture<?> commit : submitAll()) {
+                try {
+                    commit.get();
+                } catch (InterruptedException | ExecutionException e) {
+                    throw new TransactionCommitFailedException("Submit failed", e);
+                }
+            }
+        }
+
+        private ListenableFuture<?>[] submitAll() {
+            final ListenableFuture<?>[] ops = new ListenableFuture<?>[transactions.size()];
+            int i = 0;
+            for (final DOMDataTreeShardWriteTransaction tx : transactions) {
+                ops[i++] = tx.submit();
+            }
+            return ops;
+        }
+    }
+}
diff --git a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataWriteTransaction.java b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataWriteTransaction.java
deleted file mode 100644 (file)
index 5a08336..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.mdsal.dom.broker;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.util.concurrent.CheckedFuture;
-import com.google.common.util.concurrent.Futures;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
-import javax.annotation.concurrent.GuardedBy;
-import javax.annotation.concurrent.NotThreadSafe;
-import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
-import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
-import org.opendaylight.mdsal.dom.spi.store.DOMStoreThreePhaseCommitCohort;
-import org.opendaylight.mdsal.dom.spi.store.DOMStoreWriteTransaction;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@NotThreadSafe
-final class ShardedDOMDataWriteTransaction implements DOMDataTreeWriteTransaction {
-    private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataWriteTransaction.class);
-    private static final AtomicLong COUNTER = new AtomicLong();
-    private final Map<DOMDataTreeIdentifier, DOMStoreWriteTransaction> idToTransaction;
-    private final ShardedDOMDataTreeProducer producer;
-    private final String identifier;
-    @GuardedBy("this")
-    private boolean closed =  false;
-
-    ShardedDOMDataWriteTransaction(final ShardedDOMDataTreeProducer producer, final Map<DOMDataTreeIdentifier, DOMStoreWriteTransaction> idToTransaction) {
-        this.producer = Preconditions.checkNotNull(producer);
-        this.idToTransaction = Preconditions.checkNotNull(idToTransaction);
-        this.identifier = "SHARDED-DOM-" + COUNTER.getAndIncrement();
-    }
-
-    // FIXME: use atomic operations
-    @GuardedBy("this")
-    private DOMStoreWriteTransaction lookup(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
-        final DOMDataTreeIdentifier id = new DOMDataTreeIdentifier(store, path);
-
-        for (final Entry<DOMDataTreeIdentifier, DOMStoreWriteTransaction> e : idToTransaction.entrySet()) {
-            if (e.getKey().contains(id)) {
-                Preconditions.checkArgument(!producer.isDelegatedToChild(id),
-                        "Path %s is delegated to child producer.",
-                        id);
-                return e.getValue();
-            }
-        }
-        throw new IllegalArgumentException(String.format("Path %s is not acessible from transaction %s", id, this));
-    }
-
-    @Override
-    public String getIdentifier() {
-        return identifier;
-    }
-
-    @Override
-    public synchronized boolean cancel() {
-        if (closed) {
-            return false;
-        }
-
-        LOG.debug("Cancelling transaction {}", identifier);
-        for (final DOMStoreWriteTransaction tx : ImmutableSet.copyOf(idToTransaction.values())) {
-            tx.close();
-        }
-
-        closed = true;
-        producer.cancelTransaction(this);
-        return true;
-    }
-
-    @Override
-    public synchronized CheckedFuture<Void, TransactionCommitFailedException> submit() {
-        Preconditions.checkState(!closed, "Transaction %s is already closed", identifier);
-
-        final Set<DOMStoreWriteTransaction> txns = ImmutableSet.copyOf(idToTransaction.values());
-        final List<DOMStoreThreePhaseCommitCohort> cohorts = new ArrayList<>(txns.size());
-        for (final DOMStoreWriteTransaction tx : txns) {
-            cohorts.add(tx.ready());
-        }
-        producer.transactionSubmitted(this);
-        try {
-            return Futures.immediateCheckedFuture(new CommitCoordinationTask(this, cohorts, null).call());
-        } catch (final TransactionCommitFailedException e) {
-            return Futures.immediateFailedCheckedFuture(e);
-        }
-    }
-
-    @Override
-    public synchronized void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
-        lookup(store, path).delete(path);
-    }
-
-    @Override
-    public synchronized void put(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
-        lookup(store, path).write(path, data);
-    }
-
-    @Override
-    public synchronized void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
-        lookup(store, path).merge(path, data);
-    }
-}
index 380633e247ecc2d42b86bc9846630618a043ad10..3cf4bfd61d483fd54b06d0f0324fba6690ee57b3 100644 (file)
@@ -9,109 +9,201 @@ package org.opendaylight.mdsal.dom.broker;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-
-import java.util.ArrayList;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import javax.annotation.Nonnull;
 import org.junit.Test;
+import org.mockito.Mock;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
-import org.opendaylight.mdsal.dom.spi.store.DOMStoreThreePhaseCommitCohort;
-import org.opendaylight.mdsal.dom.spi.store.DOMStoreWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor;
+import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardProducer;
+import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardWriteTransaction;
+import org.opendaylight.mdsal.dom.store.inmemory.WriteableDOMDataTreeShard;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 
 public class ShardedDOMDataWriteTransactionTest {
     private static final Map<YangInstanceIdentifier, List<NormalizedNode<?, ?>>> TEST_MAP = new HashMap<>();
 
+    private static final DOMDataTreeIdentifier ROOT_ID =
+            new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.EMPTY);
+
+    @Mock
+    private WriteableDOMDataTreeShard rootShard;
+
+    @Mock
+    private DOMDataTreeShardProducer mockedProducer;
+
     @Test
     public void basicTests() throws Exception {
+
+        initMocks(this);
+
+        doReturn(new TestDOMShardWriteTransaction()).when(mockedProducer).createTransaction();
+        doReturn(mockedProducer).when(rootShard).createProducer(any(Collection.class));
+
         final ShardedDOMDataTree shardedDOMDataTree =
                 new ShardedDOMDataTree();
-        final ShardedDOMDataTreeProducer shardedDOMDataTreeProducer =
-                new ShardedDOMDataTreeProducer(shardedDOMDataTree, new HashMap<>(), new HashSet<>());
-        final TestDOMStoreWriteTransaction testDOMStoreWriteTransaction =
-                new TestDOMStoreWriteTransaction();
-        final Map<DOMDataTreeIdentifier, DOMStoreWriteTransaction> idToTransaction =
-                new HashMap<>();
+        shardedDOMDataTree.registerDataTreeShard(ROOT_ID, rootShard);
         final YangInstanceIdentifier yangInstanceIdentifier = YangInstanceIdentifier.of(QName.create("test"));
-        idToTransaction.put(new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, yangInstanceIdentifier),
-                            testDOMStoreWriteTransaction);
 
-        final ShardedDOMDataWriteTransaction shardedDOMDataWriteTransaction =
-                new ShardedDOMDataWriteTransaction(shardedDOMDataTreeProducer, idToTransaction);
-        final ShardedDOMDataWriteTransaction otherShardedDOMDataWriteTransaction =
-                new ShardedDOMDataWriteTransaction(shardedDOMDataTreeProducer, idToTransaction);
+        final DOMDataTreeProducer producer = shardedDOMDataTree.createProducer(Collections.singletonList(ROOT_ID));
+        final DOMDataTreeCursorAwareTransaction transaction = producer.createTransaction(false);
+        final DOMDataTreeWriteCursor cursor = transaction.createCursor(ROOT_ID);
 
+        assertNotNull(cursor);
         assertFalse(TEST_MAP.containsKey(yangInstanceIdentifier));
-        shardedDOMDataWriteTransaction.put(
-                LogicalDatastoreType.OPERATIONAL, yangInstanceIdentifier, TestUtils.TEST_CONTAINER);
+        cursor.write(yangInstanceIdentifier.getLastPathArgument(), TestUtils.TEST_CONTAINER);
         assertTrue(TEST_MAP.containsKey(yangInstanceIdentifier));
-        shardedDOMDataWriteTransaction.delete(
-                LogicalDatastoreType.OPERATIONAL, yangInstanceIdentifier);
+
+        cursor.delete(yangInstanceIdentifier.getLastPathArgument());
         assertFalse(TEST_MAP.containsKey(yangInstanceIdentifier));
-        shardedDOMDataWriteTransaction.merge(
-                LogicalDatastoreType.OPERATIONAL, yangInstanceIdentifier, TestUtils.TEST_CONTAINER);
+
+        cursor.merge(yangInstanceIdentifier.getLastPathArgument(), TestUtils.TEST_CONTAINER);
         assertTrue(TEST_MAP.get(yangInstanceIdentifier).contains(TestUtils.TEST_CONTAINER));
 
         try {
-            shardedDOMDataWriteTransaction.put(
-                    LogicalDatastoreType.CONFIGURATION, yangInstanceIdentifier, TestUtils.TEST_CONTAINER);
-            fail("Expected IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains(LogicalDatastoreType.CONFIGURATION.toString()));
+            producer.createTransaction(false);
+            fail("Should have failed, there's still a tx open");
+        } catch (final IllegalStateException e) {
+            assertTrue(e.getMessage().contains("open"));
+        }
+
+        cursor.close();
+        try {
+            transaction.createCursor(new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.EMPTY));
+            fail("Should have failed, config ds not available to this tx");
+        } catch (final IllegalArgumentException e) {
+            assertTrue(e.getMessage().contains("not accessible"));
         }
 
-        shardedDOMDataTreeProducer.createTransaction(true);
-        assertTrue(shardedDOMDataWriteTransaction.cancel());
-        assertFalse(shardedDOMDataWriteTransaction.cancel());
+        assertTrue(transaction.cancel());
+        assertFalse(transaction.cancel());
 
-        assertTrue(shardedDOMDataWriteTransaction.getIdentifier().matches("^SHARDED-DOM-\\d$"));
-        assertNotEquals(shardedDOMDataWriteTransaction.getIdentifier(),
-                otherShardedDOMDataWriteTransaction.getIdentifier());
+        final DOMDataTreeCursorAwareTransaction newTx = producer.createTransaction(false);
+        assertTrue("Transaction identifier incorrect " + transaction.getIdentifier(), ((String) transaction.getIdentifier()).contains("SHARDED-DOM-"));
+        assertNotEquals(transaction.getIdentifier(),
+                newTx.getIdentifier());
     }
 
-    private final class TestDOMStoreWriteTransaction implements DOMStoreWriteTransaction {
+    private final class TestDOMShardWriteTransaction implements DOMDataTreeShardWriteTransaction {
+
+        @Nonnull
+        @Override
+        public DOMDataTreeWriteCursor createCursor(@Nonnull final DOMDataTreeIdentifier prefix) {
+            return new TestCursor();
+        }
 
         @Override
-        public void write(YangInstanceIdentifier path, NormalizedNode<?, ?> data) {
-            List<NormalizedNode<?, ?>> dataList = TEST_MAP.get(path);
-            if(dataList == null) dataList = new ArrayList<>();
-            dataList.add(data);
-            TEST_MAP.put(path, dataList);
+        public void ready() {
+
         }
 
         @Override
-        public void merge(YangInstanceIdentifier path, NormalizedNode<?, ?> data) {
-            List<NormalizedNode<?, ?>> dataList = TEST_MAP.get(path);
-            if(dataList == null) dataList = new ArrayList<>();
-            dataList.add(data);
-            TEST_MAP.put(path, dataList);
+        public void close() {
+
         }
 
         @Override
-        public void delete(YangInstanceIdentifier path) {
-            TEST_MAP.remove(path);
+        public ListenableFuture<Void> submit() {
+            return null;
         }
 
         @Override
-        public DOMStoreThreePhaseCommitCohort ready() {
+        public ListenableFuture<Boolean> validate() {
             return null;
         }
 
         @Override
-        public Object getIdentifier() {
+        public ListenableFuture<Void> prepare() {
             return null;
         }
 
+        @Override
+        public ListenableFuture<Void> commit() {
+            return null;
+        }
+    }
+
+    private final class TestCursor implements DOMDataTreeWriteCursor {
+
+        private final Deque<PathArgument> stack = new ArrayDeque<>();
+
+        @Override
+        public void delete(final PathArgument child) {
+            final ArrayDeque<PathArgument> newPath = new ArrayDeque<>(stack);
+            newPath.push(child);
+            TEST_MAP.remove(YangInstanceIdentifier.create(newPath));
+        }
+
+        @Override
+        public void merge(final PathArgument child, final NormalizedNode<?, ?> data) {
+            final ArrayDeque<PathArgument> newPath = new ArrayDeque<>(stack);
+            newPath.push(child);
+            final List<NormalizedNode<?, ?>> dataList = TEST_MAP.get(YangInstanceIdentifier.create(newPath));
+            if (dataList != null) {
+                dataList.add(data);
+            } else {
+                TEST_MAP.put(YangInstanceIdentifier.create(newPath), Lists.newArrayList(data));
+            }
+        }
+
+        @Override
+        public void write(final PathArgument child, final NormalizedNode<?, ?> data) {
+            final ArrayDeque<PathArgument> newPath = new ArrayDeque<>(stack);
+            newPath.push(child);
+            TEST_MAP.put(YangInstanceIdentifier.create(newPath), Lists.newArrayList(data));
+        }
+
+        @Override
+        public void enter(@Nonnull final PathArgument child) {
+            stack.push(child);
+        }
+
+        @Override
+        public void enter(@Nonnull final PathArgument... path) {
+            for (final PathArgument pathArgument : path) {
+                stack.push(pathArgument);
+            }
+        }
+
+        @Override
+        public void enter(@Nonnull final Iterable<PathArgument> path) {
+            path.forEach(stack::push);
+        }
+
+        @Override
+        public void exit() {
+            stack.pop();
+        }
+
+        @Override
+        public void exit(final int depth) {
+            stack.pop();
+        }
+
         @Override
         public void close() {
-            // NOOP
+
         }
     }
 }
\ No newline at end of file
index 42ad3d3c09f981764826e0faf2d0e543ae3a2cbd..24a79b84aea7a22b55d3bf28f3aedbf608af17d5 100644 (file)
@@ -2,7 +2,6 @@ package org.opendaylight.mdsal.dom.broker.test;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyCollection;
 import static org.mockito.Matchers.anyMap;
@@ -62,7 +61,7 @@ public class ShardedDOMDataTreeProducerMultiShardTest {
     static {
         try {
             schemaContext = TestModel.createTestContext();
-        } catch (ReactorException e) {
+        } catch (final ReactorException e) {
             LOG.error("Unable to create schema context for TestModel", e);
         }
     }
index b9405650139889cbdb899d3e71fc78159e21a526..7a9ebdd7660c05d7dab30191219ae39a139a3e20 100644 (file)
@@ -8,9 +8,11 @@
 package org.opendaylight.mdsal.dom.broker.test;
 
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 
+import com.google.common.util.concurrent.Futures;
 import java.util.Collection;
 import java.util.Collections;
 import org.junit.Before;
@@ -18,22 +20,22 @@ import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerBusyException;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerException;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeService;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeShard;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeShardingConflictException;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
 import org.opendaylight.mdsal.dom.broker.ShardedDOMDataTree;
 import org.opendaylight.mdsal.dom.broker.test.util.TestModel;
-import org.opendaylight.mdsal.dom.spi.store.DOMStore;
 import org.opendaylight.mdsal.dom.spi.store.DOMStoreTransactionChain;
 import org.opendaylight.mdsal.dom.spi.store.DOMStoreWriteTransaction;
+import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardProducer;
+import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardWriteTransaction;
+import org.opendaylight.mdsal.dom.store.inmemory.WriteableDOMDataTreeShard;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 
 public class ShardedDOMDataTreeProducerSingleShardTest {
 
@@ -46,7 +48,7 @@ public class ShardedDOMDataTreeProducerSingleShardTest {
     private static final Collection<DOMDataTreeIdentifier> SUBTREES_ROOT = Collections.singleton(ROOT_ID);
     private static final Collection<DOMDataTreeIdentifier> SUBTREES_TEST = Collections.singleton(TEST_ID);
 
-    interface MockTestShard extends DOMDataTreeShard, DOMStore {
+    interface MockTestShard extends WriteableDOMDataTreeShard {
 
     }
 
@@ -57,6 +59,12 @@ public class ShardedDOMDataTreeProducerSingleShardTest {
     @Mock(name = "storeWriteTx")
     private DOMStoreWriteTransaction writeTxMock;
 
+    @Mock
+    private DOMDataTreeShardProducer producerMock;
+
+    @Mock
+    private DOMDataTreeShardWriteTransaction shardTxMock;
+
     @Mock(name = "storeTxChain")
     private DOMStoreTransactionChain txChainMock;
 
@@ -72,9 +80,10 @@ public class ShardedDOMDataTreeProducerSingleShardTest {
         shardReg = impl.registerDataTreeShard(ROOT_ID, rootShard);
 
         doReturn("rootShard").when(rootShard).toString();
-        doReturn(txChainMock).when(rootShard).createTransactionChain();
-        doReturn(writeTxMock).when(txChainMock).newWriteOnlyTransaction();
-        doReturn(TestCommitCohort.ALLWAYS_SUCCESS).when(writeTxMock).ready();
+        doReturn(producerMock).when(rootShard).createProducer(any(Collection.class));
+        doReturn(shardTxMock).when(producerMock).createTransaction();
+        doNothing().when(shardTxMock).ready();
+        doReturn(Futures.immediateFuture(null)).when(shardTxMock).submit();
 
         producer = treeService.createProducer(SUBTREES_ROOT);
     }
@@ -92,7 +101,7 @@ public class ShardedDOMDataTreeProducerSingleShardTest {
 
     @Test
     public void closeWithTxSubmitted() throws DOMDataTreeProducerException {
-        DOMDataTreeWriteTransaction tx = producer.createTransaction(false);
+        DOMDataTreeCursorAwareTransaction tx = producer.createTransaction(false);
         tx.submit();
         producer.close();
     }
@@ -121,9 +130,8 @@ public class ShardedDOMDataTreeProducerSingleShardTest {
     public void writeChildProducerDataToParentTx() {
         DOMDataTreeProducer childProducer = producer.createProducer(SUBTREES_TEST);
         assertNotNull(childProducer);
-        DOMDataTreeWriteTransaction parentTx = producer.createTransaction(true);
-        parentTx.put(TEST_ID.getDatastoreType(), TEST_ID.getRootIdentifier(),
-                ImmutableNodes.containerNode(TestModel.TEST_QNAME));
+        DOMDataTreeCursorAwareTransaction parentTx = producer.createTransaction(true);
+        parentTx.createCursor(TEST_ID);
     }
 
     @Test
diff --git a/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeTest.java b/dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/test/ShardedDOMDataTreeTest.java
new file mode 100644 (file)
index 0000000..9b828c2
--- /dev/null
@@ -0,0 +1,230 @@
+package org.opendaylight.mdsal.dom.broker.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyCollection;
+import static org.mockito.Matchers.anyMap;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeListener;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor;
+import org.opendaylight.mdsal.dom.broker.ShardedDOMDataTree;
+import org.opendaylight.mdsal.dom.broker.test.util.TestModel;
+import org.opendaylight.mdsal.dom.store.inmemory.InMemoryDOMDataTreeShard;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ShardedDOMDataTreeTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTreeProducerMultiShardTest.class);
+
+    private static SchemaContext schemaContext = null;
+
+    static {
+        try {
+            schemaContext = TestModel.createTestContext();
+        } catch (final ReactorException e) {
+            LOG.error("Unable to create schema context for TestModel", e);
+        }
+    }
+
+    private static final DOMDataTreeIdentifier ROOT_ID =
+            new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.EMPTY);
+    private static final DOMDataTreeIdentifier TEST_ID =
+            new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, TestModel.TEST_PATH);
+
+    private static final DOMDataTreeIdentifier INNER_CONTAINER_ID =
+            new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, TestModel.INNER_CONTAINER_PATH);
+
+    private InMemoryDOMDataTreeShard rootShard;
+
+    private ShardedDOMDataTree dataTreeService;
+    private ListenerRegistration<InMemoryDOMDataTreeShard> rootShardReg;
+
+    private final ExecutorService executor = Executors.newSingleThreadExecutor();
+
+    @Captor
+    private ArgumentCaptor<Collection<DataTreeCandidate>> captorForChanges;
+    @Captor
+    private ArgumentCaptor<Map<DOMDataTreeIdentifier, NormalizedNode<?, ?>>> captorForSubtrees;
+
+    private final ContainerNode crossShardContainer = createCrossShardContainer();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        rootShard = InMemoryDOMDataTreeShard.create(ROOT_ID, executor, 5000);
+        rootShard.onGlobalContextUpdated(schemaContext);
+
+        final ShardedDOMDataTree dataTree = new ShardedDOMDataTree();
+        rootShardReg = dataTree.registerDataTreeShard(ROOT_ID, rootShard);
+
+        dataTreeService = dataTree;
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testProducerPathContention() throws Exception {
+        final DOMDataTreeProducer p1 = dataTreeService.createProducer(Collections.singletonList(ROOT_ID));
+        final DOMDataTreeProducer p2 = dataTreeService.createProducer(Collections.singletonList(TEST_ID));
+    }
+
+    @Test
+    public void testSingleShardWrite() throws Exception {
+        final DOMDataTreeListener mockedDataTreeListener = Mockito.mock(DOMDataTreeListener.class);
+        doNothing().when(mockedDataTreeListener).onDataTreeChanged(anyCollection(), anyMap());
+
+        dataTreeService.registerListener(mockedDataTreeListener, Collections.singletonList(INNER_CONTAINER_ID), true, Collections.emptyList());
+
+        final DOMDataTreeProducer producer = dataTreeService.createProducer(Collections.singletonList(ROOT_ID));
+        DOMDataTreeCursorAwareTransaction tx = producer.createTransaction(false);
+        DOMDataTreeWriteCursor cursor = tx.createCursor(ROOT_ID);
+        assertNotNull(cursor);
+
+        cursor.write(TEST_ID.getRootIdentifier().getLastPathArgument(), crossShardContainer);
+
+        try {
+            tx.submit().checkedGet();
+            fail("There's still an open cursor");
+        } catch (final IllegalStateException e) {
+            assertTrue(e.getMessage().contains("cursor open"));
+        }
+
+        cursor.close();
+        tx.submit().checkedGet();
+
+        tx = producer.createTransaction(false);
+        cursor = tx.createCursor(TEST_ID);
+        assertNotNull(cursor);
+
+        cursor.delete(TestModel.INNER_CONTAINER_PATH.getLastPathArgument());
+        cursor.close();
+        tx.submit().checkedGet();
+
+        verify(mockedDataTreeListener, timeout(1000).times(2)).onDataTreeChanged(captorForChanges.capture(), captorForSubtrees.capture());
+        final List<Collection<DataTreeCandidate>> capturedValue = captorForChanges.getAllValues();
+        assertTrue(capturedValue.size() == 2);
+
+        final ContainerNode capturedChange = (ContainerNode) capturedValue.get(0).iterator().next().getRootNode().getDataAfter().get();
+        final ContainerNode innerContainerVerify = (ContainerNode) crossShardContainer.getChild(TestModel.INNER_CONTAINER_PATH.getLastPathArgument()).get();
+        assertEquals(innerContainerVerify, capturedChange);
+
+        verifyNoMoreInteractions(mockedDataTreeListener);
+    }
+
+    @Test
+    public void testMultipleProducerCursorCreation() throws Exception {
+
+        final DOMDataTreeProducer rootProducer = dataTreeService.createProducer(Collections.singletonList(ROOT_ID));
+        DOMDataTreeCursorAwareTransaction rootTx = rootProducer.createTransaction(false);
+        //check if we can create cursor where the new producer will be
+        DOMDataTreeWriteCursor rootTxCursor = rootTx.createCursor(INNER_CONTAINER_ID);
+        assertNotNull(rootTxCursor);
+        rootTxCursor.close();
+
+        try {
+            rootProducer.createProducer(Collections.singletonList(INNER_CONTAINER_ID));
+            fail("Should've failed there is still a tx open");
+        } catch (final IllegalStateException e) {
+            assertTrue(e.getMessage().contains("open"));
+        }
+
+        assertTrue(rootTx.cancel());
+
+        final DOMDataTreeProducer innerContainerProducer = rootProducer.createProducer(Collections.singletonList(INNER_CONTAINER_ID));
+
+        rootTx = rootProducer.createTransaction(false);
+        try {
+            rootTx.createCursor(INNER_CONTAINER_ID);
+            fail("Subtree should not be available to this producer");
+        } catch (final IllegalArgumentException e) {
+            assertTrue(e.getMessage().contains("delegated to child producer"));
+        }
+
+        rootTxCursor = rootTx.createCursor(TEST_ID);
+        assertNotNull(rootTxCursor);
+        try {
+            rootTxCursor.enter(INNER_CONTAINER_ID.getRootIdentifier().getLastPathArgument());
+            fail("Cursor should not have this subtree available");
+        } catch (final IllegalArgumentException e) {
+            assertTrue(e.getMessage().contains("not available to this cursor"));
+        }
+
+        try {
+            rootTxCursor.write(TestModel.INNER_CONTAINER_PATH.getLastPathArgument(),
+                    ImmutableContainerNodeBuilder.create()
+                            .withNodeIdentifier(new NodeIdentifier(TestModel.INNER_CONTAINER))
+                            .build());
+            fail("Cursor should not have this subtree available");
+        } catch (final IllegalArgumentException e) {
+            assertTrue(e.getMessage().contains("not available to this cursor"));
+        }
+
+        final DOMDataTreeCursorAwareTransaction innerShardTx = innerContainerProducer.createTransaction(false);
+        final DOMDataTreeWriteCursor innerShardCursor = innerShardTx.createCursor(INNER_CONTAINER_ID);
+        assertNotNull(innerShardCursor);
+    }
+
+    private ContainerNode createCrossShardContainer() {
+        final LeafNode<String> shardedValue1 =
+                ImmutableLeafNodeBuilder.<String>create().withNodeIdentifier(new NodeIdentifier(TestModel.SHARDED_VALUE_1)).withValue("sharded value 1").build();
+        final LeafNode<String> shardedValue2 =
+                ImmutableLeafNodeBuilder.<String>create().withNodeIdentifier(new NodeIdentifier(TestModel.SHARDED_VALUE_2)).withValue("sharded value 2").build();
+
+
+        final ContainerNode lowerShardContainer = ImmutableContainerNodeBuilder.create()
+                .withNodeIdentifier(new NodeIdentifier(TestModel.ANOTHER_SHARD_CONTAINER))
+                .withChild(ImmutableLeafNodeBuilder.create()
+                        .withNodeIdentifier(new NodeIdentifier(TestModel.ANOTHER_SHARD_VALUE))
+                        .withValue("testing-value")
+                        .build())
+                .build();
+
+        final ContainerNode containerNode =
+                ImmutableContainerNodeBuilder.create()
+                        .withNodeIdentifier(new NodeIdentifier(TestModel.INNER_CONTAINER))
+                        .withChild(shardedValue1)
+                        .withChild(shardedValue2)
+                        .withChild(lowerShardContainer)
+                        .build();
+
+        final ContainerNode testContainer = ImmutableContainerNodeBuilder.create()
+                .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME))
+                .withChild(containerNode)
+                .build();
+
+        return testContainer;
+    }
+}
index fc4ad229d978c76cf92a9cd0bf6d494ba8f88855..086689f2c1e0e14e39eb91def9d2c08d384cff51 100644 (file)
@@ -126,7 +126,6 @@ abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeC
 
     private static final class DOMDataTreeListenerWithSubshards implements DOMDataTreeChangeListener {
 
-        // TODO should we synchronize the access to the dataTree snapshots?
         private final DataTree dataTree;
         private final YangInstanceIdentifier listenerPath;
         private final DOMDataTreeChangeListener delegate;
@@ -145,14 +144,14 @@ abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeC
         @Override
         public void onDataTreeChanged(@Nonnull final Collection<DataTreeCandidate> changes) {
             LOG.debug("Received data changed {}", changes.iterator().next());
-            final DataTreeCandidate newCandidate = applyChanges(changes);
-            delegate.onDataTreeChanged(Collections.singleton(newCandidate));
+            delegate.onDataTreeChanged(changes);
         }
 
         void onDataTreeChanged(final YangInstanceIdentifier rootPath, final Collection<DataTreeCandidate> changes) {
-            onDataTreeChanged(changes.stream()
+            final List<DataTreeCandidate> newCandidates = changes.stream()
                     .map(candidate -> DataTreeCandidates.newDataTreeCandidate(rootPath, candidate.getRootNode()))
-                    .collect(Collectors.toList()));
+                    .collect(Collectors.toList());
+            delegate.onDataTreeChanged(Collections.singleton(applyChanges(newCandidates)));
         }
 
         void addSubshard(final ChildShardContext context) {
@@ -167,7 +166,7 @@ abstract class AbstractDOMShardTreeChangePublisher extends AbstractDOMStoreTreeC
         }
 
         void close() {
-            for (ListenerRegistration<DOMDataTreeChangeListener> registration : registrations.values()) {
+            for (final ListenerRegistration<DOMDataTreeChangeListener> registration : registrations.values()) {
                 registration.close();
             }
             registrations.clear();
index b457ddb49171c6acb7a89da7c1baed437f05e598..59fa4b0966315a94aebec176c3e5d105d5cd84ff 100644 (file)
@@ -11,12 +11,12 @@ package org.opendaylight.mdsal.dom.store.inmemory;
 import com.google.common.annotations.Beta;
 import com.google.common.util.concurrent.ListenableFuture;
 import javax.annotation.Nonnull;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorProvider;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor;
 
-// FIXME: this should be moved to a separate package
 @Beta
-public interface DOMDataTreeShardWriteTransaction {
+public interface DOMDataTreeShardWriteTransaction extends DOMDataTreeCursorProvider {
     /**
      * Create a new write cursor. Any previous cursors have to be {@link DOMDataTreeWriteCursor#close()}d.
      *
@@ -25,6 +25,7 @@ public interface DOMDataTreeShardWriteTransaction {
      * @throws IllegalStateException if a previous cursor has not been closed.
      * @throws NullPointerException if prefix is null.
      */
+    @Override
     @Nonnull DOMDataTreeWriteCursor createCursor(@Nonnull DOMDataTreeIdentifier prefix);
 
     /**
@@ -43,7 +44,6 @@ public interface DOMDataTreeShardWriteTransaction {
 
     ListenableFuture<Void> submit();
 
-    //FIXME: remove these from the public api?
     ListenableFuture<Boolean> validate();
 
     ListenableFuture<Void> prepare();
index 4a48a6471ac20ee5805abd83092f48eb7e47be98..051d658a129ffc93ed5b212059630f6e3c9490a7 100644 (file)
@@ -39,7 +39,6 @@ public class InMemoryDOMDataTreeShard implements ReadableWriteableDOMDataTreeSha
     private static final class SubshardProducerSpecification {
         private final Collection<DOMDataTreeIdentifier> prefixes = new ArrayList<>(1);
         private final ChildShardContext shard;
-
         SubshardProducerSpecification(final ChildShardContext subshard) {
             this.shard = Preconditions.checkNotNull(subshard);
         }
@@ -60,9 +59,11 @@ public class InMemoryDOMDataTreeShard implements ReadableWriteableDOMDataTreeSha
     private final DOMDataTreePrefixTable<ChildShardContext> childShardsTable = DOMDataTreePrefixTable.create();
 
     private final Map<DOMDataTreeIdentifier, ChildShardContext> childShards = new HashMap<>();
+
     private final DOMDataTreeIdentifier prefix;
     private final DataTree dataTree;
 
+    private SchemaContext schemaContext;
     private InMemoryDOMDataTreeShardChangePublisher shardChangePublisher;
 
     private InMemoryDOMDataTreeShard(final DOMDataTreeIdentifier prefix, final ExecutorService dataTreeChangeExecutor,
@@ -83,6 +84,7 @@ public class InMemoryDOMDataTreeShard implements ReadableWriteableDOMDataTreeSha
     @Override
     public void onGlobalContextUpdated(final SchemaContext context) {
         dataTree.setSchemaContext(context);
+        schemaContext = context;
     }
 
     @Override
@@ -108,7 +110,7 @@ public class InMemoryDOMDataTreeShard implements ReadableWriteableDOMDataTreeSha
 
     @Nonnull
     @Override
-    public <L extends DOMDataTreeChangeListener> ListenerRegistration<L> registerTreeChangeListener(@Nonnull YangInstanceIdentifier treeId, @Nonnull L listener) {
+    public <L extends DOMDataTreeChangeListener> ListenerRegistration<L> registerTreeChangeListener(@Nonnull final YangInstanceIdentifier treeId, @Nonnull final L listener) {
         return shardChangePublisher.registerTreeChangeListener(treeId, listener);
     }
 
index 90c05f649c10f1c1ea28404b3b18bd6167f3382d..8c6b8be4503b8334b42bf7899435bcf67badbec8 100644 (file)
@@ -50,10 +50,12 @@ final class InMemoryDOMDataTreeShardChangePublisher extends AbstractDOMShardTree
     }
 
     @Override
-    protected void notifyListeners(@Nonnull Collection<AbstractDOMDataTreeChangeListenerRegistration<?>> registrations, @Nonnull YangInstanceIdentifier path, @Nonnull DataTreeCandidateNode node) {
+    protected void notifyListeners(@Nonnull final Collection<AbstractDOMDataTreeChangeListenerRegistration<?>> registrations,
+                                   @Nonnull final YangInstanceIdentifier path,
+                                   @Nonnull final DataTreeCandidateNode node) {
         final DataTreeCandidate candidate = DataTreeCandidates.newDataTreeCandidate(path, node);
 
-        for (AbstractDOMDataTreeChangeListenerRegistration<?> reg : registrations) {
+        for (final AbstractDOMDataTreeChangeListenerRegistration<?> reg : registrations) {
             LOG.debug("Enqueueing candidate {} to registration {}", candidate, registrations);
             notificationManager.submitNotification(reg, candidate);
         }
index 95b28bdeb6c7f31bfe07b653841ecdb1f14601a8..1aaa459b231716618b62f924788b3c03193beb20 100644 (file)
@@ -17,7 +17,6 @@ import com.google.common.util.concurrent.MoreExecutors;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.Map.Entry;
-import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 import org.opendaylight.mdsal.common.api.ReadFailedException;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
@@ -63,9 +62,9 @@ class InmemoryDOMDataTreeShardWriteTransaction implements DOMDataTreeShardWriteT
         void apply(final DOMDataTreeWriteCursor cursor, final YangInstanceIdentifier path,
                 final NormalizedNode<?, ?> data) {
             int enterCount = 0;
-            Iterator<PathArgument> it = path.getPathArguments().iterator();
+            final Iterator<PathArgument> it = path.getPathArguments().iterator();
             while (it.hasNext()) {
-                PathArgument currentArg = it.next();
+                final PathArgument currentArg = it.next();
                 if (it.hasNext()) {
                     // We need to enter one level deeper, we are not at leaf (modified) node
                     cursor.enter(currentArg);
@@ -80,15 +79,15 @@ class InmemoryDOMDataTreeShardWriteTransaction implements DOMDataTreeShardWriteT
 
     private final ShardDataModification modification;
     private DOMDataTreeWriteCursor cursor;
-    private DataTree rootShardDataTree;
+    private final DataTree rootShardDataTree;
     private DataTreeModification rootModification = null;
 
-    private ArrayList<DOMStoreThreePhaseCommitCohort> cohorts = new ArrayList<>();
-    private InMemoryDOMDataTreeShardChangePublisher changePublisher;
+    private final ArrayList<DOMStoreThreePhaseCommitCohort> cohorts = new ArrayList<>();
+    private final InMemoryDOMDataTreeShardChangePublisher changePublisher;
     private boolean finished = false;
 
     // FIXME inject into shard?
-    private ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
+    private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
 
     InmemoryDOMDataTreeShardWriteTransaction(final ShardDataModification root,
                                              final DataTree rootShardDataTree,
@@ -106,7 +105,7 @@ class InmemoryDOMDataTreeShardWriteTransaction implements DOMDataTreeShardWriteT
     }
 
     void delete(final YangInstanceIdentifier path) {
-        YangInstanceIdentifier relativePath = toRelative(path);
+        final YangInstanceIdentifier relativePath = toRelative(path);
         Preconditions.checkArgument(!YangInstanceIdentifier.EMPTY.equals(relativePath),
                 "Deletion of shard root is not allowed");
         SimpleCursorOperation.DELETE.apply(getCursor(), relativePath , null);
@@ -121,33 +120,27 @@ class InmemoryDOMDataTreeShardWriteTransaction implements DOMDataTreeShardWriteT
     }
 
     private YangInstanceIdentifier toRelative(final YangInstanceIdentifier path) {
-        Optional<YangInstanceIdentifier> relative =
+        final Optional<YangInstanceIdentifier> relative =
                 path.relativeTo(modification.getPrefix().getRootIdentifier());
         Preconditions.checkArgument(relative.isPresent());
         return relative.get();
     }
 
     public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(final YangInstanceIdentifier path) {
-        // FIXME: Implement this
-        return null;
+        throw new UnsupportedOperationException("Not implemented yet");
     }
 
     public CheckedFuture<Boolean, ReadFailedException> exists(final YangInstanceIdentifier path) {
-        // TODO Auto-generated method stub
-        return null;
-    }
-
-
-    public Object getIdentifier() {
-        // TODO Auto-generated method stub
-        return null;
+        throw new UnsupportedOperationException("Not implemented yet");
     }
 
     @Override
     public void close() {
         Preconditions.checkState(!finished, "Attempting to close an already finished transaction.");
         modification.closeTransactions();
-        cursor.close();
+        if (cursor != null) {
+            cursor.close();
+        }
         finished = true;
     }
 
@@ -170,7 +163,7 @@ class InmemoryDOMDataTreeShardWriteTransaction implements DOMDataTreeShardWriteT
         rootModification = modification.seal();
 
         cohorts.add(new InMemoryDOMDataTreeShardThreePhaseCommitCohort(rootShardDataTree, rootModification, changePublisher));
-        for (Entry<DOMDataTreeIdentifier, ForeignShardModificationContext> entry : modification.getChildShards().entrySet()) {
+        for (final Entry<DOMDataTreeIdentifier, ForeignShardModificationContext> entry : modification.getChildShards().entrySet()) {
             cohorts.add(new ForeignShardThreePhaseCommitCohort(entry.getKey(), entry.getValue()));
         }
         finished = true;
@@ -220,8 +213,8 @@ class InmemoryDOMDataTreeShardWriteTransaction implements DOMDataTreeShardWriteT
     public DOMDataTreeWriteCursor createCursor(final DOMDataTreeIdentifier prefix) {
         Preconditions.checkState(!finished, "Transaction is finished/closed already.");
         Preconditions.checkState(cursor == null, "Previous cursor wasn't closed");
-        DOMDataTreeWriteCursor ret = getCursor();
-        YangInstanceIdentifier relativePath = toRelative(prefix.getRootIdentifier());
+        final DOMDataTreeWriteCursor ret = getCursor();
+        final YangInstanceIdentifier relativePath = toRelative(prefix.getRootIdentifier());
         ret.enter(relativePath.getPathArguments());
         return ret;
     }