Implement managed transactions 43/75943/13
authorStephen Kitt <skitt@redhat.com>
Mon, 10 Sep 2018 16:31:07 +0000 (18:31 +0200)
committerRobert Varga <nite@hq.sk>
Thu, 4 Oct 2018 15:27:10 +0000 (15:27 +0000)
Managed transactions ensure that transactions are always closed in the
appropriate way:
* read-only transactions are closed when they are no longer needed;
* read-write and write-only transactions are submitted if the code
  manipulating them didn’t throw an exception, cancelled otherwise.

Managed transactions are datastore-specific and enforce either
configuration or operational reads and writes.

Two providers are included: a basic transaction manager which provides
transactions, ensures they are closed, without any extras, and a
retrying transaction manager which will retry if a retriable error
occurs (i.e. an OptimisticLockFailedException when writing, or a
ReadFailedException when reading).

This is copied from Genius where it’s used extensively; it’s also used
in NetVirt.

Change-Id: I391ca4ca50d1b1177d827e33c0d11e98378e1fb4
Signed-off-by: Stephen Kitt <skitt@redhat.com>
34 files changed:
binding/mdsal-binding-util-tests/pom.xml [new file with mode: 0644]
binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunnerImplTest.java [new file with mode: 0644]
binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunnerTest.java [new file with mode: 0644]
binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/TransactionAdapterTest.java [new file with mode: 0644]
binding/mdsal-binding-util/pom.xml
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/CheckedConsumer.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/CheckedFunction.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/Datastore.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/InterruptibleCheckedConsumer.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/InterruptibleCheckedFunction.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunner.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunnerImpl.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionChain.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionChainImpl.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionFactory.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionFactoryImpl.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunner.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunnerImpl.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TransactionAdapter.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadTransaction.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadTransactionImpl.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadWriteTransaction.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadWriteTransactionImpl.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedTransaction.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransaction.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransactionImpl.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingReadWriteTransaction.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTransaction.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTypedReadWriteTransactionImpl.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTypedWriteTransactionImpl.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingWriteTransaction.java [new file with mode: 0644]
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/package-info.java [new file with mode: 0644]
binding/mdsal-binding-util/src/test/java/org/opendaylight/mdsal/binding/util/DatastoreTest.java [new file with mode: 0644]
binding/pom.xml

