From: PeterSuna Date: Mon, 9 Sep 2019 11:45:43 +0000 (+0200) Subject: Reorganize transactionChainHandler usage. X-Git-Tag: release/magnesium~40 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=0a8ab4d8188442b3d6ec7ccb14f413b06a448aa1;p=netconf.git Reorganize transactionChainHandler usage. Update transactionChainHandler to conform dependecny injection pattern. Update DOMtransactionChain to be used multiple time. JIRA: NETCONF-352 Change-Id: I26da73992ed2772f4f84e2a09788600b808283b3 Signed-off-by: PeterSuna --- diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/handlers/SchemaContextHandler.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/handlers/SchemaContextHandler.java index 5483dab8d4..26cc056ed8 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/handlers/SchemaContextHandler.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/handlers/SchemaContextHandler.java @@ -22,6 +22,7 @@ import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.common.api.TransactionCommitFailedException; import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.nb.rfc8040.Rfc8040.IetfYangLibrary; import org.opendaylight.restconf.nb.rfc8040.Rfc8040.MonitoringModule; @@ -117,7 +118,8 @@ public class SchemaContextHandler implements SchemaContextListenerHandler, AutoC private void putData( final NormalizedNode>> normNode) { - final DOMDataTreeWriteTransaction wTx = this.transactionChainHandler.get().newWriteOnlyTransaction(); + final DOMTransactionChain transactionChain = this.transactionChainHandler.get(); + final DOMDataTreeWriteTransaction wTx = transactionChain.newWriteOnlyTransaction(); wTx.put(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.create(NodeIdentifier.create(normNode.getNodeType())), normNode); try { @@ -136,10 +138,11 @@ public class SchemaContextHandler implements SchemaContextListenerHandler, AutoC * This is workaround for bug https://bugs.opendaylight.org/show_bug.cgi?id=7728 */ LOG.warn("Ignoring that another cluster node is already putting the same data to DS.", e); - this.transactionChainHandler.reset(); } else { throw new RestconfDocumentedException("Problem occurred while putting data to DS.", failure); } + } finally { + transactionChain.close(); } } } diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/handlers/TransactionChainHandler.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/handlers/TransactionChainHandler.java index 0e2df38e5f..28830dd341 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/handlers/TransactionChainHandler.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/handlers/TransactionChainHandler.java @@ -8,6 +8,8 @@ package org.opendaylight.restconf.nb.rfc8040.handlers; import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Singleton; @@ -31,19 +33,19 @@ public class TransactionChainHandler implements Handler, Au public void onTransactionChainFailed(final DOMTransactionChain chain, final DOMDataTreeTransaction transaction, final Throwable cause) { LOG.warn("TransactionChain({}) {} FAILED!", chain, transaction.getIdentifier(), cause); - reset(); + transactionChainList.remove(chain); throw new RestconfDocumentedException("TransactionChain(" + chain + ") not committed correctly", cause); } @Override public void onTransactionChainSuccessful(final DOMTransactionChain chain) { LOG.trace("TransactionChain({}) SUCCESSFUL", chain); + transactionChainList.remove(chain); } }; private final DOMDataBroker dataBroker; - - private volatile DOMTransactionChain transactionChain; + private final Queue transactionChainList; /** * Prepare transaction chain service for Restconf services. @@ -51,23 +53,37 @@ public class TransactionChainHandler implements Handler, Au @Inject public TransactionChainHandler(final DOMDataBroker dataBroker) { this.dataBroker = Objects.requireNonNull(dataBroker); - transactionChain = Objects.requireNonNull(dataBroker.createTransactionChain(transactionChainListener)); + this.transactionChainList = new ConcurrentLinkedQueue<>(); } + /** + * Create and return new instance of object {@link DOMTransactionChain}. + * After use, is important to close transactionChain by method {@link DOMTransactionChain#close()}. + * @return new instance of object {@link DOMTransactionChain} + */ @Override public DOMTransactionChain get() { - return this.transactionChain; - } - - public synchronized void reset() { - LOG.trace("Resetting TransactionChain({})", transactionChain); - transactionChain.close(); - transactionChain = dataBroker.createTransactionChain(transactionChainListener); + final DOMTransactionChain transactionChain = dataBroker.createTransactionChain(transactionChainListener); + this.transactionChainList.add(transactionChain); + LOG.trace("Started TransactionChain({})", transactionChain); + return transactionChain; } @Override @PreDestroy public synchronized void close() { - transactionChain.close(); + for (DOMTransactionChain transactionChain: this.transactionChainList) { + transactionChain.close(); + LOG.trace("Closed TransactionChain({})", transactionChain); + } + } + + /** + * Verify if {@link DOMTransactionChain} exist in {@link TransactionChainHandler} queue. + * @param transactionChain object to check. + * @return true if object still exist in {@link TransactionChainHandler}. + */ + boolean verifyIfExistTransactionChain(DOMTransactionChain transactionChain) { + return this.transactionChainList.contains(transactionChain); } } diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/DeleteDataTransactionUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/DeleteDataTransactionUtil.java index 54e3964bd9..201166185b 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/DeleteDataTransactionUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/DeleteDataTransactionUtil.java @@ -13,7 +13,7 @@ import javax.ws.rs.core.Response.Status; import org.opendaylight.mdsal.common.api.CommitInfo; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; -import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; +import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; @@ -35,28 +35,29 @@ public final class DeleteDataTransactionUtil { * @return {@link Response} */ public static Response deleteData(final TransactionVarsWrapper transactionNode) { - final FluentFuture future = submitData(transactionNode.getTransactionChainHandler(), + final DOMTransactionChain transactionChain = transactionNode.getTransactionChainHandler().get(); + final FluentFuture future = submitData(transactionChain, transactionNode.getInstanceIdentifier().getInstanceIdentifier()); final ResponseFactory response = new ResponseFactory(Status.NO_CONTENT); - FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.DeleteData.DELETE_TX_TYPE, response); + //This method will close transactionChain + FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.DeleteData.DELETE_TX_TYPE, response, + transactionChain); return response.build(); } /** * Delete data via transaction. Return error if data to delete does not exist. * - * @param transactionChainHandler - * transaction chain handler - * @param readWriteTx - * read and write transaction + * @param transactionChain + * transaction chain * @param path * path of data to delete * @return {@link FluentFuture} */ private static FluentFuture submitData( - final TransactionChainHandler transactionChainHandler, final YangInstanceIdentifier path) { - final DOMDataTreeReadWriteTransaction readWriteTx = transactionChainHandler.get().newReadWriteTransaction(); - TransactionUtil.checkItemExists(transactionChainHandler, readWriteTx, LogicalDatastoreType.CONFIGURATION, path, + final DOMTransactionChain transactionChain, final YangInstanceIdentifier path) { + final DOMDataTreeReadWriteTransaction readWriteTx = transactionChain.newReadWriteTransaction(); + TransactionUtil.checkItemExists(transactionChain, readWriteTx, LogicalDatastoreType.CONFIGURATION, path, RestconfDataServiceConstant.DeleteData.DELETE_TX_TYPE); readWriteTx.delete(LogicalDatastoreType.CONFIGURATION, path); return readWriteTx.commit(); diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/FutureCallbackTx.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/FutureCallbackTx.java index 58178b6c41..bd6d94132e 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/FutureCallbackTx.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/FutureCallbackTx.java @@ -12,9 +12,11 @@ import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ListenableFuture; import java.util.List; import java.util.concurrent.ExecutionException; +import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.mdsal.common.api.TransactionCommitFailedException; import org.opendaylight.mdsal.dom.api.DOMActionException; import org.opendaylight.mdsal.dom.api.DOMRpcException; +import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult; import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult; import org.opendaylight.netconf.api.NetconfDocumentedException; @@ -50,7 +52,28 @@ final class FutureCallbackTx { */ @SuppressWarnings("checkstyle:IllegalCatch") static void addCallback(final ListenableFuture listenableFuture, final String txType, - final FutureDataFactory dataFactory) throws RestconfDocumentedException { + final FutureDataFactory dataFactory) throws RestconfDocumentedException { + addCallback(listenableFuture,txType,dataFactory,null); + } + + /** + * Add callback to the future object and close transaction chain. + * + * @param listenableFuture + * future object + * @param txType + * type of operation (READ, POST, PUT, DELETE) + * @param dataFactory + * factory setting result + * @param transactionChain + * transaction chain + * @throws RestconfDocumentedException + * if the Future throws an exception + */ + @SuppressWarnings("checkstyle:IllegalCatch") + static void addCallback(final ListenableFuture listenableFuture, final String txType, + final FutureDataFactory dataFactory, @Nullable final DOMTransactionChain transactionChain) + throws RestconfDocumentedException { try { final T result = listenableFuture.get(); @@ -92,6 +115,10 @@ final class FutureCallbackTx { } else { throw new RestconfDocumentedException("Transaction failed", e); } + } finally { + if (transactionChain != null) { + transactionChain.close(); + } } } } diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PatchDataTransactionUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PatchDataTransactionUtil.java index 6a47e7051b..d92f143a14 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PatchDataTransactionUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PatchDataTransactionUtil.java @@ -18,6 +18,7 @@ import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadOperations; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; +import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError; import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag; @@ -46,7 +47,8 @@ public final class PatchDataTransactionUtil { } /** - * Process edit operations of one {@link PatchContext}. + * Process edit operations of one {@link PatchContext}. Close {@link DOMTransactionChain} inside of object + * {@link TransactionVarsWrapper} provided as a parameter. * @param context Patch context to be processed * @param transactionNode Wrapper for transaction * @param schemaContextRef Soft reference for global schema context @@ -56,7 +58,8 @@ public final class PatchDataTransactionUtil { final SchemaContextRef schemaContextRef) { final List editCollection = new ArrayList<>(); boolean noError = true; - final DOMDataTreeReadWriteTransaction tx = transactionNode.getTransactionChain().newReadWriteTransaction(); + final DOMTransactionChain transactionChain = transactionNode.getTransactionChain(); + final DOMDataTreeReadWriteTransaction tx = transactionChain.newReadWriteTransaction(); for (final PatchEntity patchEntity : context.getData()) { if (noError) { @@ -134,7 +137,8 @@ public final class PatchDataTransactionUtil { final FluentFuture future = tx.commit(); try { - FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response); + //This method will close transactionChain + FutureCallbackTx.addCallback(future, PatchData.PATCH_TX_TYPE, response, transactionChain); } catch (final RestconfDocumentedException e) { // if errors occurred during transaction commit then patch failed and global errors are reported return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false, @@ -144,7 +148,7 @@ public final class PatchDataTransactionUtil { return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true, null); } else { tx.cancel(); - transactionNode.getTransactionChainHandler().reset(); + transactionChain.close(); return new PatchStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false, null); } @@ -155,7 +159,7 @@ public final class PatchDataTransactionUtil { * @param dataStore Datastore to write data to * @param path Path for data to be created * @param payload Data to be created - * @param rWTransaction Transaction + * @param rwTransaction Transaction * @param schemaContextRef Soft reference for global schema context */ private static void createDataWithinTransaction(final LogicalDatastoreType dataStore, diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PostDataTransactionUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PostDataTransactionUtil.java index 937a6361a0..f1f395ba40 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PostDataTransactionUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PostDataTransactionUtil.java @@ -22,7 +22,6 @@ import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.context.NormalizedNodeContext; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError; -import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef; import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper; import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier; @@ -48,7 +47,8 @@ public final class PostDataTransactionUtil { } /** - * Check mount point and prepare variables for post data. + * Check mount point and prepare variables for post data. Close {@link DOMTransactionChain} inside of object + * {@link TransactionVarsWrapper} provided as a parameter. * * @param uriInfo * @@ -72,7 +72,9 @@ public final class PostDataTransactionUtil { transactionNode, schemaContextRef.get(), insert, point); final URI location = PostDataTransactionUtil.resolveLocation(uriInfo, transactionNode, schemaContextRef); final ResponseFactory dataFactory = new ResponseFactory(Status.CREATED).location(location); - FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.PostData.POST_TX_TYPE, dataFactory); + //This method will close transactionChain + FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.PostData.POST_TX_TYPE, dataFactory, + transactionNode.getTransactionChain()); return dataFactory.build(); } @@ -96,10 +98,10 @@ public final class PostDataTransactionUtil { private static FluentFuture submitData(final YangInstanceIdentifier path, final NormalizedNode data, final TransactionVarsWrapper transactionNode, final SchemaContext schemaContext, final String insert, final String point) { - final DOMTransactionChain domTransactionChain = transactionNode.getTransactionChain(); - final DOMDataTreeReadWriteTransaction newReadWriteTransaction = domTransactionChain.newReadWriteTransaction(); + final DOMTransactionChain transactionChain = transactionNode.getTransactionChain(); + final DOMDataTreeReadWriteTransaction newReadWriteTransaction = transactionChain.newReadWriteTransaction(); if (insert == null) { - makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(), newReadWriteTransaction); + makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); return newReadWriteTransaction.commit(); } else { final DataSchemaNode schemaNode = PutDataTransactionUtil.checkListAndOrderedType(schemaContext, path); @@ -110,15 +112,15 @@ public final class PostDataTransactionUtil { schemaContext, transactionNode.getTransactionChainHandler(), schemaNode); final OrderedMapNode readList = (OrderedMapNode) readData; if (readList == null || readList.getValue().isEmpty()) { - makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(), + makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); return newReadWriteTransaction.commit(); } else { newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent()); simplePost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data, - schemaContext, transactionNode.getTransactionChainHandler()); - makePost(path, readData, schemaContext, transactionNode.getTransactionChainHandler(), + schemaContext, transactionChain); + makePost(path, readData, schemaContext, transactionChain, newReadWriteTransaction); return newReadWriteTransaction.commit(); } @@ -128,21 +130,21 @@ public final class PostDataTransactionUtil { final OrderedLeafSetNode readLeafList = (OrderedLeafSetNode) readData; if (readLeafList == null || readLeafList.getValue().isEmpty()) { - makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(), + makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); return newReadWriteTransaction.commit(); } else { newReadWriteTransaction.delete(LogicalDatastoreType.CONFIGURATION, path.getParent().getParent()); simplePost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data, - schemaContext, transactionNode.getTransactionChainHandler()); - makePost(path, readData, schemaContext, transactionNode.getTransactionChainHandler(), + schemaContext, transactionChain); + makePost(path, readData, schemaContext, transactionChain, newReadWriteTransaction); return newReadWriteTransaction.commit(); } } case "last": - makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(), + makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); return newReadWriteTransaction.commit(); case "before": @@ -151,13 +153,13 @@ public final class PostDataTransactionUtil { schemaContext, transactionNode.getTransactionChainHandler(), schemaNode); final OrderedMapNode readList = (OrderedMapNode) readData; if (readList == null || readList.getValue().isEmpty()) { - makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(), + makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); return newReadWriteTransaction.commit(); } else { insertWithPointListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, point, readList, true, - transactionNode.getTransactionChainHandler()); + transactionChain); return newReadWriteTransaction.commit(); } } else { @@ -166,13 +168,13 @@ public final class PostDataTransactionUtil { final OrderedLeafSetNode readLeafList = (OrderedLeafSetNode) readData; if (readLeafList == null || readLeafList.getValue().isEmpty()) { - makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(), + makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); return newReadWriteTransaction.commit(); } else { insertWithPointLeafListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, point, readLeafList, true, - transactionNode.getTransactionChainHandler()); + transactionChain); return newReadWriteTransaction.commit(); } } @@ -182,13 +184,13 @@ public final class PostDataTransactionUtil { schemaContext, transactionNode.getTransactionChainHandler(), schemaNode); final OrderedMapNode readList = (OrderedMapNode) readData; if (readList == null || readList.getValue().isEmpty()) { - makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(), + makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); return newReadWriteTransaction.commit(); } else { insertWithPointListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, point, readList, false, - transactionNode.getTransactionChainHandler()); + transactionChain); return newReadWriteTransaction.commit(); } } else { @@ -197,13 +199,13 @@ public final class PostDataTransactionUtil { final OrderedLeafSetNode readLeafList = (OrderedLeafSetNode) readData; if (readLeafList == null || readLeafList.getValue().isEmpty()) { - makePost(path, data, schemaContext, transactionNode.getTransactionChainHandler(), + makePost(path, data, schemaContext, transactionChain, newReadWriteTransaction); return newReadWriteTransaction.commit(); } else { insertWithPointLeafListPost(newReadWriteTransaction, LogicalDatastoreType.CONFIGURATION, path, data, schemaContext, point, readLeafList, true, - transactionNode.getTransactionChainHandler()); + transactionChain); return newReadWriteTransaction.commit(); } } @@ -219,7 +221,7 @@ public final class PostDataTransactionUtil { private static void insertWithPointLeafListPost(final DOMDataTreeReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload, final SchemaContext schemaContext, final String point, final OrderedLeafSetNode readLeafList, - final boolean before, final TransactionChainHandler transactionChainHandler) { + final boolean before, final DOMTransactionChain transactionChain) { rwTransaction.delete(datastore, path.getParent().getParent()); final InstanceIdentifierContext instanceIdentifier = ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty()); @@ -239,12 +241,12 @@ public final class PostDataTransactionUtil { rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); for (final LeafSetEntryNode nodeChild : readLeafList.getValue()) { if (lastInsertedPosition == lastItemPosition) { - TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, path, + TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, path, RestconfDataServiceConstant.PostData.POST_TX_TYPE); rwTransaction.put(datastore, path, payload); } final YangInstanceIdentifier childPath = path.getParent().getParent().node(nodeChild.getIdentifier()); - TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, childPath, + TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, childPath, RestconfDataServiceConstant.PostData.POST_TX_TYPE); rwTransaction.put(datastore, childPath, nodeChild); lastInsertedPosition++; @@ -254,7 +256,7 @@ public final class PostDataTransactionUtil { private static void insertWithPointListPost(final DOMDataTreeReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload, final SchemaContext schemaContext, final String point, final MapNode readList, final boolean before, - final TransactionChainHandler transactionChainHandler) { + final DOMTransactionChain transactionChain) { rwTransaction.delete(datastore, path.getParent().getParent()); final InstanceIdentifierContext instanceIdentifier = ParserIdentifier.toInstanceIdentifier(point, schemaContext, Optional.empty()); @@ -274,12 +276,12 @@ public final class PostDataTransactionUtil { rwTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree); for (final MapEntryNode mapEntryNode : readList.getValue()) { if (lastInsertedPosition == lastItemPosition) { - TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, path, + TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, path, RestconfDataServiceConstant.PostData.POST_TX_TYPE); rwTransaction.put(datastore, path, payload); } final YangInstanceIdentifier childPath = path.getParent().getParent().node(mapEntryNode.getIdentifier()); - TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, childPath, + TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, childPath, RestconfDataServiceConstant.PostData.POST_TX_TYPE); rwTransaction.put(datastore, childPath, mapEntryNode); lastInsertedPosition++; @@ -287,14 +289,14 @@ public final class PostDataTransactionUtil { } private static void makePost(final YangInstanceIdentifier path, final NormalizedNode data, - final SchemaContext schemaContext, final TransactionChainHandler transactionChainHandler, + final SchemaContext schemaContext, final DOMTransactionChain transactionChain, final DOMDataTreeReadWriteTransaction transaction) { if (data instanceof MapNode) { boolean merge = false; for (final MapEntryNode child : ((MapNode) data).getValue()) { final YangInstanceIdentifier childPath = path.node(child.getIdentifier()); TransactionUtil.checkItemDoesNotExists( - transactionChainHandler, transaction, LogicalDatastoreType.CONFIGURATION, childPath, + transactionChain, transaction, LogicalDatastoreType.CONFIGURATION, childPath, RestconfDataServiceConstant.PostData.POST_TX_TYPE); if (!merge) { merge = true; @@ -307,7 +309,7 @@ public final class PostDataTransactionUtil { } } else { TransactionUtil.checkItemDoesNotExists( - transactionChainHandler, transaction, LogicalDatastoreType.CONFIGURATION, path, + transactionChain, transaction, LogicalDatastoreType.CONFIGURATION, path, RestconfDataServiceConstant.PostData.POST_TX_TYPE); TransactionUtil.ensureParentsByMerge(path, schemaContext, transaction); @@ -343,8 +345,8 @@ public final class PostDataTransactionUtil { private static void simplePost(final DOMDataTreeReadWriteTransaction rwTransaction, final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final NormalizedNode payload, - final SchemaContext schemaContext, final TransactionChainHandler transactionChainHandler) { - TransactionUtil.checkItemDoesNotExists(transactionChainHandler, rwTransaction, datastore, path, + final SchemaContext schemaContext, final DOMTransactionChain transactionChain) { + TransactionUtil.checkItemDoesNotExists(transactionChain, rwTransaction, datastore, path, RestconfDataServiceConstant.PostData.POST_TX_TYPE); TransactionUtil.ensureParentsByMerge(path, schemaContext, rwTransaction); rwTransaction.put(datastore, path, payload); diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PutDataTransactionUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PutDataTransactionUtil.java index 97f0490947..72ee864ad3 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PutDataTransactionUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/PutDataTransactionUtil.java @@ -18,6 +18,7 @@ import org.opendaylight.mdsal.common.api.CommitInfo; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; +import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.context.NormalizedNodeContext; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; @@ -140,7 +141,8 @@ public final class PutDataTransactionUtil { } /** - * Check mount point and prepare variables for put data to DS. + * Check mount point and prepare variables for put data to DS. Close {@link DOMTransactionChain} inside of object + * {@link TransactionVarsWrapper} provided as a parameter. * * @param payload * data to put @@ -171,7 +173,9 @@ public final class PutDataTransactionUtil { new ResponseFactory(existsResponse.result ? Status.NO_CONTENT : Status.CREATED); final FluentFuture submitData = submitData(path, schemaContext, transactionNode.getTransactionChainHandler(), readWriteTransaction, payload.getData(), insert, point); - FutureCallbackTx.addCallback(submitData, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, responseFactory); + //This method will close transactionChain + FutureCallbackTx.addCallback(submitData, RestconfDataServiceConstant.PutData.PUT_TX_TYPE, responseFactory, + transactionNode.getTransactionChain()); return responseFactory.build(); } diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java index 509562bb28..9f2c74dcb8 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java @@ -26,6 +26,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; +import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.context.WriterParameters; import org.opendaylight.restconf.common.context.WriterParameters.WriterParametersBuilder; @@ -203,7 +204,8 @@ public final class ReadDataTransactionUtil { } /** - * Read specific type of data from data store via transaction. + * Read specific type of data from data store via transaction. Close {@link DOMTransactionChain} inside of object + * {@link TransactionVarsWrapper} provided as a parameter. * * @param valueOfContent * type of data to read (config, state, all) @@ -234,6 +236,7 @@ public final class ReadDataTransactionUtil { return readAllData(transactionNode, withDefa, ctx); default: + transactionNode.getTransactionChain().close(); throw new RestconfDocumentedException( new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE, "Invalid content parameter: " + valueOfContent, null, @@ -425,7 +428,9 @@ public final class ReadDataTransactionUtil { /** * If is set specific {@link LogicalDatastoreType} in * {@link TransactionVarsWrapper}, then read this type of data from DS. If - * don't, we have to read all data from DS (state + config) + * don't, we have to read all data from DS (state + config). + * This method will close {@link org.opendaylight.mdsal.dom.api.DOMTransactionChain} inside of + * {@link TransactionVarsWrapper}. * * @param transactionNode * {@link TransactionVarsWrapper} - wrapper for variables @@ -433,19 +438,43 @@ public final class ReadDataTransactionUtil { */ private static @Nullable NormalizedNode readDataViaTransaction( final @NonNull TransactionVarsWrapper transactionNode) { + return readDataViaTransaction(transactionNode, true); + } + + + /** + * If is set specific {@link LogicalDatastoreType} in + * {@link TransactionVarsWrapper}, then read this type of data from DS. If + * don't, we have to read all data from DS (state + config) + * + * @param transactionNode + * {@link TransactionVarsWrapper} - wrapper for variables + * @param closeTransactionChain + * If is set to true, after transaction it will close transactionChain in {@link TransactionVarsWrapper} + * @return {@link NormalizedNode} + */ + private static @Nullable NormalizedNode readDataViaTransaction( + final @NonNull TransactionVarsWrapper transactionNode, final boolean closeTransactionChain) { final NormalizedNodeFactory dataFactory = new NormalizedNodeFactory(); try (DOMDataTreeReadTransaction tx = transactionNode.getTransactionChain().newReadOnlyTransaction()) { final FluentFuture>> listenableFuture = tx.read( transactionNode.getLogicalDatastoreType(), transactionNode.getInstanceIdentifier().getInstanceIdentifier()); - FutureCallbackTx.addCallback(listenableFuture, RestconfDataServiceConstant.ReadData.READ_TYPE_TX, - dataFactory); + if (closeTransactionChain) { + //Method close transactionChain inside of TransactionVarsWrapper, if is provide as a parameter. + FutureCallbackTx.addCallback(listenableFuture, RestconfDataServiceConstant.ReadData.READ_TYPE_TX, + dataFactory, transactionNode.getTransactionChain()); + } else { + FutureCallbackTx.addCallback(listenableFuture, RestconfDataServiceConstant.ReadData.READ_TYPE_TX, + dataFactory); + } } return dataFactory.build(); } /** - * Read config and state data, then map them. + * Read config and state data, then map them. Close {@link DOMTransactionChain} inside of object + * {@link TransactionVarsWrapper} provided as a parameter. * * @param transactionNode * {@link TransactionVarsWrapper} - wrapper for variables @@ -459,11 +488,12 @@ public final class ReadDataTransactionUtil { final String withDefa, final SchemaContext ctx) { // PREPARE STATE DATA NODE transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL); - final NormalizedNode stateDataNode = readDataViaTransaction(transactionNode); + final NormalizedNode stateDataNode = readDataViaTransaction(transactionNode, false); // PREPARE CONFIG DATA NODE transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION); final NormalizedNode configDataNode; + //Here will be closed transactionChain if (withDefa == null) { configDataNode = readDataViaTransaction(transactionNode); } else { diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/SubscribeToStreamUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/SubscribeToStreamUtil.java index 3a31dd976f..acfc6437c0 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/SubscribeToStreamUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/SubscribeToStreamUtil.java @@ -32,6 +32,7 @@ import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadOperations; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMNotificationListener; +import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag; @@ -110,10 +111,8 @@ public final class SubscribeToStreamUtil { ErrorTag.UNKNOWN_ELEMENT); } - final DOMDataTreeReadWriteTransaction writeTransaction = handlersHolder - .getTransactionChainHandler() - .get() - .newReadWriteTransaction(); + final DOMTransactionChain transactionChain = handlersHolder.getTransactionChainHandler().get(); + final DOMDataTreeReadWriteTransaction writeTransaction = transactionChain.newReadWriteTransaction(); final SchemaContext schemaContext = handlersHolder.getSchemaHandler().get(); final boolean exist = checkExist(schemaContext, writeTransaction); final URI uri = prepareUriByStreamName(uriInfo, streamName); @@ -136,6 +135,7 @@ public final class SubscribeToStreamUtil { notificationListenerAdapter.get().getSchemaPath().getLastComponent().getLocalName(), writeTransaction, exist, mapToStreams); submitData(writeTransaction); + transactionChain.close(); return uri; } @@ -208,8 +208,8 @@ public final class SubscribeToStreamUtil { registration(datastoreType, listener.get(), handlersHolder.getDomDataBrokerHandler().get()); final URI uri = prepareUriByStreamName(uriInfo, streamName); - final DOMDataTreeReadWriteTransaction writeTransaction - = handlersHolder.getTransactionChainHandler().get().newReadWriteTransaction(); + final DOMTransactionChain transactionChain = handlersHolder.getTransactionChainHandler().get(); + final DOMDataTreeReadWriteTransaction writeTransaction = transactionChain.newReadWriteTransaction(); final SchemaContext schemaContext = handlersHolder.getSchemaHandler().get(); final boolean exist = checkExist(schemaContext, writeTransaction); @@ -220,6 +220,7 @@ public final class SubscribeToStreamUtil { writeDataToDS(schemaContext, listener.get().getPath().getLastPathArgument().getNodeType().getLocalName(), writeTransaction, exist, mapToStreams); submitData(writeTransaction); + transactionChain.close(); return uri; } diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/TransactionUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/TransactionUtil.java index 3cfddd6935..0e4261c46a 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/TransactionUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/TransactionUtil.java @@ -15,10 +15,10 @@ import java.util.List; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; +import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag; import org.opendaylight.restconf.common.errors.RestconfError.ErrorType; -import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; @@ -81,13 +81,13 @@ public final class TransactionUtil { /** * Check if items already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if * data does NOT already exists. - * @param transactionChainHandler Transaction chain handler + * @param transactionChain Transaction chain * @param rwTransaction Transaction * @param store Datastore * @param path Path to be checked * @param operationType Type of operation (READ, POST, PUT, DELETE...) */ - public static void checkItemExists(final TransactionChainHandler transactionChainHandler, + public static void checkItemExists(final DOMTransactionChain transactionChain, final DOMDataTreeReadWriteTransaction rwTransaction, final LogicalDatastoreType store, final YangInstanceIdentifier path, final String operationType) { @@ -97,10 +97,9 @@ public final class TransactionUtil { FutureCallbackTx.addCallback(future, operationType, response); if (!response.result) { - // close transaction and reset transaction chain + // close transaction rwTransaction.cancel(); - transactionChainHandler.reset(); - + transactionChain.close(); // throw error LOG.trace("Operation via Restconf was not executed because data at {} does not exist", path); throw new RestconfDocumentedException( @@ -111,13 +110,13 @@ public final class TransactionUtil { /** * Check if items do NOT already exists at specified {@code path}. Throws {@link RestconfDocumentedException} if * data already exists. - * @param transactionChainHandler Transaction chain handler + * @param transactionChain Transaction chain * @param rwTransaction Transaction * @param store Datastore * @param path Path to be checked * @param operationType Type of operation (READ, POST, PUT, DELETE...) */ - public static void checkItemDoesNotExists(final TransactionChainHandler transactionChainHandler, + public static void checkItemDoesNotExists(final DOMTransactionChain transactionChain, final DOMDataTreeReadWriteTransaction rwTransaction, final LogicalDatastoreType store, final YangInstanceIdentifier path, final String operationType) { @@ -127,10 +126,9 @@ public final class TransactionUtil { FutureCallbackTx.addCallback(future, operationType, response); if (response.result) { - // close transaction and reset transaction chain + // close transaction rwTransaction.cancel(); - transactionChainHandler.reset(); - + transactionChain.close(); // throw error LOG.trace("Operation via Restconf was not executed because data at {} already exists", path); throw new RestconfDocumentedException( diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/AbstractNotificationsData.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/AbstractNotificationsData.java index da575164f9..d2c2322c4e 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/AbstractNotificationsData.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/streams/listeners/AbstractNotificationsData.java @@ -26,6 +26,7 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; +import org.opendaylight.mdsal.dom.api.DOMTransactionChain; import org.opendaylight.restconf.nb.rfc8040.Rfc8040.MonitoringModule; import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler; import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler; @@ -77,10 +78,12 @@ abstract class AbstractNotificationsData { * Delete data in DS. */ protected void deleteDataInDS() throws Exception { - final DOMDataTreeWriteTransaction wTx = this.transactionChainHandler.get().newWriteOnlyTransaction(); + final DOMTransactionChain transactionChain = this.transactionChainHandler.get(); + final DOMDataTreeWriteTransaction wTx = transactionChain.newWriteOnlyTransaction(); wTx.delete(LogicalDatastoreType.OPERATIONAL, IdentifierCodec .deserialize(MonitoringModule.PATH_TO_STREAM_WITHOUT_KEY + this.localName, this.schemaHandler.get())); wTx.commit().get(); + transactionChain.close(); } /** diff --git a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/handlers/TransactionChainHandlerTest.java b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/handlers/TransactionChainHandlerTest.java new file mode 100644 index 0000000000..7c7d727c20 --- /dev/null +++ b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/handlers/TransactionChainHandlerTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.restconf.nb.rfc8040.handlers; + +import com.google.common.collect.ClassToInstanceMap; +import org.eclipse.jdt.annotation.NonNull; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; +import org.opendaylight.mdsal.dom.api.DOMDataBroker; +import org.opendaylight.mdsal.dom.api.DOMDataBrokerExtension; +import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction; +import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; +import org.opendaylight.mdsal.dom.api.DOMDataTreeTransaction; +import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; +import org.opendaylight.mdsal.dom.api.DOMTransactionChain; +import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; + +public class TransactionChainHandlerTest { + + private TransactionChainHandler transactionChainHandler; + private static final String EXCEPTION_MESSAGE = "(TEST) Unsupported Method"; + + @After + public void shutdown() { + this.transactionChainHandler.close(); + } + + @Test + public void transactionChainTest() { + this.transactionChainHandler = new TransactionChainHandler(new DataBrokerLocal()); + final DOMTransactionChain chain1 = this.transactionChainHandler.get(); + final DOMTransactionChain chain2 = this.transactionChainHandler.get(); + Assert.assertNotNull(chain1); + Assert.assertNotNull(chain2); + Assert.assertNotEquals(chain1, chain2); + chain1.close(); + Assert.assertFalse(this.transactionChainHandler.verifyIfExistTransactionChain(chain1)); + + try { + chain2.newReadOnlyTransaction(); + } catch (RestconfDocumentedException e) { + Assert.assertEquals(e.getCause().getLocalizedMessage(), EXCEPTION_MESSAGE); + } + Assert.assertFalse(this.transactionChainHandler.verifyIfExistTransactionChain(chain2)); + } + + private final class TxChainLocal implements DOMTransactionChain { + DOMTransactionChainListener listener; + + TxChainLocal(DOMTransactionChainListener listener) { + this.listener = listener; + } + + @Override + public DOMDataTreeReadTransaction newReadOnlyTransaction() { + final DOMDataTreeTransaction domDataTreeTransaction = Mockito.mock(DOMDataTreeTransaction.class); + listener.onTransactionChainFailed(this, + domDataTreeTransaction, new Throwable(EXCEPTION_MESSAGE)); + return null; + } + + @Override + public DOMDataTreeWriteTransaction newWriteOnlyTransaction() { + return null; + } + + @Override + public DOMDataTreeReadWriteTransaction newReadWriteTransaction() { + return null; + } + + @Override + public void close() { + listener.onTransactionChainSuccessful(this); + } + } + + private final class DataBrokerLocal implements DOMDataBroker { + + @Override + public @NonNull DOMTransactionChain createTransactionChain(DOMTransactionChainListener listener) { + return new TxChainLocal(listener); + } + + @Override + public @NonNull DOMTransactionChain createMergingTransactionChain(DOMTransactionChainListener listener) { + return null; + } + + @Override + public @NonNull ClassToInstanceMap getExtensions() { + return null; + } + + @Override + public DOMDataTreeReadTransaction newReadOnlyTransaction() { + return null; + } + + @Override + public DOMDataTreeWriteTransaction newWriteOnlyTransaction() { + return null; + } + + @Override + public DOMDataTreeReadWriteTransaction newReadWriteTransaction() { + return null; + } + } +}