Eliminate AbstractPatchBodyReader 47/107447/16
authorRobert Varga <robert.varga@pantheon.tech>
Thu, 17 Aug 2023 20:44:59 +0000 (22:44 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Sat, 26 Aug 2023 09:29:37 +0000 (11:29 +0200)
This patch refactors RestconfDataServiceImpl to not rely on JAX-RS for
parsing YANG Patch body. Instead of that we introduce PatchBody backed
by an InputStream and two specializations to handle parsing from
JSON/XML.

JIRA: NETCONF-1128
Change-Id: Ia7363d641ac8e3c3719bb19e8a311a9452f79a86
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
14 files changed:
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/AbstractRestconfApplication.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/JsonPatchBody.java [moved from restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/JsonPatchBodyReader.java with 82% similarity]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/PatchBody.java [new file with mode: 0644]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/XmlPatchBody.java [moved from restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/XmlPatchBodyReader.java with 77% similarity]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/AbstractPatchBodyReader.java [deleted file]
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/AbstractPatchBodyTest.java [new file with mode: 0644]
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/JsonPatchBodyMountPointTest.java [moved from restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/XmlPatchBodyReaderMountPointTest.java with 79% similarity]
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/JsonPatchBodyTest.java [moved from restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/JsonPatchBodyReaderTest.java with 79% similarity]
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/XmlPatchBodyMountPointTest.java [moved from restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/JsonPatchBodyReaderMountPointTest.java with 78% similarity]
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/XmlPatchBodyTest.java [new file with mode: 0644]
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/AbstractPatchBodyReaderTest.java [deleted file]
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/XmlPatchBodyReaderTest.java [deleted file]
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImplTest.java

index a0c51d1306b4d2c01eb9e4b9e0e643dd86d70f4d..a309d2be00c0c80e36a91d7e50c59c66adb0f090 100644 (file)
@@ -22,9 +22,7 @@ import org.opendaylight.restconf.nb.rfc8040.jersey.providers.XmlNormalizedNodeBo
 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;
 
 /**
@@ -55,9 +53,7 @@ abstract class AbstractRestconfApplication extends Application {
         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();
     }
similarity index 82%
rename from restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/JsonPatchBodyReader.java
rename to restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/JsonPatchBody.java
index 71bbe2a228e3e696ffc846da90e18ca97e216b37..c3e4493e186fbc7f182ce0dd5640d4b98f95d0b2 100644 (file)
@@ -1,17 +1,17 @@
 /*
  * 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;
@@ -21,17 +21,11 @@ import java.io.InputStreamReader;
 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;
@@ -42,63 +36,31 @@ import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
 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()) {
@@ -150,10 +112,10 @@ public class JsonPatchBodyReader extends AbstractPatchBodyReader {
      * @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) {
@@ -191,8 +153,8 @@ public class JsonPatchBodyReader extends AbstractPatchBodyReader {
      * @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();
 
@@ -292,7 +254,7 @@ public class JsonPatchBodyReader extends AbstractPatchBodyReader {
      * @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('"');
@@ -346,8 +308,8 @@ public class JsonPatchBodyReader extends AbstractPatchBodyReader {
      * @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()),
@@ -363,7 +325,7 @@ public class JsonPatchBodyReader extends AbstractPatchBodyReader {
      */
     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());
             }
@@ -451,4 +413,4 @@ public class JsonPatchBodyReader extends AbstractPatchBodyReader {
             data = null;
         }
     }