diff --git a/binding/mdsal-binding-util-tests/pom.xml b/binding/mdsal-binding-util-tests/pom.xml
new file mode 100644 (file)
index 0000000..bf69449
--- /dev/null
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2018 Red Hat, Inc. and others.
+
+ 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.opendaylight.odlparent</groupId>
+        <artifactId>bundle-parent</artifactId>
+        <version>4.0.2</version>
+    </parent>
+
+    <groupId>org.opendaylight.mdsal</groupId>
+    <artifactId>mdsal-binding-util-tests</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.opendaylight.mdsal</groupId>
+                <artifactId>mdsal-artifacts</artifactId>
+                <version>3.0.0-SNAPSHOT</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.google.truth</groupId>
+            <artifactId>truth</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.truth.extensions</groupId>
+            <artifactId>truth-java8-extension</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.mdsal</groupId>
+            <artifactId>mdsal-binding-test-model</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.mdsal</groupId>
+            <artifactId>mdsal-binding-test-utils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.mdsal</groupId>
+            <artifactId>mdsal-binding-dom-adapter</artifactId>
+            <type>test-jar</type>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunnerImplTest.java b/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunnerImplTest.java
new file mode 100644 (file)
index 0000000..d5bb9a2
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2017 Red Hat, 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.util;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.opendaylight.mdsal.binding.test.model.util.ListsBindingUtils.TOP_FOO_KEY;
+import static org.opendaylight.mdsal.binding.test.model.util.ListsBindingUtils.path;
+import static org.opendaylight.mdsal.binding.test.model.util.ListsBindingUtils.topLevelList;
+import static org.opendaylight.mdsal.binding.util.Datastore.OPERATIONAL;
+
+import java.io.IOException;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.ReadTransaction;
+import org.opendaylight.mdsal.binding.dom.adapter.test.AbstractConcurrentDataBrokerTest;
+import org.opendaylight.mdsal.binding.testutils.DataBrokerFailuresImpl;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.OptimisticLockFailedException;
+import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.augment.rev140709.TreeComplexUsesAugment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.augment.rev140709.TreeComplexUsesAugmentBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.augment.rev140709.complex.from.grouping.ContainerWithUsesBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.TopLevelList;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Test for {@link ManagedNewTransactionRunnerImpl}.
+ *
+ * @author Michael Vorburger.ch
+ * @author Stephen Kitt
+ */
+public class ManagedNewTransactionRunnerImplTest extends AbstractConcurrentDataBrokerTest {
+
+    static final InstanceIdentifier<TopLevelList> TEST_PATH = path(TOP_FOO_KEY);
+
+    DataBrokerFailuresImpl testableDataBroker;
+    ManagedNewTransactionRunner managedNewTransactionRunner;
+
+    public ManagedNewTransactionRunnerImplTest() {
+        super(true);
+    }
+
+    protected ManagedNewTransactionRunner createManagedNewTransactionRunnerToTest(DataBroker dataBroker) {
+        return new ManagedNewTransactionRunnerImpl(dataBroker);
+    }
+
+    @Before
+    public void beforeTest() throws Exception {
+        setup();
+        testableDataBroker = new DataBrokerFailuresImpl(getDataBroker());
+        managedNewTransactionRunner = createManagedNewTransactionRunnerToTest(testableDataBroker);
+    }
+
+    @Test
+    public void testApplyWithNewReadTransactionAndCloseEmptySuccessfully() {
+        assertEquals(Long.valueOf(1),
+            managedNewTransactionRunner.applyWithNewReadOnlyTransactionAndClose(OPERATIONAL, tx -> 1L));
+    }
+
+    @Test
+    public void testCallWithNewReadTransactionAndCloseEmptySuccessfully() {
+        managedNewTransactionRunner.callWithNewReadOnlyTransactionAndClose(OPERATIONAL, tx -> { });
+    }
+
+    @Test
+    public void testCallWithNewTypedWriteOnlyTransactionAndSubmitEmptySuccessfully() throws Exception {
+        managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, writeTx -> { }).get();
+    }
+
+    @Test
+    public void testCallWithNewTypedReadWriteTransactionAndSubmitEmptySuccessfully() throws Exception {
+        managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, tx -> { }).get();
+    }
+
+    @Test
+    public void testApplyWithNewReadWriteTransactionAndSubmitEmptySuccessfully() throws Exception {
+        assertEquals(1,
+            (long) managedNewTransactionRunner.applyWithNewReadWriteTransactionAndSubmit(OPERATIONAL,
+                tx -> 1).get());
+    }
+
+    @Test
+    public void testCallWithNewTypedWriteOnlyTransactionAndSubmitPutSuccessfully() throws Exception {
+        TopLevelList data = newTestDataObject();
+        managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL,
+            writeTx -> writeTx.put(TEST_PATH, data)).get();
+        assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH));
+    }
+
+    @Test
+    public void testCallWithNewTypedReadWriteTransactionAndSubmitPutSuccessfully() throws Exception {
+        TopLevelList data = newTestDataObject();
+        managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL,
+            tx -> tx.put(TEST_PATH, data)).get();
+        assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH));
+    }
+
+    @Test
+    public void testApplyWithNewReadWriteTransactionAndSubmitPutSuccessfully() throws Exception {
+        TopLevelList data = newTestDataObject();
+        assertEquals(1, (long) managedNewTransactionRunner.applyWithNewReadWriteTransactionAndSubmit(
+            OPERATIONAL, tx -> {
+                tx.put(TEST_PATH, data);
+                return 1;
+            }).get());
+        assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH));
+    }
+
+    @Test
+    public void testCallWithNewReadTransactionAndCloseReadSuccessfully() throws Exception {
+        TopLevelList data = newTestDataObject();
+        managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL,
+            tx -> tx.put(TEST_PATH, data)).get();
+        assertEquals(data, managedNewTransactionRunner.applyWithNewReadOnlyTransactionAndClose(OPERATIONAL,
+            tx -> tx.read(TEST_PATH)).get().get());
+    }
+
+    TopLevelList newTestDataObject() {
+        TreeComplexUsesAugment fooAugment = new TreeComplexUsesAugmentBuilder()
+                .setContainerWithUses(new ContainerWithUsesBuilder().setLeafFromGrouping("foo").build()).build();
+        return topLevelList(TOP_FOO_KEY, fooAugment);
+    }
+
+    @Test
+    public void testCallWithNewTypedWriteOnlyTransactionAndSubmitPutButLaterException() throws Exception {
+        try {
+            managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, writeTx -> {
+                writeTx.put(TEST_PATH, newTestDataObject());
+                // We now throw an arbitrary kind of checked (not unchecked!) exception here
+                throw new IOException("something didn't quite go as expected...");
+            }).get();
+            fail("This should have led to an ExecutionException!");
+        } catch (ExecutionException e) {
+            assertTrue(e.getCause() instanceof IOException);
+        }
+        assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty();
+    }
+
+    @Test
+    public void testCallWithNewTypedReadWriteTransactionAndSubmitPutButLaterException() throws Exception {
+        try {
+            managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL, writeTx -> {
+                writeTx.put(TEST_PATH, newTestDataObject());
+                // We now throw an arbitrary kind of checked (not unchecked!) exception here
+                throw new IOException("something didn't quite go as expected...");
+            }).get();
+            fail("This should have led to an ExecutionException!");
+        } catch (ExecutionException e) {
+            assertTrue(e.getCause() instanceof IOException);
+        }
+        assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty();
+    }
+
+    @Test
+    public void testApplyWithNewReadWriteTransactionAndSubmitPutButLaterException() throws Exception {
+        try {
+            managedNewTransactionRunner.applyWithNewReadWriteTransactionAndSubmit(OPERATIONAL,
+                writeTx -> {
+                    writeTx.put(TEST_PATH, newTestDataObject());
+                    // We now throw an arbitrary kind of checked (not unchecked!) exception here
+                    throw new IOException("something didn't quite go as expected...");
+                }).get();
+            fail("This should have led to an ExecutionException!");
+        } catch (ExecutionException e) {
+            assertTrue(e.getCause() instanceof IOException);
+        }
+        assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty();
+    }
+
+    @Test
+    public void testCallWithNewTypedWriteOnlyTransactionCommitFailedException() throws Exception {
+        try {
+            testableDataBroker.failCommits(new TransactionCommitFailedException("bada boum bam!"));
+            managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL,
+                writeTx -> writeTx.put(TEST_PATH, newTestDataObject())).get();
+            fail("This should have led to an ExecutionException!");
+        } catch (ExecutionException e) {
+            assertTrue(e.getCause() instanceof TransactionCommitFailedException);
+        }
+        assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty();
+    }
+
+    @Test
+    public void testCallWithNewTypedReadWriteTransactionCommitFailedException() throws Exception {
+        try {
+            testableDataBroker.failCommits(new TransactionCommitFailedException("bada boum bam!"));
+            managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL,
+                writeTx -> writeTx.put(TEST_PATH, newTestDataObject())).get();
+            fail("This should have led to an ExecutionException!");
+        } catch (ExecutionException e) {
+            assertTrue(e.getCause() instanceof TransactionCommitFailedException);
+        }
+        assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty();
+    }
+
+    @Test
+    public void testApplyWithNewReadWriteTransactionCommitFailedException() throws Exception {
+        try {
+            testableDataBroker.failCommits(new TransactionCommitFailedException("bada boum bam!"));
+            managedNewTransactionRunner.applyWithNewReadWriteTransactionAndSubmit(OPERATIONAL,
+                writeTx -> {
+                    writeTx.put(TEST_PATH, newTestDataObject());
+                    return 1;
+                }).get();
+            fail("This should have led to an ExecutionException!");
+        } catch (ExecutionException e) {
+            assertTrue(e.getCause() instanceof TransactionCommitFailedException);
+        }
+        assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty();
+    }
+
+    @Test
+    public void testCallWithNewTypedWriteOnlyTransactionOptimisticLockFailedException() throws Exception {
+        try {
+            testableDataBroker.failCommits(2, new OptimisticLockFailedException("bada boum bam!"));
+            managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL,
+                writeTx -> writeTx.put(TEST_PATH, newTestDataObject())).get();
+            fail("This should have led to an ExecutionException!");
+        } catch (ExecutionException e) {
+            assertTrue(e.getCause() instanceof OptimisticLockFailedException);
+        }
+        assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty();
+    }
+
+    @Test
+    public void testCallWithNewTypedReadWriteTransactionOptimisticLockFailedException() throws Exception {
+        try {
+            testableDataBroker.failCommits(2, new OptimisticLockFailedException("bada boum bam!"));
+            managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL,
+                writeTx -> writeTx.put(TEST_PATH, newTestDataObject())).get();
+            fail("This should have led to an ExecutionException!");
+        } catch (ExecutionException e) {
+            assertTrue(e.getCause() instanceof OptimisticLockFailedException);
+        }
+        assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty();
+    }
+
+    @Test
+    public void testApplyWithNewReadWriteTransactionOptimisticLockFailedException() throws Exception {
+        try {
+            testableDataBroker.failCommits(2, new OptimisticLockFailedException("bada boum bam!"));
+            managedNewTransactionRunner.applyWithNewReadWriteTransactionAndSubmit(OPERATIONAL,
+                writeTx -> {
+                    writeTx.put(TEST_PATH, newTestDataObject());
+                    return 1;
+                }).get();
+            fail("This should have led to an ExecutionException!");
+        } catch (ExecutionException e) {
+            assertThat(e.getCause() instanceof OptimisticLockFailedException).isTrue();
+        }
+        assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty();
+    }
+
+    private <T extends DataObject> Optional<T> syncReadOptional(LogicalDatastoreType datastoreType,
+            InstanceIdentifier<T> path) throws ExecutionException, InterruptedException {
+        try (ReadTransaction tx = getDataBroker().newReadOnlyTransaction()) {
+            return tx.read(datastoreType, path).get();
+        }
+    }
+
+    <T extends DataObject> T syncRead(LogicalDatastoreType datastoreType, InstanceIdentifier<T> path)
+            throws ExecutionException, InterruptedException {
+        return syncReadOptional(datastoreType, path).get();
+    }
+}
diff --git a/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunnerTest.java b/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunnerTest.java
new file mode 100644 (file)
index 0000000..97d1e6a
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2017 Red Hat, 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.opendaylight.mdsal.binding.util.Datastore.OPERATIONAL;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.OptimisticLockFailedException;
+import org.opendaylight.mdsal.common.api.ReadFailedException;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.TopLevelList;
+
+/**
+ * Test for {@link RetryingManagedNewTransactionRunner}.
+ * Note that this test (intentionally) extends the {@link ManagedNewTransactionRunnerImplTest}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class RetryingManagedNewTransactionRunnerTest extends ManagedNewTransactionRunnerImplTest {
+
+    @Override
+    protected ManagedNewTransactionRunner createManagedNewTransactionRunnerToTest(DataBroker dataBroker) {
+        return new RetryingManagedNewTransactionRunner(dataBroker);
+    }
+
+    @Override
+    public void testCallWithNewTypedWriteOnlyTransactionOptimisticLockFailedException() throws Exception {
+        // contrary to the super() test implementation for (just) ManagedNewTransactionRunnerImpl, in the parent class
+        // here we expect the x2 OptimisticLockFailedException to be retried, and then eventually succeed:
+        testableDataBroker.failCommits(2, new OptimisticLockFailedException("bada boum bam!"));
+        TopLevelList data = newTestDataObject();
+        managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL,
+            writeTx -> writeTx.put(TEST_PATH, data)).get();
+        Assert.assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH));
+    }
+
+    @Override
+    public void testCallWithNewTypedReadWriteTransactionOptimisticLockFailedException() throws Exception {
+        // contrary to the super() test implementation for (just) ManagedNewTransactionRunnerImpl, in the parent class
+        // here we expect the x2 OptimisticLockFailedException to be retried, and then eventually succeed:
+        testableDataBroker.failCommits(2, new OptimisticLockFailedException("bada boum bam!"));
+        TopLevelList data = newTestDataObject();
+        managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL,
+            writeTx -> writeTx.put(TEST_PATH, data)).get();
+        Assert.assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH));
+    }
+
+    @Override
+    public void testApplyWithNewReadWriteTransactionOptimisticLockFailedException() throws Exception {
+        // contrary to the super() test implementation for (just) ManagedNewTransactionRunnerImpl, in the parent class
+        // here we expect the x2 OptimisticLockFailedException to be retried, and then eventually succeed:
+        testableDataBroker.failCommits(2, new OptimisticLockFailedException("bada boum bam!"));
+        TopLevelList data = newTestDataObject();
+        assertEquals(1, (long) managedNewTransactionRunner.applyWithNewReadWriteTransactionAndSubmit(
+            OPERATIONAL, writeTx -> {
+                writeTx.put(TEST_PATH, data);
+                return 1;
+            }).get());
+        Assert.assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH));
+    }
+
+    @Test
+    public void testCallWithNewTypedReadWriteTransactionReadFailedException() throws Exception {
+        testableDataBroker.failReads(2, new ReadFailedException("bada boum bam!"));
+        TopLevelList data = newTestDataObject();
+        managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL,
+            tx -> {
+                tx.put(TEST_PATH, data);
+                Assert.assertEquals(data, tx.read(TEST_PATH).get().get());
+            }).get();
+        Assert.assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH));
+    }
+
+    @Test
+    public void testApplyWithNewReadWriteTransactionReadFailedException() throws Exception {
+        testableDataBroker.failReads(2, new ReadFailedException("bada boum bam!"));
+        TopLevelList data = newTestDataObject();
+        Assert.assertEquals(data, managedNewTransactionRunner.applyWithNewReadWriteTransactionAndSubmit(
+            OPERATIONAL,
+            tx -> {
+                tx.put(TEST_PATH, data);
+                return tx.read(TEST_PATH).get().get();
+            }).get());
+        Assert.assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH));
+    }
+}
diff --git a/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/TransactionAdapterTest.java b/binding/mdsal-binding-util-tests/src/test/java/org/opendaylight/mdsal/binding/util/TransactionAdapterTest.java
new file mode 100644 (file)
index 0000000..4f79876
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import static com.google.common.truth.Truth8.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.opendaylight.mdsal.binding.test.model.util.ListsBindingUtils.TOP_FOO_KEY;
+import static org.opendaylight.mdsal.binding.test.model.util.ListsBindingUtils.path;
+import static org.opendaylight.mdsal.binding.test.model.util.ListsBindingUtils.topLevelList;
+import static org.opendaylight.mdsal.binding.util.Datastore.OPERATIONAL;
+
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.ReadTransaction;
+import org.opendaylight.mdsal.binding.dom.adapter.test.AbstractConcurrentDataBrokerTest;
+import org.opendaylight.mdsal.binding.testutils.DataBrokerFailuresImpl;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.augment.rev140709.TreeComplexUsesAugment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.augment.rev140709.TreeComplexUsesAugmentBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.augment.rev140709.complex.from.grouping.ContainerWithUsesBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.TopLevelList;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Test for {@link TransactionAdapter}.
+ */
+// This is a test for a deprecated class
+@SuppressWarnings("deprecation")
+public class TransactionAdapterTest extends AbstractConcurrentDataBrokerTest {
+
+    private static final InstanceIdentifier<TopLevelList> TEST_PATH = path(TOP_FOO_KEY);
+
+    private ManagedNewTransactionRunner managedNewTransactionRunner;
+    private DataBrokerFailuresImpl testableDataBroker;
+
+    private ManagedNewTransactionRunner createManagedNewTransactionRunnerToTest(DataBroker dataBroker) {
+        return new ManagedNewTransactionRunnerImpl(dataBroker);
+    }
+
+    @Before
+    public void beforeTest() throws Exception {
+        setup();
+        testableDataBroker = new DataBrokerFailuresImpl(getDataBroker());
+        managedNewTransactionRunner = createManagedNewTransactionRunnerToTest(testableDataBroker);
+    }
+
+    @Test
+    public void testAdaptedWriteTransactionPutsSuccessfully() throws Exception {
+        TopLevelList data = newTestDataObject();
+        managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL,
+            writeTx -> TransactionAdapter.toWriteTransaction(writeTx).put(LogicalDatastoreType.OPERATIONAL,
+                    TEST_PATH, data)).get();
+        assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH));
+    }
+
+    @Test
+    public void testAdaptedReadWriteTransactionPutsSuccessfully() throws Exception {
+        TopLevelList data = newTestDataObject();
+        managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL,
+            writeTx -> TransactionAdapter.toReadWriteTransaction(writeTx).put(LogicalDatastoreType.OPERATIONAL,
+                    TEST_PATH, data)).get();
+        assertEquals(data, syncRead(LogicalDatastoreType.OPERATIONAL, TEST_PATH));
+    }
+
+    @Test
+    public void testAdaptedWriteTransactionFailsOnInvalidDatastore() throws Exception {
+        try {
+            managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL,
+                writeTx -> TransactionAdapter.toWriteTransaction(writeTx).put(LogicalDatastoreType.CONFIGURATION, TEST_PATH,
+                    newTestDataObject())).get();
+            fail("This should have led to an ExecutionException!");
+        } catch (ExecutionException e) {
+            assertTrue(e.getCause() instanceof IllegalArgumentException);
+        }
+        assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty();
+    }
+
+    @Test
+    public void testAdaptedReadWriteTransactionFailsOnInvalidDatastore() throws Exception {
+        try {
+            managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL,
+                writeTx -> TransactionAdapter.toReadWriteTransaction(writeTx).put(LogicalDatastoreType.CONFIGURATION,
+                    TEST_PATH, newTestDataObject())).get();
+            fail("This should have led to an ExecutionException!");
+        } catch (ExecutionException e) {
+            assertTrue(e.getCause() instanceof IllegalArgumentException);
+        }
+        assertThat(syncReadOptional(LogicalDatastoreType.OPERATIONAL, TEST_PATH)).isEmpty();
+    }
+
+    @Test(expected = ExecutionException.class)
+    public void testAdaptedWriteTransactionCannotCommit() throws Exception {
+        managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL,
+            tx -> TransactionAdapter.toWriteTransaction(tx).commit()).get();
+    }
+
+    @Test(expected = ExecutionException.class)
+    public void testAdaptedReadWriteTransactionCannotCommit() throws Exception {
+        managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL,
+            tx -> TransactionAdapter.toReadWriteTransaction(tx).commit()).get();
+    }
+
+    @Test(expected = ExecutionException.class)
+    public void testAdaptedWriteTransactionCannotCancel() throws Exception {
+        managedNewTransactionRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL,
+            tx -> TransactionAdapter.toWriteTransaction(tx).cancel()).get();
+    }
+
+    @Test(expected = ExecutionException.class)
+    public void testAdaptedReadWriteTransactionCannotCancel() throws Exception {
+        managedNewTransactionRunner.callWithNewReadWriteTransactionAndSubmit(OPERATIONAL,
+            tx -> TransactionAdapter.toReadWriteTransaction(tx).cancel()).get();
+    }
+
+    private TopLevelList newTestDataObject() {
+        TreeComplexUsesAugment fooAugment = new TreeComplexUsesAugmentBuilder()
+            .setContainerWithUses(new ContainerWithUsesBuilder().setLeafFromGrouping("foo").build()).build();
+        return topLevelList(TOP_FOO_KEY, fooAugment);
+    }
+
+    private <T extends DataObject> Optional<T> syncReadOptional(LogicalDatastoreType datastoreType,
+            InstanceIdentifier<T> path) throws ExecutionException, InterruptedException {
+        try (ReadTransaction tx = getDataBroker().newReadOnlyTransaction()) {
+            return tx.read(datastoreType, path).get();
+        }
+    }
+
+    private <T extends DataObject> T syncRead(LogicalDatastoreType datastoreType, InstanceIdentifier<T> path)
+            throws ExecutionException, InterruptedException {
+        return syncReadOptional(datastoreType, path).get();
+    }
+}
index 67f47270712a23b4de23a3444f203794021360cc..23c1baf7e83726b6ec46abf15dcc0868615ad7a9 100644 (file)
         </dependency>
         <dependency>
             <groupId>org.opendaylight.mdsal</groupId>
