import com.google.common.collect.ImmutableList;
import java.io.InputStream;
import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.restconf.server.api.DataPostPath;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
public abstract sealed class ChildBody extends AbstractBody permits JsonChildBody, XmlChildBody {
public record PrefixAndBody(@NonNull ImmutableList<PathArgument> prefix, @NonNull NormalizedNode body) {
super(inputStream);
}
- public final @NonNull PrefixAndBody toPayload(final @NonNull YangInstanceIdentifier parentPath,
- final @NonNull Inference parentInference) {
- return toPayload(acquireStream(), parentPath, parentInference);
+ /**
+ * Interpret this object as a child of specified path.
+ *
+ * @param path POST request path
+ * @return A {@link PrefixAndBody}
+ */
+ public final @NonNull PrefixAndBody toPayload(final @NonNull DataPostPath path) {
+ return toPayload(path, acquireStream());
}
- abstract @NonNull PrefixAndBody toPayload(@NonNull InputStream inputStream,
- @NonNull YangInstanceIdentifier parentPath, @NonNull Inference parentInference);
+ abstract @NonNull PrefixAndBody toPayload(@NonNull DataPostPath path, @NonNull InputStream inputStream);
}
import java.io.InputStream;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.server.api.DataPostPath;
/**
* Body of a {@code POST} request as defined in
* <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.4">RFC8040 section 4.4</a>.
+ * @see DataPostPath
*/
@NonNullByDefault
public abstract sealed class DataPostBody extends AbstractBody permits JsonDataPostBody, XmlDataPostBody {
import java.nio.charset.StandardCharsets;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.server.api.DataPostPath;
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.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
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.data.impl.schema.ResultAlreadySetException;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Override
@SuppressWarnings("checkstyle:illegalCatch")
- PrefixAndBody toPayload(final InputStream inputStream, final YangInstanceIdentifier parentPath,
- final Inference parentInference) {
+ PrefixAndBody toPayload(final DataPostPath path, final InputStream inputStream) {
NormalizedNode result;
try {
- result = toNormalizedNode(inputStream, parentInference);
+ result = toNormalizedNode(path, inputStream);
} catch (Exception e) {
Throwables.throwIfInstanceOf(e, RestconfDocumentedException.class);
LOG.debug("Error parsing json input", e);
return new PrefixAndBody(iiToDataList.build(), result);
}
- private static @NonNull NormalizedNode toNormalizedNode(final InputStream inputStream, final Inference inference) {
+ private static @NonNull NormalizedNode toNormalizedNode(final DataPostPath path, final InputStream inputStream) {
final var resultHolder = new NormalizationResultHolder();
final var writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
- final var jsonParser = JsonParserStream.create(writer,
- JSONCodecFactorySupplier.RFC7951.getShared(inference.getEffectiveModelContext()), inference);
+ final var jsonParser = JsonParserStream.create(writer, path.databind().jsonCodecs(), path.inference());
final var reader = new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
jsonParser.parse(reader);
import javax.xml.transform.dom.DOMSource;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.server.api.DataPostPath;
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.common.QName;
import org.opendaylight.yangtools.yang.common.XMLNamespace;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.PathMixin;
-import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
@Override
@SuppressWarnings("checkstyle:illegalCatch")
- PrefixAndBody toPayload(final InputStream inputStream, final YangInstanceIdentifier parentPath,
- final Inference parentInference) {
+ PrefixAndBody toPayload(final DataPostPath path, final InputStream inputStream) {
try {
- return parse(parentPath, parentInference, UntrustedXML.newDocumentBuilder().parse(inputStream));
+ return parse(path, UntrustedXML.newDocumentBuilder().parse(inputStream));
} catch (final RestconfDocumentedException e) {
throw e;
} catch (final Exception e) {
}
}
- private static @NonNull PrefixAndBody parse(final YangInstanceIdentifier path, final Inference pathInference,
- final Document doc) throws XMLStreamException, IOException, SAXException, URISyntaxException {
+ private static @NonNull PrefixAndBody parse(final DataPostPath path, final Document doc)
+ throws XMLStreamException, IOException, SAXException, URISyntaxException {
+ final var pathInference = path.inference();
+
final DataSchemaNode parentNode;
if (pathInference.isEmpty()) {
parentNode = pathInference.getEffectiveModelContext();
final var qname = QName.create(it.next().localQNameModule(), docRootElm);
final var iiToDataList = ImmutableList.<PathArgument>builder();
- final var nodeAndStack = DataSchemaContextTree.from(context).enterPath(path).orElseThrow();
+ // FIXME: we should have this readily available: it is the last node the ApiPath->YangInstanceIdentifier parser
+ // has seen (and it should have the nodeAndStack handy
+ final var nodeAndStack = path.databind().schemaTree().enterPath(path.instance()).orElseThrow();
final var stack = nodeAndStack.stack();
var current = nodeAndStack.node();
do {
final var resultHolder = new NormalizationResultHolder();
final var writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
- final var xmlParser = XmlParserStream.create(writer, stack.toInference());
+ final var xmlParser = XmlParserStream.create(writer, path.databind().xmlCodecs(), stack.toInference());
xmlParser.traverse(new DOMSource(doc.getDocumentElement()));
var parsed = 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.DataPostBody;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+
+/**
+ * An {@link ApiPath} subpath of {@code /data} {@code POST} HTTP operation, as defined in
+ * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.4">RFC8040 section 4.4</a>.
+ *
+ * @param databind Associated {@link DatabindContext}
+ * @param inference Associated {@link Inference} pointing to the {@link EffectiveStatement} of the last ApiPath element.
+ * This can be one of:
+ * <ul>
+ * <li>a datatore, inference being {@link Inference#isEmpty() empty}</li>
+ * <li>a data resource, inference pointing to the the {@code data schema node} identified by
+ * {@code instance}</li>
+ * <li>an {@code rpc} or an {@code action} invocation, inference pointing to the statement</li>
+ * </ul>
+ * @param instance Associated {@link YangInstanceIdentifier}
+ * @see DataPostBody
+ */
+@NonNullByDefault
+public record DataPostPath(DatabindContext databind, Inference inference, YangInstanceIdentifier instance)
+ implements DatabindAware {
+ public DataPostPath {
+ requireNonNull(databind);
+ requireNonNull(inference);
+ 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.DataPostPath;
import org.opendaylight.restconf.server.api.DataPostResult;
import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
private @NonNull RestconfFuture<CreateResource> dataCreatePOST(final InstanceIdentifierContext reqPath,
final ChildBody body, final Map<String, String> queryParameters) {
+ final var postPath = new DataPostPath(reqPath.databind(), reqPath.inference(), reqPath.getInstanceIdentifier());
+
final Insert insert;
try {
- insert = Insert.ofQueryParameters(reqPath.databind(), queryParameters);
+ insert = Insert.ofQueryParameters(postPath.databind(), queryParameters);
} catch (IllegalArgumentException e) {
return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
}
- final var parentPath = reqPath.getInstanceIdentifier();
- final var payload = body.toPayload(parentPath, reqPath.inference());
+ final var payload = body.toPayload(postPath);
return getRestconfStrategy(reqPath.databind(), reqPath.getMountPoint())
- .postData(concat(parentPath, payload.prefix()), payload.body(), insert);
+ .postData(concat(postPath.instance(), payload.prefix()), payload.body(), insert);
}
private static YangInstanceIdentifier concat(final YangInstanceIdentifier parent, final List<PathArgument> args) {
*/
package org.opendaylight.restconf.nb.rfc8040.databind;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
-import org.junit.BeforeClass;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.opendaylight.restconf.server.api.DataPostPath;
+import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
-public class JsonChildBodyTest extends AbstractBodyTest {
- private static EffectiveModelContext schemaContext;
+class JsonChildBodyTest extends AbstractBodyTest {
+ private static DataPostPath CONT_PATH;
- @BeforeClass
- public static void initialization() throws Exception {
+ @BeforeAll
+ static void beforeAll() throws Exception {
final var testFiles = loadFiles("/instanceidentifier/yang");
testFiles.addAll(loadFiles("/modules"));
- schemaContext = YangParserTestUtils.parseYangFiles(testFiles);
+ final var modelContext = YangParserTestUtils.parseYangFiles(testFiles);
+
+ CONT_PATH = new DataPostPath(DatabindContext.ofModel(modelContext),
+ Inference.ofDataTreePath(modelContext, CONT_QNAME), YangInstanceIdentifier.of(CONT_QNAME));
}
@Test
- public void moduleSubContainerDataPostTest() throws Exception {
+ void moduleSubContainerDataPostTest() {
final var body = new JsonChildBody(
JsonChildBodyTest.class.getResourceAsStream("/instanceidentifier/json/json_sub_container.json"));
- final var payload = body.toPayload(YangInstanceIdentifier.of(CONT_QNAME),
- Inference.ofDataTreePath(schemaContext, CONT_QNAME));
+ final var payload = body.toPayload(CONT_PATH);
final var lflst11 = QName.create("augment:module:leaf:list", "2014-01-27", "lflst11");
assertEquals(List.of(new NodeIdentifier(CONT1_QNAME)), payload.prefix());
}
@Test
- public void moduleSubContainerAugmentDataPostTest() throws Exception {
+ void moduleSubContainerAugmentDataPostTest() {
final var body = new JsonChildBody(
JsonChildBodyTest.class.getResourceAsStream("/instanceidentifier/json/json_augment_container.json"));
- final var payload = body.toPayload(YangInstanceIdentifier.of(CONT_QNAME),
- Inference.ofDataTreePath(schemaContext, CONT_QNAME));
+ final var payload = body.toPayload(CONT_PATH);
final var contAugment = QName.create("augment:module", "2014-01-17", "cont-augment");
assertEquals(List.of(new NodeIdentifier(contAugment)), payload.prefix());
}
@Test
- public void moduleSubContainerChoiceAugmentDataPostTest() throws Exception {
+ void moduleSubContainerChoiceAugmentDataPostTest() {
final var body = new JsonChildBody(
JsonChildBodyTest.class.getResourceAsStream("/instanceidentifier/json/json_augment_choice_container.json"));
- final var payload = body.toPayload(YangInstanceIdentifier.of(new NodeIdentifier(CONT_QNAME)),
- Inference.ofDataTreePath(schemaContext, CONT_QNAME));
+ final var payload = body.toPayload(CONT_PATH);
final var container1 = QName.create("augment:module", "2014-01-17", "case-choice-case-container1");
assertEquals(List.of(
*/
package org.opendaylight.restconf.nb.rfc8040.databind;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
-import org.junit.BeforeClass;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.opendaylight.restconf.server.api.DataPostPath;
+import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
-public class XmlChildBodyTest extends AbstractBodyTest {
+class XmlChildBodyTest extends AbstractBodyTest {
private static final QName TOP_LEVEL_LIST = QName.create("foo", "2017-08-09", "top-level-list");
- private static EffectiveModelContext schemaContext;
+ private static DataPostPath EMPTY_PATH;
+ private static DataPostPath CONT_PATH;
- @BeforeClass
- public static void initialization() throws Exception {
+ @BeforeAll
+ static void beforeAll() throws Exception {
final var testFiles = loadFiles("/instanceidentifier/yang");
testFiles.addAll(loadFiles("/modules"));
testFiles.addAll(loadFiles("/foo-xml-test/yang"));
- schemaContext = YangParserTestUtils.parseYangFiles(testFiles);
+ final var modelContext = YangParserTestUtils.parseYangFiles(testFiles);
+
+ CONT_PATH = new DataPostPath(DatabindContext.ofModel(modelContext),
+ Inference.ofDataTreePath(modelContext, CONT_QNAME), YangInstanceIdentifier.of(CONT_QNAME));
+ EMPTY_PATH = new DataPostPath(DatabindContext.ofModel(modelContext),
+ Inference.ofDataTreePath(modelContext), YangInstanceIdentifier.of());
}
@Test
- public void postXmlTest() throws Exception {
+ void postXmlTest() {
final var body = new XmlChildBody(XmlChildBodyTest.class.getResourceAsStream("/foo-xml-test/foo.xml"));
- final var payload = body.toPayload(YangInstanceIdentifier.of(), Inference.ofDataTreePath(schemaContext));
+ final var payload = body.toPayload(EMPTY_PATH);
final var entryId = NodeIdentifierWithPredicates.of(TOP_LEVEL_LIST,
QName.create(TOP_LEVEL_LIST, "key-leaf"), "key-value");
}
@Test
- public void moduleSubContainerDataPostTest() throws Exception {
+ void moduleSubContainerDataPostTest() {
final var body = new XmlChildBody(
XmlChildBodyTest.class.getResourceAsStream("/instanceidentifier/xml/xml_sub_container.xml"));
- final var payload = body.toPayload(YangInstanceIdentifier.of(CONT_QNAME),
- Inference.ofDataTreePath(schemaContext, CONT_QNAME));
+ final var payload = body.toPayload(CONT_PATH);
final var lflst11 = QName.create("augment:module:leaf:list", "2014-01-27", "lflst11");
assertEquals(List.of(new NodeIdentifier(CONT1_QNAME)), payload.prefix());
}
@Test
- public void moduleSubContainerAugmentDataPostTest() throws Exception {
+ void moduleSubContainerAugmentDataPostTest() {
final var body = new XmlChildBody(
XmlChildBodyTest.class.getResourceAsStream("/instanceidentifier/xml/xml_augment_container.xml"));
- final var payload = body.toPayload(YangInstanceIdentifier.of(CONT_QNAME),
- Inference.ofDataTreePath(schemaContext, CONT_QNAME));
+ final var payload = body.toPayload(CONT_PATH);
final var contAugment = QName.create("augment:module", "2014-01-17", "cont-augment");
assertEquals(List.of(new NodeIdentifier(contAugment)), payload.prefix());
}
@Test
- public void moduleSubContainerChoiceAugmentDataPostTest() throws Exception {
+ void moduleSubContainerChoiceAugmentDataPostTest() {
final var body = new XmlChildBody(
XmlChildBodyTest.class.getResourceAsStream("/instanceidentifier/xml/xml_augment_choice_container.xml"));
- final var payload = body.toPayload(YangInstanceIdentifier.of(CONT_QNAME),
- Inference.ofDataTreePath(schemaContext, CONT_QNAME));
+ final var payload = body.toPayload(CONT_PATH);
final var container1 = QName.create("augment:module", "2014-01-17", "case-choice-case-container1");
assertEquals(List.of(
* name, but also by correct namespace used in payload.
*/
@Test
- public void findFooContainerUsingNamespaceTest() throws Exception {
+ void findFooContainerUsingNamespaceTest() {
final var body = new XmlChildBody(
XmlChildBodyTest.class.getResourceAsStream("/instanceidentifier/xml/xmlDataFindFooContainer.xml"));
- final var payload = body.toPayload(YangInstanceIdentifier.of(), Inference.ofDataTreePath(schemaContext));
+ final var payload = body.toPayload(EMPTY_PATH);
final var fooBarContainer = new NodeIdentifier(QName.create("foo:module", "2016-09-29", "foo-bar-container"));
assertEquals(List.of(fooBarContainer), payload.prefix());
* name, but also by correct namespace used in payload.
*/
@Test
- public void findBarContainerUsingNamespaceTest() throws Exception {
+ void findBarContainerUsingNamespaceTest() {
final var body = new XmlChildBody(
XmlChildBodyTest.class.getResourceAsStream("/instanceidentifier/xml/xmlDataFindBarContainer.xml"));
- final var payload = body.toPayload(YangInstanceIdentifier.of(), Inference.ofDataTreePath(schemaContext));
+ final var payload = body.toPayload(EMPTY_PATH);
final var fooBarContainer = new NodeIdentifier(QName.create("bar:module", "2016-09-29", "foo-bar-container"));
assertEquals(List.of(fooBarContainer), payload.prefix());