-}
+}
\ No newline at end of file
diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/PatchBody.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/PatchBody.java
new file mode 100644 (file)
index 0000000..1befc5b
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * 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);
+    }
+}
similarity index 77%
rename from restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/XmlPatchBodyReader.java
rename to restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/XmlPatchBody.java
index 18667f237d802a3ff221ea045ed191bb3a8abdf6..51884ed0987f01ea56be2eeba4dd0ab00615f9fa 100644 (file)
@@ -1,11 +1,12 @@
 /*
  * 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;
@@ -13,19 +14,13 @@ import java.io.InputStream;
 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;
@@ -43,37 +38,31 @@ import org.w3c.dom.Element;
 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);
@@ -85,10 +74,9 @@ public class XmlPatchBodyReader extends AbstractPatchBodyReader {
             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();
@@ -114,7 +102,7 @@ public class XmlPatchBodyReader extends AbstractPatchBodyReader {
             }
         }
 
-        return new PatchContext(pathContext, ImmutableList.copyOf(resultCollection), patchId);
+        return new PatchContext(targetResource, ImmutableList.copyOf(resultCollection), patchId);
     }
 
     /**
@@ -152,4 +140,4 @@ public class XmlPatchBodyReader extends AbstractPatchBodyReader {
 
         return result;
     }
-}
+}
\ No newline at end of file
diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/AbstractPatchBodyReader.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/AbstractPatchBodyReader.java
deleted file mode 100644 (file)
index 5ab7a75..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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;
-        };
-    }
-}
index f55817e08092f3eb6f66bc9af9fb64e7c0211eab..3280dfdb9fcdaca8d0c6380caef2d7856fd4cddf 100644 (file)
@@ -19,6 +19,8 @@ import com.google.common.annotations.VisibleForTesting;
 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;
@@ -45,6 +47,7 @@ import javax.ws.rs.core.MediaType;
 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;
@@ -63,6 +66,9 @@ import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
 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;
@@ -469,47 +475,102 @@ public final class RestconfDataServiceImpl {
      * <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
diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/AbstractPatchBodyTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/AbstractPatchBodyTest.java
new file mode 100644 (file)
index 0000000..072acd3
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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));
+    }
+}
@@ -5,11 +5,11 @@
  * 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/";
@@ -5,13 +5,11 @@
  * 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;
@@ -21,21 +19,14 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithV
 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",
@@ -68,7 +59,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
                   }
                 ]
               }
-            }""")));
+            }"""));
     }
 
     /**
@@ -76,10 +67,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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",
@@ -110,7 +98,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
                   }
                 ]
               }
-            }""")));
+            }"""));
     }
 
     /**
@@ -119,11 +107,8 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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",
@@ -137,7 +122,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
                       }
                     ]
                   }
-                }""")));
+                }"""));
         assertEquals(ErrorTag.MALFORMED_MESSAGE, ex.getErrors().get(0).getErrorTag());
     }
 
@@ -147,14 +132,10 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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());
     }
 
@@ -163,10 +144,8 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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")));
     }
 
@@ -175,11 +154,8 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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")));
     }
 
@@ -188,9 +164,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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",
@@ -233,7 +207,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
                   }
                 ]
               }
-            }""")));
+            }"""));
     }
 
     /**
@@ -241,12 +215,8 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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());
     }
@@ -256,10 +226,8 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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")));
     }
 
@@ -268,9 +236,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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",
@@ -294,7 +260,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
                   }
                 ]
               }
-            }"""));
+            }""");
         checkPatchContext(returnValue);
         assertEquals(Builders.containerBuilder()
             .withNodeIdentifier(new NodeIdentifier(PATCH_CONT_QNAME))
@@ -315,10 +281,8 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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",
@@ -340,7 +304,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
                   }
                 ]
               }
-            }"""));
+            }""");
         checkPatchContext(returnValue);
         assertEquals(Builders.mapBuilder()
             .withNodeIdentifier(new NodeIdentifier(MY_LIST1_QNAME))
@@ -359,9 +323,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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",
@@ -379,7 +341,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
                         }
                     ]
                 }
-            }"""));
+            }""");
         checkPatchContext(returnValue);
         assertEquals(Builders.containerBuilder()
             .withNodeIdentifier(new NodeIdentifier(CONT_AUG_QNAME))
@@ -392,9 +354,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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",
@@ -413,7 +373,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
                         }
                     ]
                 }
-            }"""));
+            }""");
         checkPatchContext(returnValue);
         assertEquals(Builders.mapBuilder()
             .withNodeIdentifier(new NodeIdentifier(MAP_CONT_QNAME))
@@ -430,9 +390,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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",
@@ -448,7 +406,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
                         }
                     ]
                 }
-            }"""));
+            }""");
         checkPatchContext(returnValue);
         assertEquals(Builders.leafSetBuilder()
             .withNodeIdentifier(new NodeIdentifier(LEAF_SET_QNAME))
@@ -464,9 +422,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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",
@@ -485,7 +441,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
                         }
                     ]
                 }
-            }"""));
+            }""");
         checkPatchContext(returnValue);
         assertEquals(Builders.unkeyedListBuilder()
             .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
@@ -502,9 +458,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
      */
     @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",
@@ -522,7 +476,7 @@ public class JsonPatchBodyReaderTest extends AbstractPatchBodyReaderTest {
                         }
                     ]
                 }
-            }"""));
+            }""");
         checkPatchContext(returnValue);
         assertEquals(Builders.containerBuilder()
             .withNodeIdentifier(new NodeIdentifier(CHOICE_CONT_QNAME))
@@ -5,11 +5,11 @@
  * 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/";
diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/XmlPatchBodyTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/XmlPatchBodyTest.java
new file mode 100644 (file)
index 0000000..e1ba53d
--- /dev/null
@@ -0,0 +1,348 @@
+/*
+ * 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());
+    }
+}
diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/AbstractPatchBodyReaderTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/AbstractPatchBodyReaderTest.java
deleted file mode 100644 (file)
index d4f9230..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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));
-    }
-}
diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/XmlPatchBodyReaderTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/patch/XmlPatchBodyReaderTest.java
deleted file mode 100644 (file)
index c48b274..0000000
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * 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());
-    }
-}
index 7764d17280938e365368771fe9b3aa300f7e71f9..6fd1447ff1ecd729ce5c0878661f2833ce79933d 100644 (file)
@@ -425,7 +425,7 @@ public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
                 .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());
@@ -445,7 +445,7 @@ public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
                 .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());
@@ -465,7 +465,7 @@ public class RestconfDataServiceImplTest extends AbstractJukeboxTest {
         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());