}
public static final String DATA = "application/yang-data";
- public static final String PATCH = "application/yang.patch";
- public static final String PATCH_STATUS = "application/yang.patch-status";
+ public static final String YANG_PATCH = "application/yang.patch";
+ public static final String YANG_PATCH_STATUS = "application/yang.patch-status";
public static final String YIN = "application/yin";
public static final String YANG = "application/yang";
}
@VisibleForTesting
static final MediaType YANG_DATA_XML_TYPE = MediaType.valueOf(MediaTypes.DATA + RestconfConstants.XML);
@VisibleForTesting
- static final MediaType YANG_PATCH_JSON_TYPE = MediaType.valueOf(MediaTypes.PATCH + RestconfConstants.JSON);
+ static final MediaType YANG_PATCH_JSON_TYPE = MediaType.valueOf(MediaTypes.YANG_PATCH + RestconfConstants.JSON);
@VisibleForTesting
- static final MediaType YANG_PATCH_XML_TYPE = MediaType.valueOf(MediaTypes.PATCH + RestconfConstants.XML);
+ static final MediaType YANG_PATCH_XML_TYPE = MediaType.valueOf(MediaTypes.YANG_PATCH + RestconfConstants.XML);
private static final Logger LOG = LoggerFactory.getLogger(RestconfDocumentedExceptionMapper.class);
private static final MediaType DEFAULT_MEDIA_TYPE = MediaType.APPLICATION_JSON_TYPE;
import org.slf4j.LoggerFactory;
@Provider
-@Consumes({Rfc8040.MediaTypes.PATCH + RestconfConstants.JSON})
+@Consumes({Rfc8040.MediaTypes.YANG_PATCH + RestconfConstants.JSON})
public class JsonToPatchBodyReader extends AbstractToPatchBodyReader {
private static final Logger LOG = LoggerFactory.getLogger(JsonToPatchBodyReader.class);
@Provider
-@Produces({ Rfc8040.MediaTypes.PATCH_STATUS + RestconfConstants.JSON })
+@Produces({ Rfc8040.MediaTypes.YANG_PATCH_STATUS + RestconfConstants.JSON })
public class PatchJsonBodyWriter implements MessageBodyWriter<PatchStatusContext> {
@Override
import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
@Provider
-@Produces({ Rfc8040.MediaTypes.PATCH_STATUS + RestconfConstants.XML })
+@Produces({ Rfc8040.MediaTypes.YANG_PATCH_STATUS + RestconfConstants.XML })
public class PatchXmlBodyWriter implements MessageBodyWriter<PatchStatusContext> {
private static final XMLOutputFactory XML_FACTORY;
import org.xml.sax.SAXException;
@Provider
-@Consumes({Rfc8040.MediaTypes.PATCH + RestconfConstants.XML})
+@Consumes({Rfc8040.MediaTypes.YANG_PATCH + RestconfConstants.XML})
public class XmlToPatchBodyReader extends AbstractToPatchBodyReader {
private static final Logger LOG = LoggerFactory.getLogger(XmlToPatchBodyReader.class);
private static final Splitter SLASH_SPLITTER = Splitter.on('/');
*/
@Patch
@Path("/data/{identifier:.+}")
- @Consumes({ Rfc8040.MediaTypes.PATCH + RestconfConstants.JSON, Rfc8040.MediaTypes.PATCH + RestconfConstants.XML })
- @Produces({ Rfc8040.MediaTypes.PATCH_STATUS + RestconfConstants.JSON,
- Rfc8040.MediaTypes.PATCH_STATUS + RestconfConstants.XML })
+ @Consumes({ Rfc8040.MediaTypes.YANG_PATCH + RestconfConstants.JSON,
+ Rfc8040.MediaTypes.YANG_PATCH + RestconfConstants.XML })
+ @Produces({ Rfc8040.MediaTypes.YANG_PATCH_STATUS + RestconfConstants.JSON,
+ Rfc8040.MediaTypes.YANG_PATCH_STATUS + RestconfConstants.XML })
PatchStatusContext patchData(@Encoded @PathParam("identifier") String identifier, PatchContext context,
@Context UriInfo uriInfo);
*/
@Patch
@Path("/data")
- @Consumes({ Rfc8040.MediaTypes.PATCH + RestconfConstants.JSON, Rfc8040.MediaTypes.PATCH + RestconfConstants.XML })
- @Produces({ Rfc8040.MediaTypes.PATCH_STATUS + RestconfConstants.JSON,
- Rfc8040.MediaTypes.PATCH_STATUS + RestconfConstants.XML })
+ @Consumes({ Rfc8040.MediaTypes.YANG_PATCH + RestconfConstants.JSON,
+ Rfc8040.MediaTypes.YANG_PATCH + RestconfConstants.XML })
+ @Produces({ Rfc8040.MediaTypes.YANG_PATCH_STATUS + RestconfConstants.JSON,
+ Rfc8040.MediaTypes.YANG_PATCH_STATUS + RestconfConstants.XML })
PatchStatusContext patchData(PatchContext context, @Context UriInfo uriInfo);
+
+
+ /**
+ * Partially modify the target data resource.
+ *
+ * @param identifier
+ * path to target
+ * @param payload
+ * data node for put to config DS
+ * @return {@link Response}
+ */
+ @Patch
+ @Path("/data/{identifier:.+}")
+ @Consumes({ Rfc8040.MediaTypes.DATA + RestconfConstants.JSON, Rfc8040.MediaTypes.DATA, MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML, MediaType.TEXT_XML })
+ Response patchData(@Encoded @PathParam("identifier") String identifier, NormalizedNodeContext payload,
+ @Context UriInfo uriInfo);
}
package org.opendaylight.restconf.nb.rfc8040.rests.services.impl;
import static java.util.Objects.requireNonNull;
+import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.PostPutQueryParameters.INSERT;
+import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.PostPutQueryParameters.POINT;
import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.CREATE_NOTIFICATION_STREAM;
import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_ACCESS_PATH_PART;
import static org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants.STREAM_LOCATION_PATH_PART;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.dom.api.DOMActionResult;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMMountPoint;
import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
import org.opendaylight.restconf.nb.rfc8040.rests.utils.DeleteDataTransactionUtil;
import org.opendaylight.restconf.nb.rfc8040.rests.utils.PatchDataTransactionUtil;
+import org.opendaylight.restconf.nb.rfc8040.rests.utils.PlainPatchDataTransactionUtil;
import org.opendaylight.restconf.nb.rfc8040.rests.utils.PostDataTransactionUtil;
import org.opendaylight.restconf.nb.rfc8040.rests.utils.PutDataTransactionUtil;
import org.opendaylight.restconf.nb.rfc8040.rests.utils.ReadDataTransactionUtil;
import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfInvokeOperationsUtil;
import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
+import org.opendaylight.yangtools.concepts.Immutable;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.Revision;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
*/
@Path("/")
public class RestconfDataServiceImpl implements RestconfDataService {
+ private static final class QueryParams implements Immutable {
+ final @Nullable String point;
+ final @Nullable String insert;
+
+ QueryParams(final @Nullable String insert, final @Nullable String point) {
+ this.insert = insert;
+ this.point = point;
+ }
+ }
private static final Logger LOG = LoggerFactory.getLogger(RestconfDataServiceImpl.class);
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss");
public Response putData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
requireNonNull(payload);
+ final QueryParams checkedParms = checkQueryParameters(uriInfo);
+
+ final InstanceIdentifierContext<? extends SchemaNode> iid = payload
+ .getInstanceIdentifierContext();
+
+ PutDataTransactionUtil.validInputData(iid.getSchemaNode(), payload);
+ PutDataTransactionUtil.validTopLevelNodeName(iid.getInstanceIdentifier(), payload);
+ PutDataTransactionUtil.validateListKeysEqualityInPayloadAndUri(payload);
+
+ final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
+ final TransactionChainHandler localTransactionChainHandler;
+ final SchemaContextRef ref;
+ if (mountPoint == null) {
+ localTransactionChainHandler = this.transactionChainHandler;
+ ref = new SchemaContextRef(this.schemaContextHandler.get());
+ } else {
+ localTransactionChainHandler = transactionChainOfMountPoint(mountPoint);
+ ref = new SchemaContextRef(mountPoint.getSchemaContext());
+ }
+
+ final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
+ payload.getInstanceIdentifierContext(), mountPoint, localTransactionChainHandler);
+ return PutDataTransactionUtil.putData(payload, ref, transactionNode, checkedParms.insert, checkedParms.point);
+ }
+
+ private static QueryParams checkQueryParameters(final UriInfo uriInfo) {
boolean insertUsed = false;
boolean pointUsed = false;
String insert = null;
for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
switch (entry.getKey()) {
- case "insert":
+ case INSERT:
if (!insertUsed) {
insertUsed = true;
- insert = entry.getValue().iterator().next();
+ insert = entry.getValue().get(0);
} else {
throw new RestconfDocumentedException("Insert parameter can be used only once.",
RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT);
}
break;
- case "point":
+ case POINT:
if (!pointUsed) {
pointUsed = true;
- point = entry.getValue().iterator().next();
+ point = entry.getValue().get(0);
} else {
throw new RestconfDocumentedException("Point parameter can be used only once.",
RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT);
}
checkQueryParams(insertUsed, pointUsed, insert);
-
- final InstanceIdentifierContext<? extends SchemaNode> iid = payload
- .getInstanceIdentifierContext();
-
- PutDataTransactionUtil.validInputData(iid.getSchemaNode(), payload);
- PutDataTransactionUtil.validTopLevelNodeName(iid.getInstanceIdentifier(), payload);
- PutDataTransactionUtil.validateListKeysEqualityInPayloadAndUri(payload);
-
- final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
- final TransactionChainHandler localTransactionChainHandler;
- final SchemaContextRef ref;
- if (mountPoint == null) {
- localTransactionChainHandler = this.transactionChainHandler;
- ref = new SchemaContextRef(this.schemaContextHandler.get());
- } else {
- localTransactionChainHandler = transactionChainOfMountPoint(mountPoint);
- ref = new SchemaContextRef(mountPoint.getSchemaContext());
- }
-
- final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
- payload.getInstanceIdentifierContext(), mountPoint, localTransactionChainHandler);
- return PutDataTransactionUtil.putData(payload, ref, transactionNode, insert, point);
+ return new QueryParams(insert, point);
}
private static void checkQueryParams(final boolean insertUsed, final boolean pointUsed, final String insert) {
if (payload.getInstanceIdentifierContext().getSchemaNode() instanceof ActionDefinition) {
return invokeAction(payload, uriInfo);
}
- boolean insertUsed = false;
- boolean pointUsed = false;
- String insert = null;
- String point = null;
-
- for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
- switch (entry.getKey()) {
- case "insert":
- if (!insertUsed) {
- insertUsed = true;
- insert = entry.getValue().iterator().next();
- } else {
- throw new RestconfDocumentedException("Insert parameter can be used only once.",
- RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT);
- }
- break;
- case "point":
- if (!pointUsed) {
- pointUsed = true;
- point = entry.getValue().iterator().next();
- } else {
- throw new RestconfDocumentedException("Point parameter can be used only once.",
- RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT);
- }
- break;
- default:
- throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey(),
- RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT);
- }
- }
- checkQueryParams(insertUsed, pointUsed, insert);
+ final QueryParams checkedParms = checkQueryParameters(uriInfo);
final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
payload.getInstanceIdentifierContext(), mountPoint, getTransactionChainHandler(mountPoint));
return PostDataTransactionUtil.postData(uriInfo, payload, transactionNode,
- getSchemaContext(mountPoint), insert, point);
+ getSchemaContext(mountPoint), checkedParms.insert, checkedParms.point);
}
@Override
return PatchDataTransactionUtil.patchData(context, transactionNode, getSchemaContext(mountPoint));
}
+ @Override
+ public Response patchData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
+ requireNonNull(payload);
+
+ final InstanceIdentifierContext<? extends SchemaNode> iid = payload
+ .getInstanceIdentifierContext();
+
+ PutDataTransactionUtil.validInputData(iid.getSchemaNode(), payload);
+ PutDataTransactionUtil.validTopLevelNodeName(iid.getInstanceIdentifier(), payload);
+ PutDataTransactionUtil.validateListKeysEqualityInPayloadAndUri(payload);
+
+ final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
+ final TransactionChainHandler localTransactionChainHandler;
+ final SchemaContextRef ref;
+ if (mountPoint == null) {
+ localTransactionChainHandler = this.transactionChainHandler;
+ ref = new SchemaContextRef(this.schemaContextHandler.get());
+ } else {
+ localTransactionChainHandler = transactionChainOfMountPoint(mountPoint);
+ ref = new SchemaContextRef(mountPoint.getSchemaContext());
+ }
+
+ final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
+ payload.getInstanceIdentifierContext(), mountPoint, localTransactionChainHandler);
+
+ return PlainPatchDataTransactionUtil.patchData(payload, transactionNode, ref);
+ }
+
private TransactionChainHandler getTransactionChainHandler(final DOMMountPoint mountPoint) {
return mountPoint == null ? transactionChainHandler : transactionChainOfMountPoint(mountPoint);
}
--- /dev/null
+/*
+ * Copyright (c) 2020 Lumina Networks, Inc. and others. All rights reserved.
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.restconf.nb.rfc8040.rests.utils;
+
+import com.google.common.util.concurrent.FluentFuture;
+
+import javax.ws.rs.core.Response;
+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.mdsal.dom.api.DOMDataTreeWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
+import org.opendaylight.restconf.common.context.NormalizedNodeContext;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.TransactionVarsWrapper;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Util class for plain patch data to DS.
+ *
+ */
+public final class PlainPatchDataTransactionUtil {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PlainPatchDataTransactionUtil.class);
+
+ private PlainPatchDataTransactionUtil() {
+ }
+
+ /**
+ * Prepare variables for put data to DS. Close {@link DOMTransactionChain} inside of object
+ * {@link TransactionVarsWrapper} provided as a parameter.
+ *
+ * @param payload
+ * data to put
+ * @param schemaContextRef
+ * reference to {@link SchemaContext}
+ * @param transactionNode
+ * wrapper of variables for transaction
+ * @return {@link Response}
+ */
+ public static Response patchData(final NormalizedNodeContext payload,
+ final TransactionVarsWrapper transactionNode,
+ final SchemaContextRef schemaContextRef) {
+
+ final DOMTransactionChain transactionChain = transactionNode.getTransactionChain();
+ final DOMDataTreeReadWriteTransaction tx = transactionChain.newReadWriteTransaction();
+
+ YangInstanceIdentifier path = payload.getInstanceIdentifierContext().getInstanceIdentifier();
+ NormalizedNode<?, ?> data = payload.getData();
+
+ try {
+ mergeDataWithinTransaction(LogicalDatastoreType.CONFIGURATION,
+ path, data, tx, schemaContextRef);
+ } catch (final RestconfDocumentedException e) {
+ tx.cancel();
+ transactionChain.close();
+
+ throw new IllegalArgumentException(e);
+ }
+
+ final FluentFuture<? extends CommitInfo> future = tx.commit();
+ final ResponseFactory response = new ResponseFactory(Status.OK);
+
+ FutureCallbackTx.addCallback(future,
+ RestconfDataServiceConstant.PatchData.PATCH_TX_TYPE,
+ response,
+ transactionChain); // closes transactionChain, may throw
+
+ return response.build();
+ }
+
+ /**
+ * Merge data within one transaction.
+ * @param dataStore Datastore to merge data to
+ * @param path Path for data to be merged
+ * @param payload Data to be merged
+ * @param writeTransaction Transaction
+ * @param schemaContextRef Soft reference for global schema context
+ */
+ private static void mergeDataWithinTransaction(final LogicalDatastoreType dataStore,
+ final YangInstanceIdentifier path,
+ final NormalizedNode<?, ?> payload,
+ final DOMDataTreeWriteTransaction writeTransaction,
+ final SchemaContextRef schemaContextRef) {
+ LOG.trace("Merge {} within Restconf Patch: {} with payload {}", dataStore.name(), path, payload);
+ TransactionUtil.ensureParentsByMerge(path, schemaContextRef.get(), writeTransaction);
+ writeTransaction.merge(dataStore, path, payload);
+ }
+}
}
}
+ /**
+ * Common for PostData and PutData.
+ */
+ public static final class PostPutQueryParameters {
+ public static final String INSERT = "insert";
+ public static final String POINT = "point";
+
+ private PostPutQueryParameters() {
+ // Hidden on purpose
+ }
+ }
+
/**
* Constants for data to put.
*
return this.delegRestconfDataService.patchData(context, uriInfo);
}
+ @Override
+ public Response patchData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
+ return this.delegRestconfDataService.patchData(identifier, payload, uriInfo);
+ }
+
@Override
public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload,
final UriInfo uriInfo) {
--- /dev/null
+/*
+ * Copyright (c) 2020 Lumina Networks, Inc. and others. All rights reserved.
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.restconf.nb.rfc8040.rests.utils;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFalseFluentFuture;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
+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.nb.rfc8040.TestRestconfUtils;
+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.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+public class PlainPatchDataTransactionUtilTest {
+
+ private static final String PATH_FOR_NEW_SCHEMA_CONTEXT = "/jukebox";
+
+ @Mock
+ private DOMTransactionChain transactionChain;
+ @Mock
+ private DOMDataTreeReadWriteTransaction readWrite;
+ @Mock
+ private DOMDataTreeReadTransaction read;
+ @Mock
+ private DOMDataTreeWriteTransaction write;
+ @Mock
+ private DOMDataBroker mockDataBroker;
+
+ private TransactionChainHandler transactionChainHandler;
+ private SchemaContextRef refSchemaCtx;
+ private LeafNode leafGap;
+ private ContainerNode jukeboxContainerWithPlayer;
+ private ContainerNode jukeboxContainerWithPlaylist;
+ private SchemaContext schema;
+ private DataSchemaNode schemaNodeForGap;
+ private YangInstanceIdentifier iidGap;
+ private DataSchemaNode schemaNodeForJukebox;
+ private YangInstanceIdentifier iidJukebox;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ this.refSchemaCtx = new SchemaContextRef(
+ YangParserTestUtils.parseYangFiles(TestRestconfUtils.loadFiles(PATH_FOR_NEW_SCHEMA_CONTEXT)));
+ this.schema = this.refSchemaCtx.get();
+
+ final QName qnJukebox = QName.create("http://example.com/ns/example-jukebox", "2015-04-04", "jukebox");
+ final QName qnPlayer = QName.create(qnJukebox, "player");
+ final QName qnGap = QName.create(qnJukebox, "gap");
+ final QName qnPlaylist = QName.create(qnJukebox, "playlist");
+ final QName qnPlaylistKey = QName.create(qnJukebox, "name");
+
+ final NodeIdentifierWithPredicates nidBandA =
+ NodeIdentifierWithPredicates.of(qnPlaylist, qnPlaylistKey, "MyFavoriteBand-A");
+ final NodeIdentifierWithPredicates nidBandB =
+ NodeIdentifierWithPredicates.of(qnPlaylist, qnPlaylistKey, "MyFavoriteBand-B");
+
+ this.iidGap = YangInstanceIdentifier.builder()
+ .node(qnJukebox)
+ .node(qnPlayer)
+ .node(qnGap)
+ .build();
+ this.schemaNodeForGap = DataSchemaContextTree.from(this.schema).getChild(this.iidGap).getDataSchemaNode();
+
+ this.iidJukebox = YangInstanceIdentifier.builder()
+ .node(qnJukebox)
+ .build();
+ this.schemaNodeForJukebox = DataSchemaContextTree.from(this.schema)
+ .getChild(this.iidJukebox).getDataSchemaNode();
+
+ this.leafGap = Builders.leafBuilder()
+ .withNodeIdentifier(new NodeIdentifier(qnGap))
+ .withValue(0.2)
+ .build();
+ final ContainerNode playerContainer = Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(qnPlayer))
+ .withChild(this.leafGap)
+ .build();
+ this.jukeboxContainerWithPlayer = Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(qnJukebox))
+ .withChild(playerContainer)
+ .build();
+
+ // ----------
+
+ final LeafNode<Object> leafBandA = Builders.leafBuilder()
+ .withNodeIdentifier(new NodeIdentifier(QName.create(qnJukebox, "name")))
+ .withValue("MyFavoriteBand-A")
+ .build();
+ final LeafNode<Object> leafDescriptionA = Builders.leafBuilder()
+ .withNodeIdentifier(new NodeIdentifier(QName.create(qnJukebox, "description")))
+ .withValue("band description A")
+ .build();
+ final MapEntryNode entryBandA = Builders.mapEntryBuilder()
+ .withNodeIdentifier(nidBandA)
+ .withChild(leafBandA)
+ .withChild(leafDescriptionA)
+ .build();
+
+ final LeafNode<Object> leafBandB = Builders.leafBuilder()
+ .withNodeIdentifier(new NodeIdentifier(QName.create(qnJukebox, "name")))
+ .withValue("MyFavoriteBand-B")
+ .build();
+ final LeafNode<Object> leafDescriptionB = Builders.leafBuilder()
+ .withNodeIdentifier(new NodeIdentifier(QName.create(qnJukebox, "description")))
+ .withValue("band description B")
+ .build();
+ final MapEntryNode entryBandB = Builders.mapEntryBuilder()
+ .withNodeIdentifier(nidBandB)
+ .withChild(leafBandB)
+ .withChild(leafDescriptionB)
+ .build();
+
+ final MapNode listBands = Builders.mapBuilder()
+ .withNodeIdentifier(new NodeIdentifier(qnPlaylist))
+ .withChild(entryBandA)
+ .withChild(entryBandB)
+ .build();
+ this.jukeboxContainerWithPlaylist = Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(qnJukebox))
+ .withChild(listBands)
+ .build();
+
+ Mockito.doReturn(transactionChain).when(mockDataBroker).createTransactionChain(Mockito.any());
+ transactionChainHandler = new TransactionChainHandler(mockDataBroker);
+ }
+
+ @Test
+ public void testPatchContainerData() {
+ final InstanceIdentifierContext<DataSchemaNode> iidContext =
+ new InstanceIdentifierContext<>(this.iidJukebox, this.schemaNodeForJukebox, null, this.schema);
+ final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.jukeboxContainerWithPlayer);
+
+ doReturn(this.readWrite).when(this.transactionChain).newReadWriteTransaction();
+ doReturn(this.read).when(this.transactionChain).newReadOnlyTransaction();
+ doReturn(this.write).when(this.transactionChain).newWriteOnlyTransaction();
+ doReturn(immediateFalseFluentFuture())
+ .when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, this.iidJukebox);
+ doNothing().when(this.readWrite).put(LogicalDatastoreType.CONFIGURATION,
+ payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData());
+ doReturn(CommitInfo.emptyFluentFuture()).when(this.readWrite).commit();
+
+ PlainPatchDataTransactionUtil.patchData(payload,
+ new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChainHandler),
+ this.refSchemaCtx);
+
+ verify(this.readWrite).merge(LogicalDatastoreType.CONFIGURATION,
+ payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData());
+ }
+
+ @Test
+ public void testPatchLeafData() {
+ final InstanceIdentifierContext<DataSchemaNode> iidContext =
+ new InstanceIdentifierContext<>(this.iidGap, this.schemaNodeForGap, null, this.schema);
+ final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.leafGap);
+
+ doReturn(this.readWrite).when(this.transactionChain).newReadWriteTransaction();
+ doReturn(this.read).when(this.transactionChain).newReadOnlyTransaction();
+ doReturn(this.write).when(this.transactionChain).newWriteOnlyTransaction();
+ doReturn(immediateFalseFluentFuture())
+ .when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, this.iidGap);
+ doNothing().when(this.readWrite).put(LogicalDatastoreType.CONFIGURATION,
+ payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData());
+ doReturn(CommitInfo.emptyFluentFuture()).when(this.readWrite).commit();
+
+ PlainPatchDataTransactionUtil.patchData(payload,
+ new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChainHandler),
+ this.refSchemaCtx);
+
+ verify(this.readWrite).merge(LogicalDatastoreType.CONFIGURATION,
+ payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData());
+ }
+
+ @Test
+ public void testPatchListData() {
+ final InstanceIdentifierContext<DataSchemaNode> iidContext =
+ new InstanceIdentifierContext<>(this.iidJukebox, this.schemaNodeForJukebox, null, this.schema);
+ final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, this.jukeboxContainerWithPlaylist);
+
+ doReturn(this.readWrite).when(this.transactionChain).newReadWriteTransaction();
+ doReturn(this.read).when(this.transactionChain).newReadOnlyTransaction();
+ doReturn(this.write).when(this.transactionChain).newWriteOnlyTransaction();
+ doReturn(immediateFalseFluentFuture())
+ .when(this.readWrite).exists(LogicalDatastoreType.CONFIGURATION, this.iidJukebox);
+ doNothing().when(this.readWrite).put(LogicalDatastoreType.CONFIGURATION,
+ payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData());
+ doReturn(CommitInfo.emptyFluentFuture()).when(this.readWrite).commit();
+
+ PlainPatchDataTransactionUtil.patchData(payload,
+ new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChainHandler),
+ this.refSchemaCtx);
+
+ verify(this.readWrite).merge(LogicalDatastoreType.CONFIGURATION, this.iidJukebox, payload.getData());
+ }
+}