-            <artifactId>mdsal-binding-api</artifactId>
+            <artifactId>mdsal-binding-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>com.google.truth</groupId>
+            <artifactId>truth</artifactId>
+            <scope>test</scope>
         </dependency>
     </dependencies>
     <scm>
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/CheckedConsumer.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/CheckedConsumer.java
new file mode 100644 (file)
index 0000000..eb6603a
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2017 Red Hat, 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.util;
+
+import java.util.function.Consumer;
+
+/**
+ * {@link Consumer} which can throw a checked exception.
+ *
+ * @param <T> the type of the input to the operation
+ * @param <E> the type of the Exception to the operation
+ *
+ * @see Consumer
+ *
+ * @author Michael Vorburger.ch
+ */
+@FunctionalInterface
+public interface CheckedConsumer<T, E extends Exception> {
+
+    void accept(T input) throws E;
+
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/CheckedFunction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/CheckedFunction.java
new file mode 100644 (file)
index 0000000..81a74dc
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+/**
+ * {@link java.util.function.Function} which can throw a checked exception.
+ *
+ * @param <T> The type of the input to be processed.
+ * @param <R> The type of the result to be returned.
+ * @param <E> The type of the exception which may be thrown.
+ */
+@FunctionalInterface
+public interface CheckedFunction<T, R, E extends Exception> {
+    R apply(T input) throws E;
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/Datastore.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/Datastore.java
new file mode 100644 (file)
index 0000000..e581f40
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import com.google.common.annotations.Beta;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+
+/**
+ * Strongly-typed representation of a datastore (configuration or operational).
+ */
+@Beta
+// FIXME Base this on ietf-datastores.yang (RFC 8342)
+public abstract class Datastore {
+
+    /** Class representing the configuration datastore. */
+    public static final Class<Configuration> CONFIGURATION = Configuration.class;
+
+    /** Class representing the operational datastore. */
+    public static final Class<Operational> OPERATIONAL = Operational.class;
+
+    public static final class Configuration extends Datastore {}
+
+    public static final class Operational extends Datastore {}
+
+    /**
+     * Returns the logical datastore type corresponding to the given datastore class.
+     *
+     * @param datastoreClass The datastore class to convert.
+     * @return The corresponding logical datastore type.
+     * @throws IllegalArgumentException if the provided datastore class isn’t handled.
+     */
+    public static LogicalDatastoreType toType(Class<? extends Datastore> datastoreClass) {
+        if (datastoreClass.equals(Configuration.class)) {
+            return LogicalDatastoreType.CONFIGURATION;
+        } else if (Operational.class.equals(datastoreClass)) {
+            return LogicalDatastoreType.OPERATIONAL;
+        } else {
+            throw new IllegalArgumentException("Unknown datastore class " + datastoreClass);
+        }
+    }
+
+    /**
+     * Returns the datastore class corresponding to the given logical datastore type.
+     * @param datastoreType The logical datastore type to convert.
+     * @return The corresponding datastore class.
+     * @throws IllegalArgumentException if the provided logical datastore type isn’t handled.
+     */
+    public static Class<? extends Datastore> toClass(LogicalDatastoreType datastoreType) {
+        switch (datastoreType) {
+            case CONFIGURATION:
+                return CONFIGURATION;
+            case OPERATIONAL:
+                return OPERATIONAL;
+            default:
+                throw new IllegalArgumentException("Unknown datastore type " + datastoreType);
+        }
+    }
+
+    private Datastore() {}
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/InterruptibleCheckedConsumer.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/InterruptibleCheckedConsumer.java
new file mode 100644 (file)
index 0000000..7f43a41
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2017 Red Hat, 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.util;
+
+import java.util.function.Consumer;
+
+/**
+ * {@link Consumer} which can throw a checked exception and be interrupted.
+ *
+ * @param <T> the type of the input to the operation
+ * @param <E> the type of the Exception to the operation
+ *
+ * @see Consumer
+ */
+@FunctionalInterface
+public interface InterruptibleCheckedConsumer<T, E extends Exception> {
+
+    void accept(T input) throws E, InterruptedException;
+
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/InterruptibleCheckedFunction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/InterruptibleCheckedFunction.java
new file mode 100644 (file)
index 0000000..acf480e
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+/**
+ * {@link java.util.function.Function} which can throw a checked exception and be interrupted.
+ *
+ * @param <T> The type of the input to be processed.
+ * @param <R> The type of the result to be returned.
+ * @param <E> The type of the exception which may be thrown.
+ */
+@FunctionalInterface
+public interface InterruptibleCheckedFunction<T, R, E extends Exception> {
+    R apply(T input) throws E, InterruptedException;
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunner.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunner.java
new file mode 100644 (file)
index 0000000..f5aeaef
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2017 Red Hat, 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.util;
+
+import com.google.common.annotations.Beta;
+import java.util.function.Function;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+
+/**
+ * Managed transactions utility to simplify handling of new transactions and ensure they are always closed.
+ * Implementation in {@link ManagedNewTransactionRunnerImpl}, alternative implementation of this API with optional
+ * retries is {@link RetryingManagedNewTransactionRunner}.
+ *
+ * <p>This should typically be used (only) directly in code which really must be creating its own new transactions,
+ * such as RPC entry points, or background jobs.  Other lower level code "behind" such entry points should
+ * just get handed over the transaction provided by this API.
+ */
+@Beta
+public interface ManagedNewTransactionRunner extends ManagedTransactionFactory {
+
+    /**
+     * Invokes a function with a new {@link ManagedTransactionChain}, which is a wrapper around standard transaction
+     * chains providing managed semantics. The transaction chain will be closed when the function returns.
+     *
+     * <p>This is an asynchronous API, like {@link DataBroker}'s own; when this method returns, the transactions in
+     * the chain may well still be ongoing in the background, or pending. <strong>It is up to the consumer and
+     * caller</strong> to agree on how failure will be handled; for example, the return type can include the futures
+     * corresponding to the transactions in the chain. The implementation uses a default transaction chain listener
+     * which logs an error if any of the transactions fail.
+     *
+     * <p>The MD-SAL transaction chain semantics are preserved: each transaction in the chain will see the results of
+     * the previous transactions in the chain, even if they haven't been fully committed yet; and any error will result
+     * in subsequent transactions in the chain <strong>not</strong> being submitted.
+     *
+     * @param chainConsumer The {@link Function} that will build transactions in the transaction chain.
+     * @param <R> The type of result returned by the function.
+     * @return The result of the function call.
+     */
+    <R> R applyWithNewTransactionChainAndClose(Function<ManagedTransactionChain, R> chainConsumer);
+
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunnerImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedNewTransactionRunnerImpl.java
new file mode 100644 (file)
index 0000000..955cf2a
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2017 Red Hat, 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.util;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import com.google.common.util.concurrent.FluentFuture;
+import java.util.function.Function;
+import javax.annotation.CheckReturnValue;
+import javax.inject.Inject;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.Transaction;
+import org.opendaylight.mdsal.binding.api.TransactionChain;
+import org.opendaylight.mdsal.binding.api.TransactionChainListener;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of {@link ManagedNewTransactionRunner}. This is based on {@link ManagedTransactionFactoryImpl} but
+ * re-implements operations based on read-write transactions to cancel transactions which don't end up making any
+ * changes to the datastore.
+ */
+@Beta
+// Do *NOT* mark this as @Singleton, because users choose their implementation
+public class ManagedNewTransactionRunnerImpl extends ManagedTransactionFactoryImpl<DataBroker>
+        implements ManagedNewTransactionRunner {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ManagedNewTransactionRunnerImpl.class);
+
+    @Inject
+    public ManagedNewTransactionRunnerImpl(DataBroker broker) {
+        // Early check to ensure the error message is understandable for the caller
+        super(requireNonNull(broker, "broker must not be null"));
+    }
+
+    // This is overridden to use this class’s commit method
+    @Override
+    @CheckReturnValue
+    public <D extends Datastore, E extends Exception, R> FluentFuture<R> applyWithNewReadWriteTransactionAndSubmit(
+            Class<D> datastoreType, InterruptibleCheckedFunction<TypedReadWriteTransaction<D>, R, E> txFunction) {
+        return super.applyWithNewTransactionAndSubmit(datastoreType, getTransactionFactory()::newReadWriteTransaction,
+            WriteTrackingTypedReadWriteTransactionImpl::new, txFunction::apply, this::commit);
+    }
+
+    @Override
+    public <R> R applyWithNewTransactionChainAndClose(Function<ManagedTransactionChain, R> chainConsumer) {
+        try (TransactionChain realTxChain = getTransactionFactory().createTransactionChain(
+            new TransactionChainListener() {
+                @Override
+                public void onTransactionChainFailed(TransactionChain chain, Transaction transaction, Throwable cause) {
+                    LOG.error("Error handling a transaction chain", cause);
+                }
+
+                @Override
+                public void onTransactionChainSuccessful(TransactionChain chain) {
+                    // Nothing to do
+                }
+            })) {
+            return chainConsumer.apply(new ManagedTransactionChainImpl(realTxChain));
+        }
+    }
+
+    // This is overridden to use this class’s commit method
+    @Override
+    @CheckReturnValue
+    public <D extends Datastore, E extends Exception> FluentFuture<? extends Object>
+        callWithNewReadWriteTransactionAndSubmit(Class<D> datastoreType,
+            InterruptibleCheckedConsumer<TypedReadWriteTransaction<D>, E> txConsumer) {
+        return callWithNewTransactionAndSubmit(datastoreType, getTransactionFactory()::newReadWriteTransaction,
+            WriteTrackingTypedReadWriteTransactionImpl::new, txConsumer::accept, this::commit);
+    }
+
+    // This is overridden to use this class’s commit method
+    @Override
+    @CheckReturnValue
+    public <D extends Datastore, E extends Exception> FluentFuture<? extends Object> callWithNewWriteOnlyTransactionAndSubmit(
+            Class<D> datastoreType, InterruptibleCheckedConsumer<TypedWriteTransaction<D>, E> txConsumer) {
+        return super.callWithNewTransactionAndSubmit(datastoreType, getTransactionFactory()::newWriteOnlyTransaction,
+            WriteTrackingTypedWriteTransactionImpl::new, txConsumer::accept, this::commit);
+    }
+
+    @CheckReturnValue
+    private FluentFuture<? extends CommitInfo> commit(WriteTransaction realTx, WriteTrackingTransaction wrappedTx) {
+        if (wrappedTx.isWritten()) {
+            // The transaction contains changes, commit it
+            return realTx.commit();
+        } else {
+            // The transaction only handled reads, cancel it
+            realTx.cancel();
+            return CommitInfo.emptyFluentFuture();
+        }
+    }
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionChain.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionChain.java
new file mode 100644 (file)
index 0000000..d79ea89
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+/**
+ * Managed transaction chains provide managed semantics around transaction chains, <em>i.e.</em> chains which provide
+ * transactions which are automatically submitted or cancelled.
+ */
+public interface ManagedTransactionChain extends ManagedTransactionFactory {
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionChainImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionChainImpl.java
new file mode 100644 (file)
index 0000000..f3d976c
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import org.opendaylight.mdsal.binding.api.TransactionChain;
+
+/**
+ * Implementation of {@link ManagedTransactionChain}, based on {@link ManagedTransactionFactoryImpl}.
+ */
+class ManagedTransactionChainImpl extends ManagedTransactionFactoryImpl implements ManagedTransactionChain {
+    ManagedTransactionChainImpl(TransactionChain realTxChain) {
+        super(realTxChain);
+    }
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionFactory.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionFactory.java
new file mode 100644 (file)
index 0000000..4a27344
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.Futures;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Future;
+import javax.annotation.CheckReturnValue;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.ReadTransaction;
+import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+
+/**
+ * Managed transaction factories provide managed transactions, <em>i.e.</em> transactions which are automatically
+ * submitted or cancelled (write) or closed (read).
+ * <p>
+ * This is a common interface for broker- and chain-based transaction managers, and should not be used directly.
+ */
+public interface ManagedTransactionFactory {
+    /**
+     * Invokes a function with a <b>NEW</b> {@link TypedReadTransaction}, and ensures that that transaction is closed.
+     * Thus when this method returns, that transaction is guaranteed to have been closed, and will never "leak" and
+     * waste memory.
+     *
+     * <p>The function must not itself attempt to close the transaction. (It can't directly, since
+     * {@link TypedReadTransaction} doesn't expose a {@code close()} method.)
+     *
+     * <p>The provided transaction is specific to the given logical datastore type and cannot be used for any
+     * other.
+     *
+     * @param datastoreType the {@link Datastore} type that will be accessed
+     * @param txFunction the {@link InterruptibleCheckedFunction} that needs a new read transaction
+     * @return the result of the function.
+     * @throws E if an error occurs.
+     * @throws InterruptedException if the function is interrupted (this is passed through from the provided function).
+     */
+    <D extends Datastore, E extends Exception, R> R applyInterruptiblyWithNewReadOnlyTransactionAndClose(
+        Class<D> datastoreType, InterruptibleCheckedFunction<TypedReadTransaction<D>, R, E> txFunction)
+        throws E, InterruptedException;
+
+    /**
+     * Invokes a function with a <b>NEW</b> {@link TypedReadTransaction}, and ensures that that transaction is closed.
+     * Thus when this method returns, that transaction is guaranteed to have been closed, and will never "leak" and
+     * waste memory.
+     *
+     * <p>The function must not itself attempt to close the transaction. (It can't directly, since
+     * {@link TypedReadTransaction} doesn't expose a {@code close()} method.)
+     *
+     * <p>The provided transaction is specific to the given logical datastore type and cannot be used for any
+     * other.
+     *
+     * @param datastoreType the {@link Datastore} type that will be accessed
+     * @param txFunction the {@link InterruptibleCheckedFunction} that needs a new read transaction
+     * @return the result of the function.
+     * @throws E if an error occurs.
+     */
+    <D extends Datastore, E extends Exception, R> R applyWithNewReadOnlyTransactionAndClose(Class<D> datastoreType,
+        CheckedFunction<TypedReadTransaction<D>, R, E> txFunction) throws E;
+
+    /**
+     * Invokes a function with a <b>NEW</b> {@link ReadWriteTransaction}, and then submits that transaction and
+     * returns the Future from that submission, or cancels it if an exception was thrown and returns a failed
+     * future with that exception. Thus when this method returns, that transaction is guaranteed to have
+     * been either submitted or cancelled, and will never "leak" and waste memory.
+     *
+     * <p>The function must not itself use
+     * {@link ReadWriteTransaction#cancel()}, or
+     * {@link ReadWriteTransaction#commit()} (it will throw an {@link UnsupportedOperationException}).
+     *
+     * <p>The provided transaction is specific to the given logical datastore type and cannot be used for any
+     * other.
+     *
+     * <p>This is an asynchronous API, like {@link DataBroker}'s own;
+     * when returning from this method, the operation of the Transaction may well still be ongoing in the background,
+     * or pending;
+     * calling code therefore <b>must</b> handle the returned future, e.g. by passing it onwards (return),
+     * or by itself adding callback listeners to it using {@link Futures}' methods, or by transforming it into a
+     * {@link CompletionStage}
+     * (but better NOT by using the blocking {@link Future#get()} on it).
+     *
+     * @param datastoreType the {@link Datastore} type that will be accessed
+     * @param txFunction the {@link InterruptibleCheckedFunction} that needs a new read-write transaction
+     * @return the {@link FluentFuture} returned by {@link ReadWriteTransaction#commit()}, or a failed future with an
+     * application specific exception (not from submit())
+     */
+    @CheckReturnValue
+    <D extends Datastore, E extends Exception, R>
+        FluentFuture<R> applyWithNewReadWriteTransactionAndSubmit(Class<D> datastoreType,
+            InterruptibleCheckedFunction<TypedReadWriteTransaction<D>, R, E> txFunction);
+
+    /**
+     * Invokes a function with a <b>NEW</b> {@link ReadTransaction}, and ensures that that transaction is closed.
+     * Thus when this method returns, that transaction is guaranteed to have been closed, and will never "leak" and
+     * waste memory.
+     *
+     * <p>The function must not itself attempt to close the transaction. (It can't directly, since
+     * {@link ReadTransaction} doesn't expose a {@code close()} method.)
+     *
+     * <p>The provided transaction is specific to the given logical datastore type and cannot be used for any
+     * other.
+     *
+     * @param datastoreType the {@link Datastore} type that will be accessed
+     * @param txConsumer the {@link InterruptibleCheckedFunction} that needs a new read transaction
+     * @throws E if an error occurs.
+     * @throws InterruptedException if the function is interrupted (this is passed through from the provided function).
+     */
+    <D extends Datastore, E extends Exception> void callInterruptiblyWithNewReadOnlyTransactionAndClose(
+        Class<D> datastoreType, InterruptibleCheckedConsumer<TypedReadTransaction<D>, E> txConsumer)
+        throws E, InterruptedException;
+
+    /**
+     * Invokes a function with a <b>NEW</b> {@link ReadTransaction}, and ensures that that transaction is closed.
+     * Thus when this method returns, that transaction is guaranteed to have been closed, and will never "leak" and
+     * waste memory.
+     *
+     * <p>The function must not itself attempt to close the transaction. (It can't directly, since
+     * {@link ReadTransaction} doesn't expose a {@code close()} method.)
+     *
+     * <p>The provided transaction is specific to the given logical datastore type and cannot be used for any
+     * other.
+     *
+     * @param datastoreType the {@link Datastore} type that will be accessed
+     * @param txConsumer the {@link InterruptibleCheckedFunction} that needs a new read transaction
+     * @throws E if an error occurs.
+     */
+    <D extends Datastore, E extends Exception> void callWithNewReadOnlyTransactionAndClose(Class<D> datastoreType,
+        CheckedConsumer<TypedReadTransaction<D>, E> txConsumer) throws E;
+
+    /**
+     * Invokes a consumer with a <b>NEW</b> {@link ReadWriteTransaction}, and then submits that transaction and
+     * returns the Future from that submission, or cancels it if an exception was thrown and returns a failed
+     * future with that exception. Thus when this method returns, that transaction is guaranteed to have
+     * been either submitted or cancelled, and will never "leak" and waste memory.
+     *
+     * <p>The consumer should not (cannot) itself use
+     * {@link ReadWriteTransaction#cancel()}, or
+     * {@link ReadWriteTransaction#commit()} (it will throw an {@link UnsupportedOperationException}).
+     *
+     * <p>The provided transaction is specific to the given logical datastore type and cannot be used for any
+     * other.
+     *
+     * <p>This is an asynchronous API, like {@link DataBroker}'s own;
+     * when returning from this method, the operation of the Transaction may well still be ongoing in the background,
+     * or pending;
+     * calling code therefore <b>must</b> handle the returned future, e.g. by passing it onwards (return),
+     * or by itself adding callback listeners to it using {@link Futures}' methods, or by transforming it into a
+     * {@link CompletionStage}
+     * (but better NOT by using the blocking {@link Future#get()} on it).
+     *
+     * @param datastoreType the {@link Datastore} type that will be accessed
+     * @param txConsumer the {@link InterruptibleCheckedConsumer} that needs a new read-write transaction
+     * @return the {@link FluentFuture} returned by {@link ReadWriteTransaction#commit()}, or a failed future with an
+     * application specific exception (not from submit())
+     */
+    @CheckReturnValue
+    <D extends Datastore, E extends Exception>
+        FluentFuture<? extends Object> callWithNewReadWriteTransactionAndSubmit(Class<D> datastoreType,
+            InterruptibleCheckedConsumer<TypedReadWriteTransaction<D>, E> txConsumer);
+
+    /**
+     * Invokes a consumer with a <b>NEW</b> {@link WriteTransaction}, and then submits that transaction and
+     * returns the Future from that submission, or cancels it if an exception was thrown and returns a failed
+     * future with that exception. Thus when this method returns, that transaction is guaranteed to have
+     * been either submitted or cancelled, and will never "leak" and waste memory.
+     *
+     * <p>The consumer should not (cannot) itself use
+     * {@link WriteTransaction#cancel()}, or
+     * {@link WriteTransaction#commit()} (it will throw an {@link UnsupportedOperationException}).
+     *
+     * <p>The provided transaction is specific to the given logical datastore type and cannot be used for any
+     * other.
+     *
+     * <p>This is an asynchronous API, like {@link DataBroker}'s own;
+     * when returning from this method, the operation of the Transaction may well still be ongoing in the background,
+     * or pending;
+     * calling code therefore <b>must</b> handle the returned future, e.g. by passing it onwards (return),
+     * or by itself adding callback listeners to it using {@link Futures}' methods, or by transforming it into a
+     * {@link CompletionStage}
+     * (but better NOT by using the blocking {@link Future#get()} on it).
+     *
+     * @param datastoreType the {@link Datastore} type that will be accessed
+     * @param txConsumer the {@link InterruptibleCheckedConsumer} that needs a new write only transaction
+     * @return the {@link FluentFuture} returned by {@link WriteTransaction#commit()}, or a failed future with an
+     * application specific exception (not from submit())
+     */
+    @CheckReturnValue
+    <D extends Datastore, E extends Exception>
+        FluentFuture<? extends Object> callWithNewWriteOnlyTransactionAndSubmit(Class<D> datastoreType,
+            InterruptibleCheckedConsumer<TypedWriteTransaction<D>, E> txConsumer);
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionFactoryImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/ManagedTransactionFactoryImpl.java
new file mode 100644 (file)
index 0000000..299ef15
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+import javax.annotation.CheckReturnValue;
+import org.opendaylight.mdsal.binding.api.ReadTransaction;
+import org.opendaylight.mdsal.binding.api.TransactionFactory;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.yangtools.util.concurrent.FluentFutures;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Basic implementation of a {@link ManagedTransactionFactory}.
+ */
+class ManagedTransactionFactoryImpl<T extends TransactionFactory> implements ManagedTransactionFactory {
+    private static final Logger LOG = LoggerFactory.getLogger(ManagedTransactionFactoryImpl.class);
+
+    private final T transactionFactory;
+
+    ManagedTransactionFactoryImpl(T transactionFactory) {
+        this.transactionFactory = requireNonNull(transactionFactory, "transactionFactory must not be null");
+    }
+
+    @Override
+    public <D extends Datastore, E extends Exception, R> R applyInterruptiblyWithNewReadOnlyTransactionAndClose(
+            Class<D> datastoreType, InterruptibleCheckedFunction<TypedReadTransaction<D>, R, E> txFunction)
+            throws E, InterruptedException {
+        try (ReadTransaction realTx = transactionFactory.newReadOnlyTransaction()) {
+            TypedReadTransaction<D>
+                wrappedTx = new TypedReadTransactionImpl<>(datastoreType, realTx);
+            return txFunction.apply(wrappedTx);
+        }
+    }
+
+    @Override
+    public <D extends Datastore, E extends Exception, R> R applyWithNewReadOnlyTransactionAndClose(
+            Class<D> datastoreType, CheckedFunction<TypedReadTransaction<D>, R, E> txFunction) throws E {
+        try (ReadTransaction realTx = transactionFactory.newReadOnlyTransaction()) {
+            TypedReadTransaction<D>
+                wrappedTx = new TypedReadTransactionImpl<>(datastoreType, realTx);
+            return txFunction.apply(wrappedTx);
+        }
+    }
+
+    @Override
+    @CheckReturnValue
+    public <D extends Datastore, E extends Exception, R>
+        FluentFuture<R> applyWithNewReadWriteTransactionAndSubmit(Class<D> datastoreType,
+            InterruptibleCheckedFunction<TypedReadWriteTransaction<D>, R, E> txFunction) {
+        return applyWithNewTransactionAndSubmit(datastoreType, transactionFactory::newReadWriteTransaction,
+            TypedReadWriteTransactionImpl::new, txFunction, (realTx, wrappedTx) -> realTx.commit());
+    }
+
+    @Override
+    public <D extends Datastore, E extends Exception> void callInterruptiblyWithNewReadOnlyTransactionAndClose(
+            Class<D> datastoreType, InterruptibleCheckedConsumer<TypedReadTransaction<D>, E> txConsumer)
+            throws E, InterruptedException {
+        try (ReadTransaction realTx = transactionFactory.newReadOnlyTransaction()) {
+            TypedReadTransaction<D> wrappedTx = new TypedReadTransactionImpl<>(datastoreType, realTx);
+            txConsumer.accept(wrappedTx);
+        }
+    }
+
+    @Override
+    public <D extends Datastore, E extends Exception> void callWithNewReadOnlyTransactionAndClose(
+            Class<D> datastoreType, CheckedConsumer<TypedReadTransaction<D>, E> txConsumer) throws E {
+        try (ReadTransaction realTx = transactionFactory.newReadOnlyTransaction()) {
+            TypedReadTransaction<D> wrappedTx = new TypedReadTransactionImpl<>(datastoreType, realTx);
+            txConsumer.accept(wrappedTx);
+        }
+    }
+
+    @Override
+    @CheckReturnValue
+    public <D extends Datastore, E extends Exception>
+        FluentFuture<? extends Object> callWithNewReadWriteTransactionAndSubmit(Class<D> datastoreType,
+            InterruptibleCheckedConsumer<TypedReadWriteTransaction<D>, E> txConsumer) {
+        return callWithNewTransactionAndSubmit(datastoreType, transactionFactory::newReadWriteTransaction,
+            TypedReadWriteTransactionImpl::new, txConsumer, (realTx, wrappedTx) -> realTx.commit());
+    }
+
+    @Override
+    @CheckReturnValue
+    public <D extends Datastore, E extends Exception> FluentFuture<? extends Object> callWithNewWriteOnlyTransactionAndSubmit(
+            Class<D> datastoreType, InterruptibleCheckedConsumer<TypedWriteTransaction<D>, E> txConsumer) {
+        return callWithNewTransactionAndSubmit(datastoreType, transactionFactory::newWriteOnlyTransaction,
+            TypedWriteTransactionImpl::new, txConsumer, (realTx, wrappedTx) -> realTx.commit());
+    }
+
+    @CheckReturnValue
+    protected <D extends Datastore, T extends WriteTransaction, W, E extends Exception> FluentFuture<? extends Object>
+        callWithNewTransactionAndSubmit(
+            Class<D> datastoreType, Supplier<T> txSupplier, BiFunction<Class<D>, T, W> txWrapper,
+            InterruptibleCheckedConsumer<W, E> txConsumer, BiFunction<T, W, FluentFuture<?>> txSubmitter) {
+        return applyWithNewTransactionAndSubmit(datastoreType, txSupplier, txWrapper, tx -> {
+            txConsumer.accept(tx);
+            return null;
+        }, txSubmitter);
+    }
+
+    @CheckReturnValue
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    protected <D extends Datastore, T extends WriteTransaction, W, R, E extends Exception> FluentFuture<R>
+        applyWithNewTransactionAndSubmit(
+            Class<D> datastoreType, Supplier<T> txSupplier, BiFunction<Class<D>, T, W> txWrapper,
+            InterruptibleCheckedFunction<W, R, E> txFunction, BiFunction<T, W, FluentFuture<?>> txSubmitter) {
+        T realTx = txSupplier.get();
+        W wrappedTx = txWrapper.apply(datastoreType, realTx);
+        R result;
+        try {
+            // We must store the result before submitting the transaction; if we inline the next line in the
+            // transform lambda, that's not guaranteed
+            result = txFunction.apply(wrappedTx);
+        } catch (Exception e) {
+            // catch Exception for both the <E extends Exception> thrown by accept() as well as any RuntimeException
+            if (!realTx.cancel()) {
+                LOG.error("Transaction.cancel() returned false - this should never happen (here)");
+            }
+            return FluentFutures.immediateFailedFluentFuture(e);
+        }
+        return txSubmitter.apply(realTx, wrappedTx).transform(v -> result, MoreExecutors.directExecutor());
+    }
+
+    protected T getTransactionFactory() {
+        return transactionFactory;
+    }
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunner.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunner.java
new file mode 100644 (file)
index 0000000..30dc642
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2017 Red Hat, 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.util;
+
+import com.google.common.annotations.Beta;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.concurrent.Executor;
+import javax.inject.Inject;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.common.api.OptimisticLockFailedException;
+
+/**
+ * Implementation of {@link ManagedNewTransactionRunner} with automatic transparent retries.
+ *
+ * <h3>Details about the threading model used by this class</h3>
+ *
+ * <p>This class runs the first attempt to call the delegated {@link ManagedNewTransactionRunner},
+ * which typically is a {@link ManagedNewTransactionRunnerImpl} which safely invokes {@link WriteTransaction#commit()},
+ * in the using application's thread (like a {@link MoreExecutors#directExecutor()} would, if this were an
+ * {@link Executor}, which it's not).
+ *
+ * <p>Any retry attempts required, if that <code>submit()</code> (eventually) fails with an
+ * {@link OptimisticLockFailedException}, are run in the calling thread of that eventual future completion by a
+ * {@link MoreExecutors#directExecutor()} implicit in the constructor which does not require you to specify an
+ * explicit Executor argument.  Normally that will be an internal thread from the respective DataBroker implementation,
+ * not your application's thread anymore, because that meanwhile could well be off doing something else!  Normally,
+ * that is not a problem, because retries "should" be relatively uncommon, and (re)issuing some DataBroker
+ * <code>put()</code> or <code>delete()</code> and <code>re-submit()</code> <i>should</i> be fast.
+ *
+ * <p>If this default is not suitable (e.g. for particularly slow try/retry code), then you can specify
+ * another {@link Executor} to be used for the retries by using the alternative constructor.
+ *
+ * @author Michael Vorburger.ch &amp; Stephen Kitt
+ */
+@Beta
+// Do *NOT* mark this as @Singleton, because users choose Impl; and as long as this in API, because of https://wiki.opendaylight.org/view/BestPractices/DI_Guidelines#Nota_Bene
+public class RetryingManagedNewTransactionRunner extends RetryingManagedNewTransactionRunnerImpl {
+
+    /**
+     * Constructor.
+     * Please see the class level documentation above for more details about the threading model used.
+     * This uses the default of 3 retries, which is typically suitable.
+     *
+     * @param dataBroker the {@link DataBroker} from which transactions are obtained
+     * @throws NullPointerException if {@code dataBroker} is {@code null}.
+     */
+    @Inject
+    public RetryingManagedNewTransactionRunner(DataBroker dataBroker) {
+        super(new ManagedNewTransactionRunnerImpl(dataBroker));
+    }
+
+    /**
+     * Constructor.
+     * Please see the class level documentation above for more details about the threading model used.
+     *
+     * @param dataBroker the {@link DataBroker} from which transactions are obtained
+     * @param maxRetries the maximum number of retry attempts
+     */
+    public RetryingManagedNewTransactionRunner(DataBroker dataBroker, int maxRetries) {
+        super(new ManagedNewTransactionRunnerImpl(dataBroker), maxRetries);
+    }
+
+    /**
+     * Constructor.
+     * Please see the class level documentation above for more details about the threading model used.
+     *
+     * @param dataBroker the {@link DataBroker} from which transactions are obtained
+     * @param executor the {@link Executor} to asynchronously run any retry attempts in
+     * @param maxRetries the maximum number of retry attempts
+     */
+    public RetryingManagedNewTransactionRunner(DataBroker dataBroker, Executor executor, int maxRetries) {
+        super(new ManagedNewTransactionRunnerImpl(dataBroker), executor, maxRetries);
+    }
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunnerImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/RetryingManagedNewTransactionRunnerImpl.java
new file mode 100644 (file)
index 0000000..412bcbe
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2017 Red Hat, 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.util;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.function.Function;
+import org.opendaylight.mdsal.common.api.OptimisticLockFailedException;
+import org.opendaylight.mdsal.common.api.ReadFailedException;
+
+/**
+ * Implementation of {@link ManagedNewTransactionRunner} with automatic transparent retries on transaction failure
+ * ({@link OptimisticLockFailedException} on write transactions and {@link ReadFailedException} on read transactions
+ * will cause the operation constructing the transaction to be re-run).
+ * This is a package local private internal class; end-users use the {@link RetryingManagedNewTransactionRunner}.
+ * @see RetryingManagedNewTransactionRunner
+ *
+ * @author Michael Vorburger.ch &amp; Stephen Kitt, with input from Tom Pantelis re. catchingAsync &amp; direct Executor
+ */
+// intentionally package local
+class RetryingManagedNewTransactionRunnerImpl implements ManagedNewTransactionRunner {
+
+    // NB: The RetryingManagedNewTransactionRunnerTest is in mdsalutil-testutils's src/test, not this project's
+
+    private static final int DEFAULT_RETRIES = 3; // duplicated in SingleTransactionDataBroker
+
+    private final int maxRetries;
+
+    private final ManagedNewTransactionRunner delegate;
+
+    private final Executor executor;
+
+    RetryingManagedNewTransactionRunnerImpl(ManagedNewTransactionRunner delegate) {
+        this(delegate, MoreExecutors.directExecutor(), DEFAULT_RETRIES);
+    }
+
+    RetryingManagedNewTransactionRunnerImpl(ManagedNewTransactionRunner delegate, int maxRetries) {
+        this(delegate, MoreExecutors.directExecutor(), maxRetries);
+    }
+
+    RetryingManagedNewTransactionRunnerImpl(ManagedNewTransactionRunner delegate, Executor executor, int maxRetries) {
+        this.delegate = requireNonNull(delegate, "delegate must not be null");
+        this.executor = requireNonNull(executor, "executor must not be null");
+        this.maxRetries = maxRetries;
+    }
+
+    @Override
+    public <D extends Datastore, E extends Exception, R> R applyInterruptiblyWithNewReadOnlyTransactionAndClose(
+            Class<D> datastoreType, InterruptibleCheckedFunction<TypedReadTransaction<D>, R, E> txFunction)
+            throws E, InterruptedException {
+        return applyInterruptiblyWithNewReadOnlyTransactionAndClose(datastoreType, txFunction, maxRetries);
+    }
+
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    private <R, D extends Datastore, E extends Exception> R applyInterruptiblyWithNewReadOnlyTransactionAndClose(
+            Class<D> datastoreType, InterruptibleCheckedFunction<TypedReadTransaction<D>, R, E> txFunction,
+            int tries) throws E, InterruptedException {
+        try {
+            return delegate.applyInterruptiblyWithNewReadOnlyTransactionAndClose(datastoreType, txFunction);
+        } catch (Exception e) {
+            if (isRetriableException(e) && tries - 1 > 0) {
+                return applyInterruptiblyWithNewReadOnlyTransactionAndClose(datastoreType, txFunction, tries - 1);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public <D extends Datastore, E extends Exception, R> R applyWithNewReadOnlyTransactionAndClose(
+            Class<D> datastoreType, CheckedFunction<TypedReadTransaction<D>, R, E> txFunction) throws E {
+        return applyWithNewReadOnlyTransactionAndClose(datastoreType, txFunction, maxRetries);
+    }
+
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    private <R, D extends Datastore, E extends Exception> R applyWithNewReadOnlyTransactionAndClose(
+        Class<D> datastoreType, CheckedFunction<TypedReadTransaction<D>, R, E> txFunction, int tries) throws E {
+        try {
+            return delegate.applyWithNewReadOnlyTransactionAndClose(datastoreType, txFunction);
+        } catch (Exception e) {
+            if (isRetriableException(e) && tries - 1 > 0) {
+                return applyWithNewReadOnlyTransactionAndClose(datastoreType, txFunction, tries - 1);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public <D extends Datastore, E extends Exception, R> FluentFuture<R> applyWithNewReadWriteTransactionAndSubmit(
+            Class<D> datastoreType, InterruptibleCheckedFunction<TypedReadWriteTransaction<D>, R, E> txFunction) {
+        return applyWithNewReadWriteTransactionAndSubmit(datastoreType, txFunction, maxRetries);
+    }
+
+    private <D extends Datastore, E extends Exception, R> FluentFuture<R> applyWithNewReadWriteTransactionAndSubmit(
+            Class<D> datastoreType, InterruptibleCheckedFunction<TypedReadWriteTransaction<D>, R, E> txRunner,
+            int tries) {
+        FluentFuture<R> future = requireNonNull(
+            delegate.applyWithNewReadWriteTransactionAndSubmit(datastoreType, txRunner),
+            "delegate.callWithNewReadWriteTransactionAndSubmit() == null");
+        return future.catchingAsync(Exception.class, exception -> {
+            if (isRetriableException(exception) && tries - 1 > 0) {
+                return applyWithNewReadWriteTransactionAndSubmit(datastoreType, txRunner, tries - 1);
+            } else {
+                throw exception;
+            }
+        }, executor);
+    }
+
+    @Override
+    public <R> R applyWithNewTransactionChainAndClose(Function<ManagedTransactionChain, R> chainConsumer) {
+        throw new UnsupportedOperationException("The retrying transaction manager doesn't support transaction chains");
+    }
+
+    @Override
+    public <D extends Datastore, E extends Exception> void callInterruptiblyWithNewReadOnlyTransactionAndClose(
+            Class<D> datastoreType, InterruptibleCheckedConsumer<TypedReadTransaction<D>, E> txConsumer)
+            throws E, InterruptedException {
+        callInterruptiblyWithNewReadOnlyTransactionAndClose(datastoreType, txConsumer, maxRetries);
+    }
+
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    private <D extends Datastore, E extends Exception> void callInterruptiblyWithNewReadOnlyTransactionAndClose(
+            Class<D> datastoreType, InterruptibleCheckedConsumer<TypedReadTransaction<D>, E> txConsumer, int tries)
+            throws E, InterruptedException {
+        try {
+            delegate.callInterruptiblyWithNewReadOnlyTransactionAndClose(datastoreType, txConsumer);
+        } catch (Exception e) {
+            if (isRetriableException(e) && tries - 1 > 0) {
+                callInterruptiblyWithNewReadOnlyTransactionAndClose(datastoreType, txConsumer, tries - 1);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public <D extends Datastore, E extends Exception> void callWithNewReadOnlyTransactionAndClose(
+            Class<D> datastoreType, CheckedConsumer<TypedReadTransaction<D>, E> txConsumer) throws E {
+        callWithNewReadOnlyTransactionAndClose(datastoreType, txConsumer, maxRetries);
+    }
+
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    private <D extends Datastore, E extends Exception> void callWithNewReadOnlyTransactionAndClose(
+            Class<D> datastoreType, CheckedConsumer<TypedReadTransaction<D>, E> txConsumer, int tries) throws E {
+        try {
+            delegate.callWithNewReadOnlyTransactionAndClose(datastoreType, txConsumer);
+        } catch (Exception e) {
+            if (isRetriableException(e) && tries - 1 > 0) {
+                callWithNewReadOnlyTransactionAndClose(datastoreType, txConsumer, tries - 1);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public <D extends Datastore, E extends Exception> FluentFuture<? extends Object>
+        callWithNewReadWriteTransactionAndSubmit(
+            Class<D> datastoreType, InterruptibleCheckedConsumer<TypedReadWriteTransaction<D>, E> txConsumer) {
+        return callWithNewReadWriteTransactionAndSubmit(datastoreType, txConsumer, maxRetries);
+    }
+
+    private <D extends Datastore, E extends Exception, T> FluentFuture<T>
+        callWithNewReadWriteTransactionAndSubmit(Class<D> datastoreType,
+            InterruptibleCheckedConsumer<TypedReadWriteTransaction<D>, E> txRunner, int tries) {
+
+        return (FluentFuture<T>) requireNonNull(
+            delegate.callWithNewReadWriteTransactionAndSubmit(datastoreType, txRunner),
+            "delegate.callWithNewWriteOnlyTransactionAndSubmit() == null")
+            .catchingAsync(Exception.class, exception -> {
+                // as per AsyncWriteTransaction.submit()'s JavaDoc re. retries
+                if (isRetriableException(exception) && tries - 1 > 0) {
+                    return callWithNewReadWriteTransactionAndSubmit(datastoreType, txRunner, tries - 1);
+                } else {
+                    // out of retries, so propagate the exception
+                    throw exception;
+                }
+            }, executor);
+    }
+
+    @Override
+    public <D extends Datastore, E extends Exception> FluentFuture<? extends Object>
+        callWithNewWriteOnlyTransactionAndSubmit(Class<D> datastoreType,
+            InterruptibleCheckedConsumer<TypedWriteTransaction<D>, E> txConsumer) {
+        return callWithNewWriteOnlyTransactionAndSubmit(datastoreType, txConsumer, maxRetries);
+    }
+
+    private <D extends Datastore, E extends Exception, T> FluentFuture<T>
+        callWithNewWriteOnlyTransactionAndSubmit(Class<D> datastoreType,
+            InterruptibleCheckedConsumer<TypedWriteTransaction<D>, E> txRunner, int tries) {
+
+        return (FluentFuture<T>) requireNonNull(
+            delegate.callWithNewWriteOnlyTransactionAndSubmit(datastoreType, txRunner),
+            "delegate.callWithNewWriteOnlyTransactionAndSubmit() == null")
+            .catchingAsync(OptimisticLockFailedException.class, optimisticLockFailedException -> {
+                // as per AsyncWriteTransaction.submit()'s JavaDoc re. retries
+                if (tries - 1 > 0) {
+                    return callWithNewWriteOnlyTransactionAndSubmit(datastoreType, txRunner, tries - 1);
+                } else {
+                    // out of retries, so propagate the OptimisticLockFailedException
+                    throw optimisticLockFailedException;
+                }
+            }, executor);
+    }
+
+    private boolean isRetriableException(Throwable throwable) {
+        return throwable instanceof OptimisticLockFailedException || throwable instanceof ReadFailedException || (
+            throwable instanceof ExecutionException && isRetriableException(throwable.getCause()));
+    }
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TransactionAdapter.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TransactionAdapter.java
new file mode 100644 (file)
index 0000000..3511002
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ForwardingObject;
+import com.google.common.util.concurrent.FluentFuture;
+import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Adapter allowing managed, datastore-constrained transactions to be used with methods expecting
+ * generic {@link DataBroker} transactions.
+ *
+ * <p>The adapted transactions maintain the following constraints: they cannot be cancelled or
+ * submitted (only the transaction manager can do this), and they cannot access a logical datastore
+ * other than the one they were created for.
+ *
+ * @deprecated This is only intended for temporary use during complex migrations to managed transactions.
+ */
+@Deprecated
+public final class TransactionAdapter {
+    private TransactionAdapter() {
+    }
+
+    /**
+     * Adapts the given datastore-constrained read-write transaction to a generic read-write transaction.
+     *
+     * @param datastoreTx The transaction to adapt.
+     * @return The adapted transaction.
+     * @throws NullPointerException if the provided transaction is {@code null}.
+     */
+    public static ReadWriteTransaction toReadWriteTransaction(
+            TypedReadWriteTransaction<? extends Datastore> datastoreTx) {
+        if (datastoreTx instanceof TypedReadWriteTransactionImpl) {
+            TypedReadWriteTransactionImpl nonSubmitCancelableDatastoreReadWriteTransaction =
+                    (TypedReadWriteTransactionImpl) datastoreTx;
+            return new ReadWriteTransactionAdapter(nonSubmitCancelableDatastoreReadWriteTransaction.datastoreType,
+                    nonSubmitCancelableDatastoreReadWriteTransaction);
+        }
+        throw new IllegalArgumentException(
+                "Unsupported TypedWriteTransaction implementation " + datastoreTx.getClass());
+    }
+
+    /**
+     * Adapts the given datastore-constrained write transaction to a generic write transaction. Note that this
+     * can be used to adapt a read-write transaction to a write transaction.
+     *
+     * @param datastoreTx The transaction to adapt.
+     * @return The adapted transaction.
+     */
+    public static WriteTransaction toWriteTransaction(TypedWriteTransaction<? extends Datastore> datastoreTx) {
+        if (datastoreTx instanceof TypedWriteTransactionImpl) {
+            TypedWriteTransactionImpl nonSubmitCancelableDatastoreWriteTransaction =
+                    (TypedWriteTransactionImpl) datastoreTx;
+            return new WriteTransactionAdapter(nonSubmitCancelableDatastoreWriteTransaction.datastoreType,
+                    nonSubmitCancelableDatastoreWriteTransaction);
+        }
+        throw new IllegalArgumentException(
+                "Unsupported TypedWriteTransaction implementation " + datastoreTx.getClass());
+    }
+
+    // We want to subclass this class, even though it has a private constructor
+    @SuppressWarnings("FinalClass")
+    private static class WriteTransactionAdapter<D extends Datastore, T extends TypedWriteTransaction<D>>
+            extends ForwardingObject implements WriteTransaction {
+        private final LogicalDatastoreType datastoreType;
+        private final T delegate;
+
+        private WriteTransactionAdapter(LogicalDatastoreType datastoreType, T delegate) {
+            this.datastoreType = datastoreType;
+            this.delegate = delegate;
+        }
+
+        @Override
+        public <T extends DataObject> void put(LogicalDatastoreType store, InstanceIdentifier<T> path, T data) {
+            checkStore(store);
+            delegate.put(path, data);
+        }
+
+        @Override
+        public <T extends DataObject> void put(LogicalDatastoreType store, InstanceIdentifier<T> path, T data,
+                boolean createMissingParents) {
+            checkStore(store);
+            delegate.put(path, data, createMissingParents);
+        }
+
+        @Override
+        public <T extends DataObject> void merge(LogicalDatastoreType store, InstanceIdentifier<T> path, T data) {
+            checkStore(store);
+            delegate.merge(path, data);
+        }
+
+        @Override
+        public <T extends DataObject> void merge(LogicalDatastoreType store, InstanceIdentifier<T> path, T data,
+                boolean createMissingParents) {
+            checkStore(store);
+            delegate.merge(path, data, createMissingParents);
+        }
+
+        @Override
+        public boolean cancel() {
+            throw new UnsupportedOperationException("Managed transactions must not be cancelled");
+        }
+
+        @Override
+        public void delete(LogicalDatastoreType store, InstanceIdentifier<?> path) {
+            checkStore(store);
+            delegate.delete(path);
+        }
+
+        @Override
+        public @NonNull FluentFuture<? extends CommitInfo> commit() {
+            throw new UnsupportedOperationException("Managed transactions must not be committed");
+        }
+
+        void checkStore(LogicalDatastoreType store) {
+            checkArgument(datastoreType.equals(store), "Invalid datastore %s used instead of %s", store, datastoreType);
+        }
+
+        @Override
+        public Object getIdentifier() {
+            return delegate.getIdentifier();
+        }
+
+        @Override
+        protected T delegate() {
+            return delegate;
+        }
+    }
+
+    private static final class ReadWriteTransactionAdapter<D extends Datastore>
+            extends WriteTransactionAdapter<D, TypedReadWriteTransaction<D>> implements ReadWriteTransaction {
+        private ReadWriteTransactionAdapter(LogicalDatastoreType datastoreType, TypedReadWriteTransaction<D> delegate) {
+            super(datastoreType, delegate);
+        }
+
+        @Override
+        public <T extends DataObject> FluentFuture<Optional<T>> read(LogicalDatastoreType store,
+                InstanceIdentifier<T> path) {
+            checkStore(store);
+            return delegate().read(path);
+        }
+    }
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadTransaction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadTransaction.java
new file mode 100644 (file)
index 0000000..f42e692
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import com.google.common.util.concurrent.FluentFuture;
+import java.util.Optional;
+import org.opendaylight.mdsal.binding.api.ReadTransaction;
+import org.opendaylight.mdsal.binding.api.Transaction;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Read transaction which is specific to a single logical datastore (configuration or operational). Designed for use
+ * with {@link ManagedNewTransactionRunner} (it doesn’t support explicit cancel or commit operations).
+ *
+ * @see ReadTransaction
+ *
+ * @param <D> The logical datastore handled by the transaction.
+ */
+public interface TypedReadTransaction<D extends Datastore> extends Transaction {
+    /**
+     * Reads an object from the given path.
+     *
+     * @see ReadTransaction#read(LogicalDatastoreType, InstanceIdentifier)
+     *
+     * @param path The path to read from.
+     * @param <T> The type of the expected object.
+     * @return A future providing access to the result of the read, when it’s available, or any error encountered.
+     */
+    <T extends DataObject> FluentFuture<Optional<T>> read(InstanceIdentifier<T> path);
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadTransactionImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadTransactionImpl.java
new file mode 100644 (file)
index 0000000..c06e240
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import com.google.common.util.concurrent.FluentFuture;
+import java.util.Optional;
+import org.opendaylight.mdsal.binding.api.ReadTransaction;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Implementation of {@link TypedReadTransaction}.
+ *
+ * @param <D> The datastore which the transaction targets.
+ */
+class TypedReadTransactionImpl<D extends Datastore> extends TypedTransaction<D>
+        implements TypedReadTransaction<D> {
+    private final ReadTransaction delegate;
+
+    TypedReadTransactionImpl(Class<D> datastoreType, ReadTransaction realTx) {
+        super(datastoreType);
+        this.delegate = realTx;
+    }
+
+    @Override
+    public <T extends DataObject> FluentFuture<Optional<T>> read(InstanceIdentifier<T> path) {
+        return FluentFuture.from(delegate.read(getDatastoreType(), path));
+    }
+
+    @Override
+    public Object getIdentifier() {
+        return delegate.getIdentifier();
+    }
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadWriteTransaction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadWriteTransaction.java
new file mode 100644 (file)
index 0000000..1085e7a
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+
+/**
+ * Read-write transaction which is specific to a single logical datastore (configuration or operational). Designed
+ * for use with {@link ManagedNewTransactionRunner} (it doesn’t support explicit cancel or commit operations).
+ *
+ * @param <D> The logical datastore handled by the transaction.
+ * @see ReadWriteTransaction
+ */
+public interface TypedReadWriteTransaction<D extends Datastore>
+        extends TypedReadTransaction<D>, TypedWriteTransaction<D> {
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadWriteTransactionImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedReadWriteTransactionImpl.java
new file mode 100644 (file)
index 0000000..f78b4cd
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import com.google.common.util.concurrent.FluentFuture;
+import java.util.Optional;
+import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Implementation of {@link TypedReadWriteTransaction}.
+ *
+ * @param <D> The datastore which the transaction targets.
+ */
+class TypedReadWriteTransactionImpl<D extends Datastore>
+        extends TypedWriteTransactionImpl<D>
+        implements TypedReadWriteTransaction<D> {
+    // Temporarily package protected for TransactionAdapter
+    final ReadWriteTransaction delegate;
+
+    TypedReadWriteTransactionImpl(Class<D> datastoreType, ReadWriteTransaction realTx) {
+        super(datastoreType, realTx);
+        this.delegate = realTx;
+    }
+
+    @Override
+    public <T extends DataObject> FluentFuture<Optional<T>> read(InstanceIdentifier<T> path) {
+        return FluentFuture.from(delegate.read(getDatastoreType(), path));
+    }
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedTransaction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedTransaction.java
new file mode 100644 (file)
index 0000000..1e9c508
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+
+abstract class TypedTransaction<D extends Datastore> {
+    // Temporarily package protected for TransactionAdapter
+    final LogicalDatastoreType datastoreType;
+
+    TypedTransaction(Class<D> datastoreType) {
+        this.datastoreType = Datastore.toType(datastoreType);
+    }
+
+    LogicalDatastoreType getDatastoreType() {
+        return this.datastoreType;
+    }
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransaction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransaction.java
new file mode 100644 (file)
index 0000000..f8be2a4
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import org.opendaylight.mdsal.binding.api.Transaction;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Write transaction which is specific to a single logical datastore (configuration or operational). Designed for use
+ * with {@link ManagedNewTransactionRunner} (it doesn’t support explicit cancel or commit operations).
+ *
+ * @param <D> The logical datastore handled by the transaction.
+ * @see WriteTransaction
+ */
+public interface TypedWriteTransaction<D extends Datastore> extends Transaction {
+    /**
+     * Writes an object to the given path.
+     *
+     * @see WriteTransaction#put(LogicalDatastoreType, InstanceIdentifier, DataObject)
+     *
+     * @param path The path to write to.
+     * @param data The object to write.
+     * @param <T> The type of the provided object.
+     */
+    <T extends DataObject> void put(InstanceIdentifier<T> path, T data);
+
+    /**
+     * Writes an object to the given path, creating missing parents if requested.
+     *
+     * @see WriteTransaction#put(LogicalDatastoreType, InstanceIdentifier, DataObject, boolean)
+     *
+     * @param path The path to write to.
+     * @param data The object to write.
+     * @param createMissingParents {@link WriteTransaction#CREATE_MISSING_PARENTS} to create missing parents,
+     * {@link WriteTransaction#FAIL_ON_MISSING_PARENTS} to fail if parents are missing.
+     * @param <T> The type of the provided object.
+     */
+    <T extends DataObject> void put(InstanceIdentifier<T> path, T data, boolean createMissingParents);
+
+    /**
+     * Merges an object with the data already present at the given path.
+     *
+     * @see WriteTransaction#merge(LogicalDatastoreType, InstanceIdentifier, DataObject)
+     *
+     * @param path The path to write to.
+     * @param data The object to merge.
+     * @param <T> The type of the provided object.
+     */
+    <T extends DataObject> void merge(InstanceIdentifier<T> path, T data);
+
+    /**
+     * Merges an object with the data already present at the given path, creating missing parents if requested.
+     *
+     * @see WriteTransaction#merge(LogicalDatastoreType, InstanceIdentifier, DataObject, boolean)
+     *
+     * @param path The path to write to.
+     * @param data The object to merge.
+     * @param createMissingParents {@link WriteTransaction#CREATE_MISSING_PARENTS} to create missing parents,
+     * {@link WriteTransaction#FAIL_ON_MISSING_PARENTS} to fail if parents are missing.
+     * @param <T> The type of the provided object.
+     */
+    <T extends DataObject> void merge(InstanceIdentifier<T> path, T data, boolean createMissingParents);
+
+    /**
+     * Deletes the object present at the given path.
+     *
+     * @see WriteTransaction#delete(LogicalDatastoreType, InstanceIdentifier)
+     *
+     * @param path The path to delete.
+     */
+    void delete(InstanceIdentifier<?> path);
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransactionImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransactionImpl.java
new file mode 100644 (file)
index 0000000..85746cc
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import javax.annotation.Nonnull;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Implementation of {@link TypedWriteTransaction}.
+ *
+ * @param <D> The datastore which the transaction targets.
+ */
+class TypedWriteTransactionImpl<D extends Datastore> extends TypedTransaction<D>
+        implements TypedWriteTransaction<D> {
+    // Temporarily package protected for TransactionAdapter
+    final WriteTransaction delegate;
+
+    TypedWriteTransactionImpl(Class<D> datastoreType, WriteTransaction realTx) {
+        super(datastoreType);
+        this.delegate = realTx;
+    }
+
+    @Override
+    public <T extends DataObject> void put(InstanceIdentifier<T> path, T data) {
+        delegate.put(getDatastoreType(), path, data);
+    }
+
+    @Override
+    public <T extends DataObject> void put(InstanceIdentifier<T> path, T data, boolean createMissingParents) {
+        delegate.put(getDatastoreType(), path, data, createMissingParents);
+    }
+
+    @Override
+    public <T extends DataObject> void merge(InstanceIdentifier<T> path, T data) {
+        delegate.merge(getDatastoreType(), path, data);
+    }
+
+    @Override
+    public <T extends DataObject> void merge(InstanceIdentifier<T> path, T data, boolean createMissingParents) {
+        delegate.merge(getDatastoreType(), path, data, createMissingParents);
+    }
+
+    @Override
+    public void delete(InstanceIdentifier<?> path) {
+        delegate.delete(getDatastoreType(), path);
+    }
+
+    @Override
+    @Nonnull
+    public Object getIdentifier() {
+        return delegate.getIdentifier();
+    }
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingReadWriteTransaction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingReadWriteTransaction.java
new file mode 100644 (file)
index 0000000..9df6617
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+import org.opendaylight.mdsal.binding.spi.ForwardingReadWriteTransaction;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Read-write transaction which keeps track of writes.
+ */
+class WriteTrackingReadWriteTransaction extends ForwardingReadWriteTransaction implements WriteTrackingTransaction {
+    // This is volatile to ensure we get the latest value; transactions aren't supposed to be used in multiple threads,
+    // but the cost here is tiny (one read penalty at the end of a transaction) so we play it safe
+    private volatile boolean written;
+
+    WriteTrackingReadWriteTransaction(ReadWriteTransaction delegate) {
+        super(delegate);
+    }
+
+    @Override
+    public <T extends DataObject> void put(LogicalDatastoreType store, InstanceIdentifier<T> path, T data) {
+        super.put(store, path, data);
+        written = true;
+    }
+
+    @Override
+    public <T extends DataObject> void put(LogicalDatastoreType store, InstanceIdentifier<T> path, T data,
+            boolean createMissingParents) {
+        super.put(store, path, data, createMissingParents);
+        written = true;
+    }
+
+    @Override
+    public <T extends DataObject> void merge(LogicalDatastoreType store, InstanceIdentifier<T> path, T data) {
+        super.merge(store, path, data);
+        written = true;
+    }
+
+    @Override
+    public <T extends DataObject> void merge(LogicalDatastoreType store, InstanceIdentifier<T> path, T data,
+            boolean createMissingParents) {
+        super.merge(store, path, data, createMissingParents);
+        written = true;
+    }
+
+    @Override
+    public void delete(LogicalDatastoreType store, InstanceIdentifier<?> path) {
+        super.delete(store, path);
+        written = true;
+    }
+
+    @Override
+    public boolean isWritten() {
+        return written;
+    }
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTransaction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTransaction.java
new file mode 100644 (file)
index 0000000..19f6527
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2018 Red Hat, 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.util;
+
+// intentionally package local, not public
+interface WriteTrackingTransaction {
+
+    boolean isWritten();
+
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTypedReadWriteTransactionImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTypedReadWriteTransactionImpl.java
new file mode 100644 (file)
index 0000000..f882aab
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Read-write typed transaction which keeps track of writes.
+ */
+class WriteTrackingTypedReadWriteTransactionImpl<D extends Datastore> extends TypedReadWriteTransactionImpl<D>
+        implements WriteTrackingTransaction {
+
+    // This is volatile to ensure we get the latest value; transactions aren't supposed to be used in multiple threads,
+    // but the cost here is tiny (one read penalty at the end of a transaction) so we play it safe
+    private volatile boolean written;
+
+    WriteTrackingTypedReadWriteTransactionImpl(Class<D> datastoreType, ReadWriteTransaction realTx) {
+        super(datastoreType, realTx);
+    }
+
+    @Override
+    public <T extends DataObject> void put(InstanceIdentifier<T> path, T data) {
+        super.put(path, data);
+        written = true;
+    }
+
+    @Override
+    public <T extends DataObject> void put(InstanceIdentifier<T> path, T data, boolean createMissingParents) {
+        super.put(path, data, createMissingParents);
+        written = true;
+    }
+
+    @Override
+    public <T extends DataObject> void merge(InstanceIdentifier<T> path, T data) {
+        super.merge(path, data);
+        written = true;
+    }
+
+    @Override
+    public <T extends DataObject> void merge(InstanceIdentifier<T> path, T data, boolean createMissingParents) {
+        super.merge(path, data, createMissingParents);
+        written = true;
+    }
+
+    @Override
+    public void delete(InstanceIdentifier<?> path) {
+        super.delete(path);
+        written = true;
+    }
+
+    @Override
+    public boolean isWritten() {
+        return written;
+    }
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTypedWriteTransactionImpl.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingTypedWriteTransactionImpl.java
new file mode 100644 (file)
index 0000000..b6c3223
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Write typed transaction which keeps track of writes.
+ */
+class WriteTrackingTypedWriteTransactionImpl<D extends Datastore> extends TypedWriteTransactionImpl<D>
+        implements WriteTrackingTransaction {
+
+    // This is volatile to ensure we get the latest value; transactions aren't supposed to be used in multiple threads,
+    // but the cost here is tiny (one read penalty at the end of a transaction) so we play it safe
+    private volatile boolean written;
+
+    WriteTrackingTypedWriteTransactionImpl(Class<D> datastoreType, WriteTransaction realTx) {
+        super(datastoreType, realTx);
+    }
+
+    @Override
+    public <T extends DataObject> void put(InstanceIdentifier<T> path, T data) {
+        super.put(path, data);
+        written = true;
+    }
+
+    @Override
+    public <T extends DataObject> void put(InstanceIdentifier<T> path, T data, boolean createMissingParents) {
+        super.put(path, data, createMissingParents);
+        written = true;
+    }
+
+    @Override
+    public <T extends DataObject> void merge(InstanceIdentifier<T> path, T data) {
+        super.merge(path, data);
+        written = true;
+    }
+
+    @Override
+    public <T extends DataObject> void merge(InstanceIdentifier<T> path, T data, boolean createMissingParents) {
+        super.merge(path, data, createMissingParents);
+        written = true;
+    }
+
+    @Override
+    public void delete(InstanceIdentifier<?> path) {
+        super.delete(path);
+        written = true;
+    }
+
+    @Override
+    public boolean isWritten() {
+        return written;
+    }
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingWriteTransaction.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/WriteTrackingWriteTransaction.java
new file mode 100644 (file)
index 0000000..9d21fdb
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * 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.util;
+
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.binding.spi.ForwardingWriteTransaction;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Write transaction which keeps track of writes.
+ */
+class WriteTrackingWriteTransaction extends ForwardingWriteTransaction implements WriteTrackingTransaction {
+    // This is only ever read *after* changes to the transaction are complete
+    private boolean written;
+
+    WriteTrackingWriteTransaction(WriteTransaction delegate) {
+        super(delegate);
+    }
+
+    @Override
+    public <T extends DataObject> void put(LogicalDatastoreType store, InstanceIdentifier<T> path, T data) {
+        super.put(store, path, data);
+        written = true;
+    }
+
+    @Override
+    public <T extends DataObject> void put(LogicalDatastoreType store, InstanceIdentifier<T> path, T data,
+        boolean createMissingParents) {
+        super.put(store, path, data, createMissingParents);
+        written = true;
+    }
+
+    @Override
+    public <T extends DataObject> void merge(LogicalDatastoreType store, InstanceIdentifier<T> path, T data) {
+        super.merge(store, path, data);
+        written = true;
+    }
+
+    @Override
+    public <T extends DataObject> void merge(LogicalDatastoreType store, InstanceIdentifier<T> path, T data,
+        boolean createMissingParents) {
+        super.merge(store, path, data, createMissingParents);
+        written = true;
+    }
+
+    @Override
+    public void delete(LogicalDatastoreType store, InstanceIdentifier<?> path) {
+        super.delete(store, path);
+        written = true;
+    }
+
+    @Override
+    public boolean isWritten() {
+        return written;
+    }
+}
diff --git a/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/package-info.java b/binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/package-info.java
new file mode 100644 (file)
index 0000000..f10ce22
--- /dev/null
@@ -0,0 +1,9 @@
+/*
+ * Copyright © 2017, 2018 Red Hat, 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
+ */
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.opendaylight.mdsal.binding.util;
diff --git a/binding/mdsal-binding-util/src/test/java/org/opendaylight/mdsal/binding/util/DatastoreTest.java b/binding/mdsal-binding-util/src/test/java/org/opendaylight/mdsal/binding/util/DatastoreTest.java
new file mode 100644 (file)
index 0000000..8e1976e
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018 Red Hat, 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.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+
+public class DatastoreTest {
+
+    @Test
+    public void testDatastore() {
+        assertThat(Datastore.toType(Datastore.CONFIGURATION)).isEqualTo(LogicalDatastoreType.CONFIGURATION);
+        assertThat(Datastore.toType(Datastore.OPERATIONAL)).isEqualTo(LogicalDatastoreType.OPERATIONAL);
+        try {
+            Datastore.toType(null);
+            Assert.fail("Expected Datastore.toType(null) to throw NullPointerException");
+        } catch (NullPointerException e) {
+            // OK, this is what we're expecting
+        }
+
+        assertThat(Datastore.toClass(LogicalDatastoreType.CONFIGURATION)).isEqualTo(Datastore.CONFIGURATION);
+        assertThat(Datastore.toClass(LogicalDatastoreType.OPERATIONAL)).isEqualTo(Datastore.OPERATIONAL);
+        try {
+            Datastore.toClass(null);
+            Assert.fail("Expected Datastore.toClass(null) to throw NullPointerException");
+        } catch (NullPointerException e) {
+            // OK, this is what we're expecting
+        }
+    }
+
+}
index 691fca4a492abafafd5405acd53cefedb8f07fdf..123aeb21a427e8309c95a0a625c1f00a7c286db6 100644 (file)
@@ -45,6 +45,7 @@
         <module>mdsal-binding-util</module>
         <module>mdsal-binding-test-utils</module>
         <module>mdsal-binding-dom-adapter</module>
+        <module>mdsal-binding-util-tests</module>
     </modules>
 
     <build>