This is a minimal capture of state we need, allowing us to reuse codecs.
Change-Id: Iefa54fef563ab1adac3a0cedd166d5aedbd45588
JIRA: NETCONF-1157
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
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.server.api.DataPatchPath;
import org.opendaylight.restconf.server.api.DatabindContext;
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.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-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.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.api.SchemaNode;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
}
@Override
- PatchContext toPatchContext(final DatabindContext databind, final YangInstanceIdentifier urlPath,
- final InputStream inputStream) throws IOException {
+ PatchContext toPatchContext(final DataPatchPath path, final InputStream inputStream) throws IOException {
try (var jsonReader = new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
final var patchId = new AtomicReference<String>();
- final var resultList = read(jsonReader, databind, urlPath, patchId);
+ final var resultList = read(jsonReader, path, patchId);
// Note: patchId side-effect of above
return new PatchContext(patchId.get(), resultList);
}
}
- private static ImmutableList<PatchEntity> read(final JsonReader in, final DatabindContext databind,
- final YangInstanceIdentifier urlPath, final AtomicReference<String> patchId) throws IOException {
+ private static ImmutableList<PatchEntity> read(final JsonReader in, final DataPatchPath path,
+ final AtomicReference<String> patchId) throws IOException {
final var edits = ImmutableList.<PatchEntity>builder();
final var edit = new PatchEdit();
case END_DOCUMENT:
break;
case NAME:
- parseByName(in.nextName(), edit, in, urlPath, databind, edits, patchId);
+ parseByName(in.nextName(), edit, in, path, edits, patchId);
break;
case END_OBJECT:
in.endObject();
// Switch value of parsed JsonToken.NAME and read edit definition or patch id
private static void parseByName(final @NonNull String name, final @NonNull PatchEdit edit,
- final @NonNull JsonReader in, final @NonNull YangInstanceIdentifier urlPath,
- final @NonNull DatabindContext databind, final @NonNull Builder<PatchEntity> resultCollection,
- final @NonNull AtomicReference<String> patchId) throws IOException {
+ final @NonNull JsonReader in, final @NonNull DataPatchPath path,
+ final @NonNull Builder<PatchEntity> resultCollection, final @NonNull AtomicReference<String> patchId)
+ throws IOException {
switch (name) {
case "edit":
if (in.peek() == JsonToken.BEGIN_ARRAY) {
in.beginArray();
while (in.hasNext()) {
- readEditDefinition(edit, in, urlPath, databind);
+ readEditDefinition(edit, in, path);
resultCollection.add(prepareEditOperation(edit));
edit.clear();
}
in.endArray();
} else {
- readEditDefinition(edit, in, urlPath, databind);
+ readEditDefinition(edit, in, path);
resultCollection.add(prepareEditOperation(edit));
edit.clear();
}
// Read one patch edit object from JSON input
private static void readEditDefinition(final @NonNull PatchEdit edit, final @NonNull JsonReader in,
- final @NonNull YangInstanceIdentifier urlPath, final @NonNull DatabindContext databind)
- throws IOException {
+ final @NonNull DataPatchPath path) throws IOException {
String deferredValue = null;
in.beginObject();
break;
case "target":
// target can be specified completely in request URI
- edit.setTarget(parsePatchTarget(databind, urlPath, in.nextString()));
- final var stack = databind.schemaTree().enterPath(edit.getTarget()).orElseThrow().stack();
+ edit.setTarget(parsePatchTarget(path, in.nextString()));
+ final var stack = path.databind().schemaTree().enterPath(edit.getTarget()).orElseThrow().stack();
if (!stack.isEmpty()) {
stack.exit();
}
deferredValue = readValueNode(in);
} else {
// We have a target schema node, reuse this reader without buffering the value.
- edit.setData(readEditData(in, edit.getTargetSchemaNode(), databind.modelContext()));
+ edit.setData(readEditData(in, edit.getTargetSchemaNode(), path.databind()));
}
break;
default:
if (deferredValue != null) {
// read saved data to normalized node when target schema is already known
edit.setData(readEditData(new JsonReader(new StringReader(deferredValue)), edit.getTargetSchemaNode(),
- databind.modelContext()));
+ path.databind()));
}
}
* @return NormalizedNode representing data
*/
private static NormalizedNode readEditData(final @NonNull JsonReader in, final @NonNull Inference targetSchemaNode,
- final @NonNull EffectiveModelContext context) {
+ final @NonNull DatabindContext databind) {
final var resultHolder = new NormalizationResultHolder();
final var writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
- JsonParserStream.create(writer, JSONCodecFactorySupplier.RFC7951.getShared(context), targetSchemaNode)
- .parse(in);
-
+ JsonParserStream.create(writer, databind.jsonCodecs(), targetSchemaNode).parse(in);
return resultHolder.getResult().data();
}
import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
-import org.opendaylight.restconf.server.api.DatabindContext;
+import org.opendaylight.restconf.server.api.DataPatchPath;
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;
super(inputStream);
}
- public final @NonNull PatchContext toPatchContext(final @NonNull DatabindContext databind,
- final @NonNull YangInstanceIdentifier urlPath) throws IOException {
+ public final @NonNull PatchContext toPatchContext(final @NonNull DataPatchPath path) throws IOException {
try (var is = acquireStream()) {
- return toPatchContext(databind, urlPath, is);
+ return toPatchContext(path, is);
}
}
- abstract @NonNull PatchContext toPatchContext(@NonNull DatabindContext databind,
- @NonNull YangInstanceIdentifier urlPath, @NonNull InputStream inputStream) throws IOException;
+ abstract @NonNull PatchContext toPatchContext(@NonNull DataPatchPath path, @NonNull InputStream inputStream)
+ throws IOException;
- static final YangInstanceIdentifier parsePatchTarget(final DatabindContext databind,
- final YangInstanceIdentifier urlPath, final String target) {
+ static final YangInstanceIdentifier parsePatchTarget(final DataPatchPath path, final String target) {
+ final var urlPath = path.instance();
if (target.equals("/")) {
verify(!urlPath.isEmpty(),
"target resource of URI must not be a datastore resource when target is '/'");
return urlPath;
}
+ final var databind = path.databind();
final String targetUrl;
if (urlPath.isEmpty()) {
targetUrl = target.startsWith("/") ? target.substring(1) : target;
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.server.api.DatabindContext;
+import org.opendaylight.restconf.server.api.DataPatchPath;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
import org.opendaylight.yangtools.util.xml.UntrustedXML;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
}
@Override
- PatchContext toPatchContext(final DatabindContext databind, final YangInstanceIdentifier urlPath,
- final InputStream inputStream) throws IOException {
+ PatchContext toPatchContext(final DataPatchPath path, final InputStream inputStream) throws IOException {
try {
- return parse(databind, urlPath, UntrustedXML.newDocumentBuilder().parse(inputStream));
+ return parse(path, 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,
}
}
- private static @NonNull PatchContext parse(final DatabindContext databind, final YangInstanceIdentifier urlPath,
- final Document doc) throws XMLStreamException, IOException, SAXException, URISyntaxException {
+ private static @NonNull PatchContext parse(final DataPatchPath path, final Document doc)
+ throws XMLStreamException, IOException, SAXException, URISyntaxException {
final var entities = ImmutableList.<PatchEntity>builder();
final var patchId = doc.getElementsByTagName("patch-id").item(0).getFirstChild().getNodeValue();
final var editNodes = doc.getElementsByTagName("edit");
+ final var databind = path.databind();
for (int i = 0; i < editNodes.getLength(); i++) {
final Element element = (Element) editNodes.item(i);
final Element firstValueElement = values != null ? values.get(0) : null;
// find complete path to target, it can be also empty (only slash)
- final var targetII = parsePatchTarget(databind, urlPath, target);
+ final var targetII = parsePatchTarget(path, target);
// move schema node
final var lookup = databind.schemaTree().enterPath(targetII).orElseThrow();
if (requiresValue(oper)) {
final var resultHolder = new NormalizationResultHolder();
final var writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
- final var xmlParser = XmlParserStream.create(writer, inference);
+ final var xmlParser = XmlParserStream.create(writer, databind.xmlCodecs(), inference);
xmlParser.traverse(new DOMSource(firstValueElement));
final var result = resultHolder.getResult().data();
--- /dev/null
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.restconf.server.api;
+
+import static java.util.Objects.requireNonNull;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.api.ApiPath;
+import org.opendaylight.restconf.nb.rfc8040.databind.PatchBody;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+/**
+ * An {@link ApiPath} subpath of {@code /data} {@code PUT} HTTP operation, as defined in
+ * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.5">RFC8040 section 4.5</a>.
+ *
+ * @param databind Associated {@link DatabindContext}
+ * @param instance Associated {@link YangInstanceIdentifier}
+ * @see PatchBody
+ */
+@NonNullByDefault
+public record DataPatchPath(DatabindContext databind, YangInstanceIdentifier instance)
+ implements DatabindAware {
+ public DataPatchPath {
+ requireNonNull(databind);
+ requireNonNull(instance);
+ }
+}
import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.NetconfFieldsTranslator;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.WriterFieldsTranslator;
+import org.opendaylight.restconf.server.api.DataPatchPath;
import org.opendaylight.restconf.server.api.DataPostPath;
import org.opendaylight.restconf.server.api.DataPostResult;
import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
private @NonNull RestconfFuture<PatchStatusContext> dataPATCH(final InstanceIdentifierContext reqPath,
final PatchBody body) {
+ final var patchPath = new DataPatchPath(reqPath.databind(), reqPath.getInstanceIdentifier());
final PatchContext patch;
try {
- patch = body.toPatchContext(reqPath.databind(), reqPath.getInstanceIdentifier());
+ patch = body.toPatchContext(patchPath);
} catch (IOException e) {
LOG.debug("Error parsing YANG Patch input", e);
return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest;
import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
+import org.opendaylight.restconf.server.api.DataPatchPath;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@RunWith(MockitoJUnitRunner.Silent.class)
final var iid = InstanceIdentifierContext.ofApiPath(apiPath, IID_DATABIND, mountPointService);
try (var body = bodyConstructor.apply(stringInputStream(patchBody))) {
- return body.toPatchContext(iid.databind(), iid.getInstanceIdentifier());
+ return body.toPatchContext(new DataPatchPath(iid.databind(), iid.getInstanceIdentifier()));
}
}
}