--- /dev/null
+<?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
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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));
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
</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>
--- /dev/null
+/*
+ * 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;
+
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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() {}
+}
--- /dev/null
+/*
+ * 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;
+
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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 {
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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 & 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);
+ }
+}
--- /dev/null
+/*
+ * 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 & Stephen Kitt, with input from Tom Pantelis re. catchingAsync & 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()));
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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> {
+}
--- /dev/null
+/*
+ * 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));
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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();
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
--- /dev/null
+/*
+ * 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
+ }
+ }
+
+}
<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>