import org.opendaylight.restconf.nb.rfc8040.jersey.providers.YangSchemaExportBodyWriter;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.YinSchemaExportBodyWriter;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.errors.RestconfDocumentedExceptionMapper;
-import org.opendaylight.restconf.nb.rfc8040.jersey.providers.patch.JsonPatchBodyReader;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.patch.JsonPatchStatusBodyWriter;
-import org.opendaylight.restconf.nb.rfc8040.jersey.providers.patch.XmlPatchBodyReader;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.patch.XmlPatchStatusBodyWriter;
/**
return ImmutableSet.<Object>builderWithExpectedSize(services.size() + 5)
.addAll(services)
.add(new JsonNormalizedNodeBodyReader(databindProvider, mountPointService))
- .add(new JsonPatchBodyReader(databindProvider, mountPointService))
.add(new XmlNormalizedNodeBodyReader(databindProvider, mountPointService))
- .add(new XmlPatchBodyReader(databindProvider, mountPointService))
.add(new RestconfDocumentedExceptionMapper(databindProvider))
.build();
}
/*
* Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o.
*
* 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.jersey.providers.patch;
+package org.opendaylight.restconf.nb.rfc8040.databind;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Verify.verify;
import static java.util.Objects.requireNonNull;
-import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.ext.Provider;
import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.restconf.common.patch.PatchEntity;
-import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
-import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
-import org.opendaylight.yangtools.yang.data.impl.schema.ResultAlreadySetException;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
import org.opendaylight.yangtools.yang.model.api.SchemaNode;
import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-@Provider
-@Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
-public class JsonPatchBodyReader extends AbstractPatchBodyReader {
- private static final Logger LOG = LoggerFactory.getLogger(JsonPatchBodyReader.class);
-
- public JsonPatchBodyReader(final DatabindProvider databindProvider,
- final DOMMountPointService mountPointService) {
- super(databindProvider, mountPointService);
+public final class JsonPatchBody extends PatchBody {
+ public JsonPatchBody(final InputStream inputStream) {
+ super(inputStream);
}
- @SuppressWarnings("checkstyle:IllegalCatch")
@Override
- protected PatchContext readBody(final InstanceIdentifierContext path, final InputStream entityStream)
- throws WebApplicationException {
- try {
- return readFrom(path, entityStream);
- } catch (final Exception e) {
- throw propagateExceptionAs(e);
- }
- }
-
- private static PatchContext readFrom(final InstanceIdentifierContext path, final InputStream entityStream)
+ PatchContext toPatchContext(final InstanceIdentifierContext targetResource, final InputStream inputStream)
throws IOException {
- try (var jsonReader = new JsonReader(new InputStreamReader(entityStream, StandardCharsets.UTF_8))) {
+ try (var jsonReader = new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
final var patchId = new AtomicReference<String>();
- final var resultList = read(jsonReader, path, patchId);
- return new PatchContext(path, resultList, patchId.get());
- }
- }
-
- private static RestconfDocumentedException propagateExceptionAs(final Exception exception)
- throws RestconfDocumentedException {
- Throwables.throwIfInstanceOf(exception, RestconfDocumentedException.class);
- LOG.debug("Error parsing json input", exception);
-
- if (exception instanceof ResultAlreadySetException) {
- throw new RestconfDocumentedException("Error parsing json input: Failed to create new parse result data. ");
+ final var resultList = read(jsonReader, targetResource, patchId);
+ return new PatchContext(targetResource, resultList, patchId.get());
}
-
- RestconfDocumentedException.throwIfYangError(exception);
- throw new RestconfDocumentedException("Error parsing json input: " + exception.getMessage(), ErrorType.PROTOCOL,
- ErrorTag.MALFORMED_MESSAGE, exception);
}
private static ImmutableList<PatchEntity> read(final JsonReader in, final InstanceIdentifierContext path,
final AtomicReference<String> patchId) throws IOException {
final var schemaTree = DataSchemaContextTree.from(path.getSchemaContext());
final var edits = ImmutableList.<PatchEntity>builder();
- final var edit = new JsonPatchBodyReader.PatchEdit();
+ final var edit = new PatchEdit();
while (in.hasNext()) {
switch (in.peek()) {
* @throws IOException if operation fails
*/
private static void parseByName(final @NonNull String name, final @NonNull PatchEdit edit,
- final @NonNull JsonReader in, final @NonNull InstanceIdentifierContext path,
- final @NonNull DataSchemaContextTree schemaTree,
- final ImmutableList.@NonNull Builder<PatchEntity> resultCollection,
- final @NonNull AtomicReference<String> patchId) throws IOException {
+ final @NonNull JsonReader in, final @NonNull InstanceIdentifierContext path,
+ final @NonNull DataSchemaContextTree schemaTree,
+ final ImmutableList.@NonNull Builder<PatchEntity> resultCollection,
+ final @NonNull AtomicReference<String> patchId) throws IOException {
switch (name) {
case "edit":
if (in.peek() == JsonToken.BEGIN_ARRAY) {
* @throws IOException if operation fails
*/
private static void readEditDefinition(final @NonNull PatchEdit edit, final @NonNull JsonReader in,
- final @NonNull InstanceIdentifierContext path,
- final @NonNull DataSchemaContextTree schemaTree) throws IOException {
+ final @NonNull InstanceIdentifierContext path, final @NonNull DataSchemaContextTree schemaTree)
+ throws IOException {
String deferredValue = null;
in.beginObject();
* @throws IOException if operation fails
*/
private static void readValueObject(final @NonNull StringBuilder sb, final @NonNull JsonReader in)
- throws IOException {
+ throws IOException {
// read simple leaf value
if (in.peek() == JsonToken.STRING) {
sb.append('"').append(in.nextString()).append('"');
* @param in reader JsonReader reader
* @return NormalizedNode representing data
*/
- private static NormalizedNode readEditData(final @NonNull JsonReader in,
- final @NonNull Inference targetSchemaNode, final @NonNull InstanceIdentifierContext path) {
+ private static NormalizedNode readEditData(final @NonNull JsonReader in, final @NonNull Inference targetSchemaNode,
+ final @NonNull InstanceIdentifierContext path) {
final var resultHolder = new NormalizationResultHolder();
final var writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
JsonParserStream.create(writer, JSONCodecFactorySupplier.RFC7951.getShared(path.getSchemaContext()),
*/
private static PatchEntity prepareEditOperation(final @NonNull PatchEdit edit) {
if (edit.getOperation() != null && edit.getTargetSchemaNode() != null
- && checkDataPresence(edit.getOperation(), edit.getData() != null)) {
+ && checkDataPresence(edit.getOperation(), edit.getData() != null)) {
if (!requiresValue(edit.getOperation())) {
return new PatchEntity(edit.getId(), edit.getOperation(), edit.getTarget());
}
data = null;
}
}
-}
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2023 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.databind;
+
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+/**
+ * A YANG Patch body.
+ */
+public abstract class PatchBody implements AutoCloseable {
+ private static final VarHandle INPUT_STREAM;
+
+ static {
+ try {
+ INPUT_STREAM = MethodHandles.lookup().findVarHandle(PatchBody.class, "inputStream", InputStream.class);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private volatile InputStream inputStream;
+
+ PatchBody(final InputStream inputStream) {
+ this.inputStream = requireNonNull(inputStream);
+ }
+
+ @Override
+ public final void close() throws IOException {
+ final var is = acquireStream();
+ if (is != null) {
+ is.close();
+ }
+ }
+
+ public final @NonNull PatchContext toPatchContext(final @NonNull InstanceIdentifierContext targetResource)
+ throws IOException {
+ final var is = acquireStream();
+ if (is == null) {
+ throw new IllegalStateException("Input stream has already been consumed");
+ }
+
+ try {
+ return toPatchContext(targetResource, is);
+ } finally {
+ is.close();
+ }
+ }
+
+ abstract @NonNull PatchContext toPatchContext(@NonNull InstanceIdentifierContext targetResource,
+ @NonNull InputStream inputStream) throws IOException;
+
+ static final YangInstanceIdentifier parsePatchTarget(final InstanceIdentifierContext targetResource,
+ final String target) {
+ final var urlPath = targetResource.getInstanceIdentifier();
+ if (target.equals("/")) {
+ verify(!urlPath.isEmpty(),
+ "target resource of URI must not be a datastore resource when target is '/'");
+ return urlPath;
+ }
+
+ final var schemaContext = targetResource.getSchemaContext();
+ final String targetUrl;
+ if (urlPath.isEmpty()) {
+ targetUrl = target.startsWith("/") ? target.substring(1) : target;
+ } else {
+ targetUrl = IdentifierCodec.serialize(urlPath, schemaContext) + target;
+ }
+
+ return ParserIdentifier.toInstanceIdentifier(targetUrl, schemaContext, null).getInstanceIdentifier();
+ }
+
+ /**
+ * Not all patch operations support value node. Check if operation requires value or not.
+ *
+ * @param operation Patch edit operation
+ * @return true if operation requires value, false otherwise
+ */
+ static final boolean requiresValue(final Operation operation) {
+ return switch (operation) {
+ case Create, Insert, Merge, Replace -> true;
+ case Delete, Move, Remove -> false;
+ };
+ }
+
+ private @Nullable InputStream acquireStream() {
+ return (InputStream) INPUT_STREAM.getAndSet(this, null);
+ }
+}
/*
* Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o.
*
* 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.jersey.providers.patch;
+package org.opendaylight.restconf.nb.rfc8040.databind;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.ext.Provider;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.dom.DOMSource;
import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.restconf.common.patch.PatchEntity;
-import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
-import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
import org.opendaylight.yangtools.util.xml.UntrustedXML;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
-@Provider
-@Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
-public class XmlPatchBodyReader extends AbstractPatchBodyReader {
- private static final Logger LOG = LoggerFactory.getLogger(XmlPatchBodyReader.class);
+public final class XmlPatchBody extends PatchBody {
+ private static final Logger LOG = LoggerFactory.getLogger(XmlPatchBody.class);
- public XmlPatchBodyReader(final DatabindProvider databindProvider,
- final DOMMountPointService mountPointService) {
- super(databindProvider, mountPointService);
+ public XmlPatchBody(final InputStream inputStream) {
+ super(inputStream);
}
- @SuppressWarnings("checkstyle:IllegalCatch")
@Override
- protected PatchContext readBody(final InstanceIdentifierContext path, final InputStream entityStream)
- throws WebApplicationException {
+ PatchContext toPatchContext(final InstanceIdentifierContext targetResource, final InputStream inputStream)
+ throws IOException {
try {
- return parse(path, UntrustedXML.newDocumentBuilder().parse(entityStream));
- } catch (final RestconfDocumentedException e) {
- throw e;
- } catch (final Exception e) {
- LOG.debug("Error parsing xml input", e);
-
- throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
- ErrorTag.MALFORMED_MESSAGE, e);
+ return parse(targetResource, UntrustedXML.newDocumentBuilder().parse(inputStream));
+ } catch (XMLStreamException | SAXException | URISyntaxException e) {
+ LOG.debug("Failed to parse YANG Patch XML", e);
+ throw new RestconfDocumentedException("Error parsing YANG Patch XML: " + e.getMessage(), ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE, e);
}
}
- private static PatchContext parse(final InstanceIdentifierContext pathContext, final Document doc)
+ private static @NonNull PatchContext parse(final InstanceIdentifierContext targetResource, final Document doc)
throws XMLStreamException, IOException, SAXException, URISyntaxException {
final var resultCollection = new ArrayList<PatchEntity>();
final var patchId = doc.getElementsByTagName("patch-id").item(0).getFirstChild().getNodeValue();
final var editNodes = doc.getElementsByTagName("edit");
+ final var schemaTree = DataSchemaContextTree.from(targetResource.getSchemaContext());
for (int i = 0; i < editNodes.getLength(); i++) {
final Element element = (Element) editNodes.item(i);
final Element firstValueElement = values != null ? values.get(0) : null;
// find complete path to target, it can be also empty (only slash)
- final var targetII = parsePatchTarget(pathContext, target);
+ final var targetII = parsePatchTarget(targetResource, target);
// move schema node
- final var lookup = DataSchemaContextTree.from(pathContext.getSchemaContext())
- .enterPath(targetII).orElseThrow();
+ final var lookup = schemaTree.enterPath(targetII).orElseThrow();
final var stack = lookup.stack();
final var inference = stack.toInference();
}
}
- return new PatchContext(pathContext, ImmutableList.copyOf(resultCollection), patchId);
+ return new PatchContext(targetResource, ImmutableList.copyOf(resultCollection), patchId);
}
/**
return result;
}
-}
+}
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright (c) 2017 Pantheon Technologies, 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.jersey.providers.patch;
-
-import static com.google.common.base.Verify.verify;
-
-import org.opendaylight.mdsal.dom.api.DOMMountPointService;
-import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
-import org.opendaylight.restconf.common.patch.PatchContext;
-import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
-import org.opendaylight.restconf.nb.rfc8040.jersey.providers.spi.AbstractIdentifierAwareJaxRsProvider;
-import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
-import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-
-/**
- * Common superclass for readers producing {@link PatchContext}.
- *
- * @author Robert Varga
- */
-abstract class AbstractPatchBodyReader extends AbstractIdentifierAwareJaxRsProvider<PatchContext> {
- protected AbstractPatchBodyReader(final DatabindProvider databindProvider,
- final DOMMountPointService mountPointService) {
- super(databindProvider, mountPointService);
- }
-
- @Override
- protected final PatchContext emptyBody(final InstanceIdentifierContext path) {
- return new PatchContext(path, null, null);
- }
-
- static final YangInstanceIdentifier parsePatchTarget(final InstanceIdentifierContext context, final String target) {
- final var urlPath = context.getInstanceIdentifier();
- if (target.equals("/")) {
- verify(!urlPath.isEmpty(),
- "target resource of URI must not be a datastore resource when target is '/'");
- return urlPath;
- }
-
- final var schemaContext = context.getSchemaContext();
- final String targetUrl;
- if (urlPath.isEmpty()) {
- targetUrl = target.startsWith("/") ? target.substring(1) : target;
- } else {
- targetUrl = IdentifierCodec.serialize(urlPath, schemaContext) + target;
- }
-
- return ParserIdentifier.toInstanceIdentifier(targetUrl, schemaContext, null).getInstanceIdentifier();
- }
-
- /**
- * Not all patch operations support value node. Check if operation requires value or not.
- *
- * @param operation Patch edit operation
- * @return true if operation requires value, false otherwise
- */
- static final boolean requiresValue(final Operation operation) {
- return switch (operation) {
- case Create, Insert, Merge, Replace -> true;
- case Delete, Move, Remove -> false;
- };
- }
-}
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
+import java.io.IOException;
+import java.io.InputStream;
import java.net.URI;
import java.time.Clock;
import java.time.LocalDateTime;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.dom.api.DOMActionException;
import org.opendaylight.mdsal.dom.api.DOMActionResult;
import org.opendaylight.restconf.nb.rfc8040.ReadDataParams;
import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
+import org.opendaylight.restconf.nb.rfc8040.databind.JsonPatchBody;
+import org.opendaylight.restconf.nb.rfc8040.databind.PatchBody;
+import org.opendaylight.restconf.nb.rfc8040.databind.XmlPatchBody;
import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
* <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
*
* @param identifier path to target
- * @param context edits
+ * @param body YANG Patch body
* @return {@link PatchStatusContext}
*/
@PATCH
@Path("/data/{identifier:.+}")
- @Consumes({
- MediaTypes.APPLICATION_YANG_PATCH_JSON,
- MediaTypes.APPLICATION_YANG_PATCH_XML
- })
+ @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
@Produces({
MediaTypes.APPLICATION_YANG_DATA_JSON,
MediaTypes.APPLICATION_YANG_DATA_XML
})
- public PatchStatusContext yangPatchData(@Encoded @PathParam("identifier") final String identifier,
- final PatchContext context) {
- return yangPatchData(context);
+ public PatchStatusContext yangPatchDataXML(@Encoded @PathParam("identifier") final String identifier,
+ final InputStream body) {
+ return yangPatchData(identifier, new XmlPatchBody(body));
}
/**
* Ordered list of edits that are applied to the datastore by the server, as defined in
* <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
*
- * @param context edits
+ * @param body YANG Patch body
* @return {@link PatchStatusContext}
*/
@PATCH
@Path("/data")
- @Consumes({
- MediaTypes.APPLICATION_YANG_PATCH_JSON,
- MediaTypes.APPLICATION_YANG_PATCH_XML
+ @Consumes(MediaTypes.APPLICATION_YANG_PATCH_XML)
+ @Produces({
+ MediaTypes.APPLICATION_YANG_DATA_JSON,
+ MediaTypes.APPLICATION_YANG_DATA_XML
+ })
+ public PatchStatusContext yangPatchDataXML(final InputStream body) {
+ return yangPatchData(new XmlPatchBody(body));
+ }
+
+ /**
+ * Ordered list of edits that are applied to the target datastore by the server, as defined in
+ * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
+ *
+ * @param identifier path to target
+ * @param body YANG Patch body
+ * @return {@link PatchStatusContext}
+ */
+ @PATCH
+ @Path("/data/{identifier:.+}")
+ @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
+ @Produces({
+ MediaTypes.APPLICATION_YANG_DATA_JSON,
+ MediaTypes.APPLICATION_YANG_DATA_XML
})
+ public PatchStatusContext yangPatchDataJSON(@Encoded @PathParam("identifier") final String identifier,
+ final InputStream body) {
+ return yangPatchData(identifier, new JsonPatchBody(body));
+ }
+
+ /**
+ * Ordered list of edits that are applied to the datastore by the server, as defined in
+ * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
+ *
+ * @param body YANG Patch body
+ * @return {@link PatchStatusContext}
+ */
+ @PATCH
+ @Path("/data")
+ @Consumes(MediaTypes.APPLICATION_YANG_PATCH_JSON)
@Produces({
MediaTypes.APPLICATION_YANG_DATA_JSON,
MediaTypes.APPLICATION_YANG_DATA_XML
})
- public PatchStatusContext yangPatchData(final PatchContext context) {
- final InstanceIdentifierContext iid = RestconfDocumentedException.throwIfNull(context,
- ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, "No patch documented provided")
- .getInstanceIdentifierContext();
- final RestconfStrategy strategy = getRestconfStrategy(iid.getMountPoint());
- return PatchDataTransactionUtil.patchData(context, strategy, iid.getSchemaContext());
+ public PatchStatusContext yangPatchDataJSON(final InputStream body) {
+ return yangPatchData(new JsonPatchBody(body));
+ }
+
+ private PatchStatusContext yangPatchData(final @NonNull PatchBody body) {
+ return yangPatchData(InstanceIdentifierContext.ofLocalRoot(databindProvider.currentContext().modelContext()),
+ body);
+ }
+
+ private PatchStatusContext yangPatchData(final String identifier, final @NonNull PatchBody body) {
+ return yangPatchData(ParserIdentifier.toInstanceIdentifier(identifier,
+ databindProvider.currentContext().modelContext(), mountPointService), body);
+ }
+
+ private PatchStatusContext yangPatchData(final @NonNull InstanceIdentifierContext targetResource,
+ final @NonNull PatchBody body) {
+ try {
+ return yangPatchData(targetResource, body.toPatchContext(targetResource));
+ } catch (IOException e) {
+ LOG.debug("Error parsing YANG Patch input", e);
+ throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
+ ErrorTag.MALFORMED_MESSAGE, e);
+ }
+ }
+
+ @VisibleForTesting
+ PatchStatusContext yangPatchData(final InstanceIdentifierContext targetResource, final PatchContext context) {
+ return PatchDataTransactionUtil.patchData(context, getRestconfStrategy(targetResource.getMountPoint()),
+ targetResource.getSchemaContext());
}
@VisibleForTesting
--- /dev/null
+/*
+ * Copyright (c) 2023 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.databind;
+
+import static java.util.Objects.requireNonNull;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+import java.util.function.Function;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
+import org.opendaylight.restconf.common.patch.PatchContext;
+import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+@RunWith(MockitoJUnitRunner.Silent.class)
+abstract class AbstractPatchBodyTest extends AbstractInstanceIdentifierTest {
+ private final Function<InputStream, PatchBody> bodyConstructor;
+
+ @Mock
+ DOMMountPointService mountPointService;
+ @Mock
+ DOMMountPoint mountPoint;
+
+ AbstractPatchBodyTest(final Function<InputStream, PatchBody> bodyConstructor) {
+ this.bodyConstructor = requireNonNull(bodyConstructor);
+ }
+
+ @Before
+ public final void before() {
+ doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(any(YangInstanceIdentifier.class));
+ doReturn(Optional.of(FixedDOMSchemaService.of(IID_SCHEMA))).when(mountPoint).getService(DOMSchemaService.class);
+ }
+
+ @NonNull String mountPrefix() {
+ return "";
+ }
+
+ @Nullable DOMMountPoint mountPoint() {
+ return null;
+ }
+
+ final void checkPatchContext(final PatchContext patchContext) {
+ assertNotNull(patchContext.getData());
+
+ final var iid = patchContext.getInstanceIdentifierContext();
+ assertNotNull(iid);
+
+ assertNotNull(iid.getInstanceIdentifier());
+ assertNotNull(iid.getSchemaContext());
+ assertNotNull(iid.getSchemaNode());
+ assertSame(mountPoint(), iid.getMountPoint());
+ }
+
+ final @NonNull PatchContext parse(final String uriPath, final String patchBody) throws IOException {
+ return parse(uriPath, new ByteArrayInputStream(patchBody.getBytes(StandardCharsets.UTF_8)));
+ }
+
+ // FIXME: migrate callers to use the above instead of resources
+ @Deprecated
+ final @NonNull PatchContext parse(final String uriPath, final InputStream patchBody) throws IOException {
+ return bodyConstructor.apply(patchBody).toPatchContext(
+ ParserIdentifier.toInstanceIdentifier(uriPath, IID_SCHEMA, mountPointService));
+ }
+}
* 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.jersey.providers.patch;
+package org.opendaylight.restconf.nb.rfc8040.databind;
import org.opendaylight.mdsal.dom.api.DOMMountPoint;
-public class XmlPatchBodyReaderMountPointTest extends XmlPatchBodyReaderTest {
+public class JsonPatchBodyMountPointTest extends JsonPatchBodyTest {
@Override
String mountPrefix() {
return "instance-identifier-module:cont/yang-ext:mount/";
* 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.jersey.providers.patch;
+package org.opendaylight.restconf.nb.rfc8040.databind;
-import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
-import javax.ws.rs.core.MediaType;
import org.junit.Test;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
- private final JsonPatchBodyReader jsonToPatchBodyReader =
- new JsonPatchBodyReader(databindProvider, mountPointService);
-
- @Override
- protected final MediaType getMediaType() {
- return new MediaType(APPLICATION_JSON, null);
+public class JsonPatchBodyTest extends AbstractPatchBodyTest {
+ public JsonPatchBodyTest() {
+ super(JsonPatchBody::new);
}
@Test
public final void modulePatchDataTest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
- jsonToPatchBodyReader, false);
-
- checkPatchContext(jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, stringInputStream("""
+ checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
{
"ietf-yang-patch:yang-patch" : {
"patch-id" : "test-patch",
}
]
}
- }""")));
+ }"""));
}
/**
*/
@Test
public final void modulePatchCreateAndDeleteTest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
- jsonToPatchBodyReader, false);
-
- checkPatchContext(jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, stringInputStream("""
+ checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
{
"ietf-yang-patch:yang-patch" : {
"patch-id" : "test-patch",
}
]
}
- }""")));
+ }"""));
}
/**
*/
@Test
public final void modulePatchValueMissingNegativeTest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
- jsonToPatchBodyReader, false);
-
final var ex = assertThrows(RestconfDocumentedException.class,
- () -> jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, stringInputStream("""
+ () -> parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
{
"ietf-yang-patch:yang-patch" : {
"patch-id" : "test-patch",
}
]
}
- }""")));
+ }"""));
assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
}
*/
@Test
public final void modulePatchValueNotSupportedNegativeTest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
- jsonToPatchBodyReader, false);
-
- final var inputStream = JsonPatchBodyReaderTest.class.getResourceAsStream(
+ final var inputStream = JsonPatchBodyTest.class.getResourceAsStream(
"/instanceidentifier/json/jsonPATCHdataValueNotSupported.json");
-
final var ex = assertThrows(RestconfDocumentedException.class,
- () -> jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream));
+ () -> parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", inputStream));
assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
}
*/
@Test
public final void modulePatchCompleteTargetInURITest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont", jsonToPatchBodyReader, false);
-
- checkPatchContext(jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- JsonPatchBodyReaderTest.class.getResourceAsStream(
+ checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont",
+ JsonPatchBodyTest.class.getResourceAsStream(
"/instanceidentifier/json/jsonPATCHdataCompleteTargetInURI.json")));
}
*/
@Test
public final void modulePatchMergeOperationOnListTest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
- jsonToPatchBodyReader, false);
-
- checkPatchContext(jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- JsonPatchBodyReaderTest.class.getResourceAsStream(
+ checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
+ JsonPatchBodyTest.class.getResourceAsStream(
"/instanceidentifier/json/jsonPATCHMergeOperationOnList.json")));
}
*/
@Test
public final void modulePatchMergeOperationOnContainerTest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont", jsonToPatchBodyReader, false);
-
- checkPatchContext(jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, stringInputStream("""
+ checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont", """
{
"ietf-yang-patch:yang-patch" : {
"patch-id" : "Test merge operation",
}
]
}
- }""")));
+ }"""));
}
/**
*/
@Test
public final void modulePatchSimpleLeafValueTest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
- jsonToPatchBodyReader, false);
-
- final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- JsonPatchBodyReaderTest.class.getResourceAsStream(
- "/instanceidentifier/json/jsonPATCHSimpleLeafValue.json"));
+ final var returnValue = parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
+ JsonPatchBodyTest.class.getResourceAsStream("/instanceidentifier/json/jsonPATCHSimpleLeafValue.json"));
checkPatchContext(returnValue);
assertEquals(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf20"), returnValue.getData().get(0).getNode());
}
*/
@Test
public final void modulePatchTargetTopLevelContainerWithEmptyURITest() throws Exception {
- mockBodyReader(mountPrefix(), jsonToPatchBodyReader, false);
-
- checkPatchContext(jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- JsonPatchBodyReaderTest.class.getResourceAsStream(
+ checkPatchContext(parse(mountPrefix(),
+ JsonPatchBodyTest.class.getResourceAsStream(
"/instanceidentifier/json/jsonPATCHTargetTopLevelContainerWithEmptyURI.json")));
}
*/
@Test
public final void modulePatchTargetTopLevelContainerWithFullPathURITest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont", jsonToPatchBodyReader, false);
-
- final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, stringInputStream("""
+ final var returnValue = parse(mountPrefix() + "instance-identifier-patch-module:patch-cont", """
{
"ietf-yang-patch:yang-patch": {
"patch-id": "test-patch",
}
]
}
- }"""));
+ }""");
checkPatchContext(returnValue);
assertEquals(Builders.containerBuilder()
.withNodeIdentifier(new NodeIdentifier(PATCH_CONT_QNAME))
*/
@Test
public final void modulePatchTargetSecondLevelListWithFullPathURITest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=my-leaf-set",
- jsonToPatchBodyReader, false);
-
- final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, stringInputStream("""
+ final var returnValue = parse(
+ mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=my-leaf-set", """
{
"ietf-yang-patch:yang-patch": {
"patch-id": "test-patch",
}
]
}
- }"""));
+ }""");
checkPatchContext(returnValue);
assertEquals(Builders.mapBuilder()
.withNodeIdentifier(new NodeIdentifier(MY_LIST1_QNAME))
*/
@Test
public final void modulePatchTargetTopLevelAugmentedContainerTest() throws Exception {
- mockBodyReader(mountPrefix(), jsonToPatchBodyReader, false);
-
- final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, stringInputStream("""
+ final var returnValue = parse(mountPrefix(), """
{
"ietf-yang-patch:yang-patch": {
"patch-id": "test-patch",
}
]
}
- }"""));
+ }""");
checkPatchContext(returnValue);
assertEquals(Builders.containerBuilder()
.withNodeIdentifier(new NodeIdentifier(CONT_AUG_QNAME))
*/
@Test
public final void modulePatchTargetMapNodeTest() throws Exception {
- mockBodyReader(mountPrefix(), jsonToPatchBodyReader, false);
-
- final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, stringInputStream("""
+ final var returnValue = parse(mountPrefix(), """
{
"ietf-yang-patch:yang-patch": {
"patch-id": "map-patch",
}
]
}
- }"""));
+ }""");
checkPatchContext(returnValue);
assertEquals(Builders.mapBuilder()
.withNodeIdentifier(new NodeIdentifier(MAP_CONT_QNAME))
*/
@Test
public final void modulePatchTargetLeafSetNodeTest() throws Exception {
- mockBodyReader(mountPrefix() + "", jsonToPatchBodyReader, false);
-
- final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, stringInputStream("""
+ final var returnValue = parse(mountPrefix(), """
{
"ietf-yang-patch:yang-patch": {
"patch-id": "set-patch",
}
]
}
- }"""));
+ }""");
checkPatchContext(returnValue);
assertEquals(Builders.leafSetBuilder()
.withNodeIdentifier(new NodeIdentifier(LEAF_SET_QNAME))
*/
@Test
public final void modulePatchTargetUnkeyedListNodeTest() throws Exception {
- mockBodyReader(mountPrefix(), jsonToPatchBodyReader, false);
-
- final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, stringInputStream("""
+ final var returnValue = parse(mountPrefix(), """
{
"ietf-yang-patch:yang-patch": {
"patch-id": "list-patch",
}
]
}
- }"""));
+ }""");
checkPatchContext(returnValue);
assertEquals(Builders.unkeyedListBuilder()
.withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
*/
@Test
public final void modulePatchTargetCaseNodeTest() throws Exception {
- mockBodyReader(mountPrefix(), jsonToPatchBodyReader, false);
-
- final var returnValue = jsonToPatchBodyReader.readFrom(null, null, null, mediaType, null, stringInputStream("""
+ final var returnValue = parse(mountPrefix(), """
{
"ietf-yang-patch:yang-patch": {
"patch-id": "choice-patch",
}
]
}
- }"""));
+ }""");
checkPatchContext(returnValue);
assertEquals(Builders.containerBuilder()
.withNodeIdentifier(new NodeIdentifier(CHOICE_CONT_QNAME))
* 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.jersey.providers.patch;
+package org.opendaylight.restconf.nb.rfc8040.databind;
import org.opendaylight.mdsal.dom.api.DOMMountPoint;
-public class JsonPatchBodyReaderMountPointTest extends JsonPatchBodyReaderTest {
+public class XmlPatchBodyMountPointTest extends XmlPatchBodyTest {
@Override
String mountPrefix() {
return "instance-identifier-module:cont/yang-ext:mount/";
--- /dev/null
+/*
+ * 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.databind;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+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.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+
+public class XmlPatchBodyTest extends AbstractPatchBodyTest {
+ public XmlPatchBodyTest() {
+ super(XmlPatchBody::new);
+ }
+
+ @Test
+ public final void moduleDataTest() throws Exception {
+ checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
+ XmlPatchBodyTest.class.getResourceAsStream("/instanceidentifier/xml/xmlPATCHdata.xml")));
+ }
+
+ /**
+ * Test trying to use Patch create operation which requires value without value. Error code 400 should be returned.
+ */
+ @Test
+ public final void moduleDataValueMissingNegativeTest() throws Exception {
+ final var inputStream = XmlPatchBodyTest.class.getResourceAsStream(
+ "/instanceidentifier/xml/xmlPATCHdataValueMissing.xml");
+ final var ex = assertThrows(RestconfDocumentedException.class,
+ () -> parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", inputStream));
+ assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
+ }
+
+ /**
+ * Test trying to use value with Patch delete operation which does not support value. Error code 400 should be
+ * returned.
+ */
+ @Test
+ public final void moduleDataNotValueNotSupportedNegativeTest() throws Exception {
+ final var inputStream = XmlPatchBodyTest.class.getResourceAsStream(
+ "/instanceidentifier/xml/xmlPATCHdataValueNotSupported.xml");
+ final var ex = assertThrows(RestconfDocumentedException.class,
+ () -> parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", inputStream));
+ assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
+ }
+
+ /**
+ * Test of YANG Patch with absolute target path.
+ */
+ @Test
+ public final void moduleDataAbsoluteTargetPathTest() throws Exception {
+ checkPatchContext(parse(mountPrefix(), XmlPatchBodyTest.class.getResourceAsStream(
+ "/instanceidentifier/xml/xmlPATCHdataAbsoluteTargetPath.xml")));
+ }
+
+ /**
+ * Test using Patch when target is completely specified in request URI and thus target leaf contains only '/' sign.
+ */
+ @Test
+ public final void modulePatchCompleteTargetInURITest() throws Exception {
+ checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont",
+ XmlPatchBodyTest.class.getResourceAsStream("/instanceidentifier/xml/xmlPATCHdataCompleteTargetInURI.xml")));
+ }
+
+ /**
+ * Test of Yang Patch merge operation on list. Test consists of two edit operations - replace and merge.
+ */
+ @Test
+ public final void moduleDataMergeOperationOnListTest() throws Exception {
+ checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
+ XmlPatchBodyTest.class.getResourceAsStream(
+ "/instanceidentifier/xml/xmlPATCHdataMergeOperationOnList.xml")));
+ }
+
+ /**
+ * Test of Yang Patch merge operation on container. Test consists of two edit operations - create and merge.
+ */
+ @Test
+ public final void moduleDataMergeOperationOnContainerTest() throws Exception {
+ checkPatchContext(parse(mountPrefix() + "instance-identifier-patch-module:patch-cont",
+ XmlPatchBodyTest.class.getResourceAsStream(
+ "/instanceidentifier/xml/xmlPATCHdataMergeOperationOnContainer.xml")));
+ }
+
+ /**
+ * Test of Yang Patch on the top-level container with empty URI for data root.
+ */
+ @Test
+ public final void modulePatchTargetTopLevelContainerWithEmptyURITest() throws Exception {
+ checkPatchContext(parse(mountPrefix(),
+ XmlPatchBodyTest.class.getResourceAsStream(
+ "/instanceidentifier/xml/xmlPATCHTargetTopLevelContainerWithEmptyURI.xml")));
+ }
+
+ /**
+ * Test of YANG Patch on the top-level container with the full path in the URI and "/" in 'target'.
+ */
+ @Test
+ public final void modulePatchTargetTopLevelContainerWithFullPathURITest() throws Exception {
+ final var returnValue = parse(mountPrefix() + "instance-identifier-patch-module:patch-cont", """
+ <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+ <patch-id>test-patch</patch-id>
+ <comment>Test patch applied to the top-level container with '/' in target</comment>
+ <edit>
+ <edit-id>edit1</edit-id>
+ <operation>replace</operation>
+ <target>/</target>
+ <value>
+ <patch-cont xmlns="instance:identifier:patch:module">
+ <my-list1>
+ <name>my-leaf-set</name>
+ <my-leaf11>leaf-a</my-leaf11>
+ <my-leaf12>leaf-b</my-leaf12>
+ </my-list1>
+ </patch-cont>
+ </value>
+ </edit>
+ </yang-patch>""");
+ checkPatchContext(returnValue);
+ assertEquals(Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(PATCH_CONT_QNAME))
+ .withChild(Builders.mapBuilder()
+ .withNodeIdentifier(new NodeIdentifier(MY_LIST1_QNAME))
+ .withChild(Builders.mapEntryBuilder()
+ .withNodeIdentifier(NodeIdentifierWithPredicates.of(MY_LIST1_QNAME, LEAF_NAME_QNAME, "my-leaf-set"))
+ .withChild(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf-set"))
+ .withChild(ImmutableNodes.leafNode(MY_LEAF11_QNAME, "leaf-a"))
+ .withChild(ImmutableNodes.leafNode(MY_LEAF12_QNAME, "leaf-b"))
+ .build())
+ .build())
+ .build(), returnValue.getData().get(0).getNode());
+ }
+
+ /**
+ * Test of YANG Patch on the second-level list with the full path in the URI and "/" in 'target'.
+ */
+ @Test
+ public final void modulePatchTargetSecondLevelListWithFullPathURITest() throws Exception {
+ final var returnValue = parse(
+ mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=my-leaf-set",
+ """
+ <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+ <patch-id>test-patch</patch-id>
+ <comment>Test patch applied to the second-level list with '/' in target</comment>
+ <edit>
+ <edit-id>edit1</edit-id>
+ <operation>replace</operation>
+ <target>/</target>
+ <value>
+ <my-list1 xmlns="instance:identifier:patch:module">
+ <name>my-leaf-set</name>
+ <my-leaf11>leaf-a</my-leaf11>
+ <my-leaf12>leaf-b</my-leaf12>
+ </my-list1>
+ </value>
+ </edit>
+ </yang-patch>""");
+ checkPatchContext(returnValue);
+ assertEquals(Builders.mapBuilder()
+ .withNodeIdentifier(new NodeIdentifier(MY_LIST1_QNAME))
+ .withChild(Builders.mapEntryBuilder()
+ .withNodeIdentifier(NodeIdentifierWithPredicates.of(MY_LIST1_QNAME, LEAF_NAME_QNAME, "my-leaf-set"))
+ .withChild(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf-set"))
+ .withChild(ImmutableNodes.leafNode(MY_LEAF11_QNAME, "leaf-a"))
+ .withChild(ImmutableNodes.leafNode(MY_LEAF12_QNAME, "leaf-b"))
+ .build())
+ .build(), returnValue.getData().get(0).getNode());
+ }
+
+ /**
+ * Test of Yang Patch on the top augmented element.
+ */
+ @Test
+ public final void moduleTargetTopLevelAugmentedContainerTest() throws Exception {
+ final var returnValue = parse(mountPrefix(), """
+ <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+ <patch-id>test-patch</patch-id>
+ <comment>This test patch for augmented element</comment>
+ <edit>
+ <edit-id>edit1</edit-id>
+ <operation>replace</operation>
+ <target>/test-m:container-root/test-m:container-lvl1/test-m-aug:container-aug</target>
+ <value>
+ <container-aug xmlns="test-ns-aug">
+ <leaf-aug>data</leaf-aug>
+ </container-aug>
+ </value>
+ </edit>
+ </yang-patch>""");
+ checkPatchContext(returnValue);
+ assertEquals(Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(CONT_AUG_QNAME))
+ .withChild(ImmutableNodes.leafNode(LEAF_AUG_QNAME, "data"))
+ .build(), returnValue.getData().get(0).getNode());
+ }
+
+ /**
+ * Test of YANG Patch on the top system map node element.
+ */
+ @Test
+ public final void moduleTargetMapNodeTest() throws Exception {
+ final var returnValue = parse(mountPrefix(), """
+ <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+ <patch-id>map-patch</patch-id>
+ <comment>YANG patch comment</comment>
+ <edit>
+ <edit-id>edit1</edit-id>
+ <operation>replace</operation>
+ <target>/map-model:cont-root/map-model:cont1/map-model:my-map=key</target>
+ <value>
+ <my-map xmlns="map:ns">
+ <key-leaf>key</key-leaf>
+ <data-leaf>data</data-leaf>
+ </my-map>
+ </value>
+ </edit>
+ </yang-patch>""");
+ checkPatchContext(returnValue);
+ assertEquals(Builders.mapBuilder()
+ .withNodeIdentifier(new NodeIdentifier(MAP_CONT_QNAME))
+ .withChild(Builders.mapEntryBuilder()
+ .withNodeIdentifier(NodeIdentifierWithPredicates.of(MAP_CONT_QNAME, KEY_LEAF_QNAME, "key"))
+ .withChild(ImmutableNodes.leafNode(KEY_LEAF_QNAME, "key"))
+ .withChild(ImmutableNodes.leafNode(DATA_LEAF_QNAME, "data"))
+ .build())
+ .build(), returnValue.getData().get(0).getNode());
+ }
+
+ /**
+ * Test of YANG Patch on the leaf set node element.
+ */
+ @Test
+ public final void modulePatchTargetLeafSetNodeTest() throws Exception {
+ final var returnValue = parse(mountPrefix(), """
+ <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+ <patch-id>set-patch</patch-id>
+ <comment>YANG patch comment</comment>
+ <edit>
+ <edit-id>edit1</edit-id>
+ <operation>replace</operation>
+ <target>/set-model:cont-root/set-model:cont1/set-model:my-set="data1"</target>
+ <value>
+ <my-set xmlns="set:ns">data1</my-set>
+ </value>
+ </edit>
+ </yang-patch>""");
+ checkPatchContext(returnValue);
+ assertEquals(Builders.leafSetBuilder()
+ .withNodeIdentifier(new NodeIdentifier(LEAF_SET_QNAME))
+ .withChild(Builders.leafSetEntryBuilder()
+ .withNodeIdentifier(new NodeWithValue<>(LEAF_SET_QNAME, "data1"))
+ .withValue("data1")
+ .build())
+ .build(), returnValue.getData().get(0).getNode());
+ }
+
+ /**
+ * Test of Yang Patch on the top unkeyed list element.
+ */
+ @Test
+ public final void moduleTargetUnkeyedListNodeTest() throws Exception {
+ final var returnValue = parse(mountPrefix(), """
+ <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+ <patch-id>list-patch</patch-id>
+ <comment>YANG patch comment</comment>
+ <edit>
+ <edit-id>edit1</edit-id>
+ <operation>replace</operation>
+ <target>/list-model:cont-root/list-model:cont1/list-model:unkeyed-list</target>
+ <value>
+ <unkeyed-list xmlns="list:ns">
+ <leaf1>data1</leaf1>
+ <leaf2>data2</leaf2>
+ </unkeyed-list>
+ </value>
+ </edit>
+ </yang-patch>""");
+ checkPatchContext(returnValue);
+ assertEquals(Builders.unkeyedListBuilder()
+ .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
+ .withChild(Builders.unkeyedListEntryBuilder()
+ .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
+ .withChild(ImmutableNodes.leafNode(LIST_LEAF1_QNAME, "data1"))
+ .withChild(ImmutableNodes.leafNode(LIST_LEAF2_QNAME, "data2"))
+ .build())
+ .build(), returnValue.getData().get(0).getNode());
+ }
+
+ /**
+ * Test of Yang Patch on the top case node element.
+ */
+ @Test
+ public final void moduleTargetCaseNodeTest() throws Exception {
+ final var returnValue = parse(mountPrefix(), """
+ <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+ <patch-id>choice-patch</patch-id>
+ <comment>YANG patch comment</comment>
+ <edit>
+ <edit-id>edit1</edit-id>
+ <operation>replace</operation>
+ <target>/choice-model:cont-root/choice-model:cont1/choice-model:case-cont1</target>
+ <value>
+ <case-cont1 xmlns="choice:ns">
+ <case-leaf1>data</case-leaf1>
+ </case-cont1>
+ </value>
+ </edit>
+ </yang-patch>""");
+ checkPatchContext(returnValue);
+ assertEquals(Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(CHOICE_CONT_QNAME))
+ .withChild(ImmutableNodes.leafNode(CASE_LEAF1_QNAME, "data"))
+ .build(), returnValue.getData().get(0).getNode());
+ }
+
+ /**
+ * Test reading simple leaf value.
+ */
+ @Test
+ public final void modulePatchSimpleLeafValueTest() throws Exception {
+ final var returnValue = parse(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1", """
+ <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+ <patch-id>test-patch</patch-id>
+ <comment>this is test patch</comment>
+ <edit>
+ <edit-id>edit1</edit-id>
+ <operation>replace</operation>
+ <target>/my-list2=my-leaf20/name</target>
+ <value>
+ <name xmlns="instance:identifier:patch:module">my-leaf20</name>
+ </value>
+ </edit>
+ </yang-patch>""");
+ checkPatchContext(returnValue);
+ assertEquals(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf20"), returnValue.getData().get(0).getNode());
+ }
+}
+++ /dev/null
-/*
- * Copyright (c) 2023 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.jersey.providers.patch;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.mdsal.dom.api.DOMMountPoint;
-import org.opendaylight.restconf.common.patch.PatchContext;
-import org.opendaylight.restconf.nb.rfc8040.jersey.providers.test.AbstractBodyReaderTest;
-
-abstract class AbstractPatchBodyReaderTest extends AbstractBodyReaderTest {
-
- @NonNull String mountPrefix() {
- return "";
- }
-
- @Nullable DOMMountPoint mountPoint() {
- return null;
- }
-
- final void checkPatchContext(final PatchContext patchContext) {
- assertNotNull(patchContext.getData());
-
- final var iid = patchContext.getInstanceIdentifierContext();
- assertNotNull(iid);
-
- assertNotNull(iid.getInstanceIdentifier());
- assertNotNull(iid.getSchemaContext());
- assertNotNull(iid.getSchemaNode());
- assertSame(mountPoint(), iid.getMountPoint());
- }
-
- static final @NonNull InputStream stringInputStream(final String str) {
- return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
- }
-}
+++ /dev/null
-/*
- * 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.jersey.providers.patch;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
-
-import javax.ws.rs.core.MediaType;
-import org.junit.Test;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.yangtools.yang.common.ErrorTag;
-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.YangInstanceIdentifier.NodeWithValue;
-import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
-import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-
-public class XmlPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
- private final XmlPatchBodyReader xmlToPatchBodyReader = new XmlPatchBodyReader(databindProvider, mountPointService);
-
- @Override
- protected final MediaType getMediaType() {
- return new MediaType(MediaType.APPLICATION_XML, null);
- }
-
- @Test
- public final void moduleDataTest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
- xmlToPatchBodyReader, false);
-
- checkPatchContext(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- XmlPatchBodyReaderTest.class.getResourceAsStream("/instanceidentifier/xml/xmlPATCHdata.xml")));
- }
-
- /**
- * Test trying to use Patch create operation which requires value without value. Error code 400 should be returned.
- */
- @Test
- public final void moduleDataValueMissingNegativeTest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
- xmlToPatchBodyReader, false);
-
- final var inputStream = XmlPatchBodyReaderTest.class.getResourceAsStream(
- "/instanceidentifier/xml/xmlPATCHdataValueMissing.xml");
- final var ex = assertThrows(RestconfDocumentedException.class,
- () -> xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream));
- assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
- }
-
- /**
- * Test trying to use value with Patch delete operation which does not support value. Error code 400 should be
- * returned.
- */
- @Test
- public final void moduleDataNotValueNotSupportedNegativeTest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
- xmlToPatchBodyReader, false);
-
- final var inputStream = XmlPatchBodyReaderTest.class.getResourceAsStream(
- "/instanceidentifier/xml/xmlPATCHdataValueNotSupported.xml");
- final var ex = assertThrows(RestconfDocumentedException.class,
- () -> xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null, inputStream));
- assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
- }
-
- /**
- * Test of YANG Patch with absolute target path.
- */
- @Test
- public final void moduleDataAbsoluteTargetPathTest() throws Exception {
- mockBodyReader(mountPrefix(), xmlToPatchBodyReader, false);
-
- checkPatchContext(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- XmlPatchBodyReaderTest.class.getResourceAsStream(
- "/instanceidentifier/xml/xmlPATCHdataAbsoluteTargetPath.xml")));
- }
-
- /**
- * Test using Patch when target is completely specified in request URI and thus target leaf contains only '/' sign.
- */
- @Test
- public final void modulePatchCompleteTargetInURITest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont", xmlToPatchBodyReader, false);
-
- checkPatchContext(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- XmlPatchBodyReaderTest.class.getResourceAsStream(
- "/instanceidentifier/xml/xmlPATCHdataCompleteTargetInURI.xml")));
- }
-
- /**
- * Test of Yang Patch merge operation on list. Test consists of two edit operations - replace and merge.
- */
- @Test
- public final void moduleDataMergeOperationOnListTest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
- xmlToPatchBodyReader, false);
-
- checkPatchContext(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- XmlPatchBodyReaderTest.class.getResourceAsStream(
- "/instanceidentifier/xml/xmlPATCHdataMergeOperationOnList.xml")));
- }
-
- /**
- * Test of Yang Patch merge operation on container. Test consists of two edit operations - create and merge.
- */
- @Test
- public final void moduleDataMergeOperationOnContainerTest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont", xmlToPatchBodyReader, false);
-
- checkPatchContext(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- XmlPatchBodyReaderTest.class.getResourceAsStream(
- "/instanceidentifier/xml/xmlPATCHdataMergeOperationOnContainer.xml")));
- }
-
- /**
- * Test of Yang Patch on the top-level container with empty URI for data root.
- */
- @Test
- public final void modulePatchTargetTopLevelContainerWithEmptyURITest() throws Exception {
- mockBodyReader(mountPrefix(), xmlToPatchBodyReader, false);
-
- checkPatchContext(xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- XmlPatchBodyReaderTest.class.getResourceAsStream(
- "/instanceidentifier/xml/xmlPATCHTargetTopLevelContainerWithEmptyURI.xml")));
- }
-
- /**
- * Test of YANG Patch on the top-level container with the full path in the URI and "/" in 'target'.
- */
- @Test
- public final void modulePatchTargetTopLevelContainerWithFullPathURITest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont", xmlToPatchBodyReader, false);
-
- final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- stringInputStream("""
- <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
- <patch-id>test-patch</patch-id>
- <comment>Test patch applied to the top-level container with '/' in target</comment>
- <edit>
- <edit-id>edit1</edit-id>
- <operation>replace</operation>
- <target>/</target>
- <value>
- <patch-cont xmlns="instance:identifier:patch:module">
- <my-list1>
- <name>my-leaf-set</name>
- <my-leaf11>leaf-a</my-leaf11>
- <my-leaf12>leaf-b</my-leaf12>
- </my-list1>
- </patch-cont>
- </value>
- </edit>
- </yang-patch>"""));
- checkPatchContext(returnValue);
- assertEquals(Builders.containerBuilder()
- .withNodeIdentifier(new NodeIdentifier(PATCH_CONT_QNAME))
- .withChild(Builders.mapBuilder()
- .withNodeIdentifier(new NodeIdentifier(MY_LIST1_QNAME))
- .withChild(Builders.mapEntryBuilder()
- .withNodeIdentifier(NodeIdentifierWithPredicates.of(MY_LIST1_QNAME, LEAF_NAME_QNAME, "my-leaf-set"))
- .withChild(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf-set"))
- .withChild(ImmutableNodes.leafNode(MY_LEAF11_QNAME, "leaf-a"))
- .withChild(ImmutableNodes.leafNode(MY_LEAF12_QNAME, "leaf-b"))
- .build())
- .build())
- .build(), returnValue.getData().get(0).getNode());
- }
-
- /**
- * Test of YANG Patch on the second-level list with the full path in the URI and "/" in 'target'.
- */
- @Test
- public final void modulePatchTargetSecondLevelListWithFullPathURITest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=my-leaf-set",
- xmlToPatchBodyReader, false);
-
- final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- stringInputStream("""
- <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
- <patch-id>test-patch</patch-id>
- <comment>Test patch applied to the second-level list with '/' in target</comment>
- <edit>
- <edit-id>edit1</edit-id>
- <operation>replace</operation>
- <target>/</target>
- <value>
- <my-list1 xmlns="instance:identifier:patch:module">
- <name>my-leaf-set</name>
- <my-leaf11>leaf-a</my-leaf11>
- <my-leaf12>leaf-b</my-leaf12>
- </my-list1>
- </value>
- </edit>
- </yang-patch>
- """));
- checkPatchContext(returnValue);
- assertEquals(Builders.mapBuilder()
- .withNodeIdentifier(new NodeIdentifier(MY_LIST1_QNAME))
- .withChild(Builders.mapEntryBuilder()
- .withNodeIdentifier(NodeIdentifierWithPredicates.of(MY_LIST1_QNAME, LEAF_NAME_QNAME, "my-leaf-set"))
- .withChild(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf-set"))
- .withChild(ImmutableNodes.leafNode(MY_LEAF11_QNAME, "leaf-a"))
- .withChild(ImmutableNodes.leafNode(MY_LEAF12_QNAME, "leaf-b"))
- .build())
- .build(), returnValue.getData().get(0).getNode());
- }
-
- /**
- * Test of Yang Patch on the top augmented element.
- */
- @Test
- public final void moduleTargetTopLevelAugmentedContainerTest() throws Exception {
- mockBodyReader(mountPrefix(), xmlToPatchBodyReader, false);
-
- final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- stringInputStream("""
- <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
- <patch-id>test-patch</patch-id>
- <comment>This test patch for augmented element</comment>
- <edit>
- <edit-id>edit1</edit-id>
- <operation>replace</operation>
- <target>/test-m:container-root/test-m:container-lvl1/test-m-aug:container-aug</target>
- <value>
- <container-aug xmlns="test-ns-aug">
- <leaf-aug>data</leaf-aug>
- </container-aug>
- </value>
- </edit>
- </yang-patch>"""));
- checkPatchContext(returnValue);
- assertEquals(Builders.containerBuilder()
- .withNodeIdentifier(new NodeIdentifier(CONT_AUG_QNAME))
- .withChild(ImmutableNodes.leafNode(LEAF_AUG_QNAME, "data"))
- .build(), returnValue.getData().get(0).getNode());
- }
-
- /**
- * Test of YANG Patch on the top system map node element.
- */
- @Test
- public final void moduleTargetMapNodeTest() throws Exception {
- mockBodyReader(mountPrefix(), xmlToPatchBodyReader, false);
-
- final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- stringInputStream("""
- <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
- <patch-id>map-patch</patch-id>
- <comment>YANG patch comment</comment>
- <edit>
- <edit-id>edit1</edit-id>
- <operation>replace</operation>
- <target>/map-model:cont-root/map-model:cont1/map-model:my-map=key</target>
- <value>
- <my-map xmlns="map:ns">
- <key-leaf>key</key-leaf>
- <data-leaf>data</data-leaf>
- </my-map>
- </value>
- </edit>
- </yang-patch>"""));
- checkPatchContext(returnValue);
- assertEquals(Builders.mapBuilder()
- .withNodeIdentifier(new NodeIdentifier(MAP_CONT_QNAME))
- .withChild(Builders.mapEntryBuilder()
- .withNodeIdentifier(NodeIdentifierWithPredicates.of(MAP_CONT_QNAME, KEY_LEAF_QNAME, "key"))
- .withChild(ImmutableNodes.leafNode(KEY_LEAF_QNAME, "key"))
- .withChild(ImmutableNodes.leafNode(DATA_LEAF_QNAME, "data"))
- .build())
- .build(), returnValue.getData().get(0).getNode());
- }
-
- /**
- * Test of YANG Patch on the leaf set node element.
- */
- @Test
- public final void modulePatchTargetLeafSetNodeTest() throws Exception {
- mockBodyReader(mountPrefix(), xmlToPatchBodyReader, false);
-
- final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- stringInputStream("""
- <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
- <patch-id>set-patch</patch-id>
- <comment>YANG patch comment</comment>
- <edit>
- <edit-id>edit1</edit-id>
- <operation>replace</operation>
- <target>/set-model:cont-root/set-model:cont1/set-model:my-set="data1"</target>
- <value>
- <my-set xmlns="set:ns">data1</my-set>
- </value>
- </edit>
- </yang-patch>"""));
- checkPatchContext(returnValue);
- assertEquals(Builders.leafSetBuilder()
- .withNodeIdentifier(new NodeIdentifier(LEAF_SET_QNAME))
- .withChild(Builders.leafSetEntryBuilder()
- .withNodeIdentifier(new NodeWithValue<>(LEAF_SET_QNAME, "data1"))
- .withValue("data1")
- .build())
- .build(), returnValue.getData().get(0).getNode());
- }
-
- /**
- * Test of Yang Patch on the top unkeyed list element.
- */
- @Test
- public final void moduleTargetUnkeyedListNodeTest() throws Exception {
- mockBodyReader(mountPrefix(), xmlToPatchBodyReader, false);
-
- final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- stringInputStream("""
- <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
- <patch-id>list-patch</patch-id>
- <comment>YANG patch comment</comment>
- <edit>
- <edit-id>edit1</edit-id>
- <operation>replace</operation>
- <target>/list-model:cont-root/list-model:cont1/list-model:unkeyed-list</target>
- <value>
- <unkeyed-list xmlns="list:ns">
- <leaf1>data1</leaf1>
- <leaf2>data2</leaf2>
- </unkeyed-list>
- </value>
- </edit>
- </yang-patch>"""));
- checkPatchContext(returnValue);
- assertEquals(Builders.unkeyedListBuilder()
- .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
- .withChild(Builders.unkeyedListEntryBuilder()
- .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
- .withChild(ImmutableNodes.leafNode(LIST_LEAF1_QNAME, "data1"))
- .withChild(ImmutableNodes.leafNode(LIST_LEAF2_QNAME, "data2"))
- .build())
- .build(), returnValue.getData().get(0).getNode());
- }
-
- /**
- * Test of Yang Patch on the top case node element.
- */
- @Test
- public final void moduleTargetCaseNodeTest() throws Exception {
- mockBodyReader(mountPrefix(), xmlToPatchBodyReader, false);
-
- final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- stringInputStream("""
- <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
- <patch-id>choice-patch</patch-id>
- <comment>YANG patch comment</comment>
- <edit>
- <edit-id>edit1</edit-id>
- <operation>replace</operation>
- <target>/choice-model:cont-root/choice-model:cont1/choice-model:case-cont1</target>
- <value>
- <case-cont1 xmlns="choice:ns">
- <case-leaf1>data</case-leaf1>
- </case-cont1>
- </value>
- </edit>
- </yang-patch>"""));
- checkPatchContext(returnValue);
- assertEquals(Builders.containerBuilder()
- .withNodeIdentifier(new NodeIdentifier(CHOICE_CONT_QNAME))
- .withChild(ImmutableNodes.leafNode(CASE_LEAF1_QNAME, "data"))
- .build(), returnValue.getData().get(0).getNode());
- }
-
- /**
- * Test reading simple leaf value.
- */
- @Test
- public final void modulePatchSimpleLeafValueTest() throws Exception {
- mockBodyReader(mountPrefix() + "instance-identifier-patch-module:patch-cont/my-list1=leaf1",
- xmlToPatchBodyReader, false);
-
- final var returnValue = xmlToPatchBodyReader.readFrom(null, null, null, mediaType, null,
- stringInputStream("""
- <yang-patch xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
- <patch-id>test-patch</patch-id>
- <comment>this is test patch</comment>
- <edit>
- <edit-id>edit1</edit-id>
- <operation>replace</operation>
- <target>/my-list2=my-leaf20/name</target>
- <value>
- <name xmlns="instance:identifier:patch:module">my-leaf20</name>
- </value>
- </edit>
- </yang-patch>"""));
- checkPatchContext(returnValue);
- assertEquals(ImmutableNodes.leafNode(LEAF_NAME_QNAME, "my-leaf20"), returnValue.getData().get(0).getNode());
- }
-}
.when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
doReturn(immediateTrueFluentFuture())
.when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
- final PatchStatusContext status = dataService.yangPatchData(patch);
+ final PatchStatusContext status = dataService.yangPatchData(iidContext, patch);
assertTrue(status.ok());
assertEquals(3, status.editCollection().size());
assertEquals("replace data", status.editCollection().get(1).getEditId());
.when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
doReturn(immediateTrueFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
- final PatchStatusContext status = dataService.yangPatchData(patch);
+ final PatchStatusContext status = dataService.yangPatchData(iidContext, patch);
assertTrue(status.ok());
assertEquals(3, status.editCollection().size());
assertNull(status.globalErrors());
doReturn(immediateFalseFluentFuture())
.when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
doReturn(true).when(readWrite).cancel();
- final PatchStatusContext status = dataService.yangPatchData(patch);
+ final PatchStatusContext status = dataService.yangPatchData(iidContext, patch);
assertFalse(status.ok());
assertEquals(3, status.editCollection().size());