--- /dev/null
+/*
+ * Copyright (c) 2021 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.netconf.util;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Map;
+import javax.xml.stream.XMLStreamWriter;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadata;
+import org.opendaylight.yangtools.rfc7952.data.api.StreamWriterMetadataExtension;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.ForwardingNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+
+/**
+ * This class extends capability of {@link EmptyListXmlWriter} delegate to write metadata
+ * using {@link StreamWriterMetadataExtension} the same way as
+ * {@link org.opendaylight.yangtools.rfc7952.data.util.NormalizedNodeStreamWriterMetadataDecorator}.
+ */
+final class EmptyListXmlMetadataWriter extends ForwardingNormalizedNodeStreamWriter {
+ private final Deque<NormalizedMetadata> stack = new ArrayDeque<>();
+ private final EmptyListXmlWriter dataWriterDelegate;
+ private final StreamWriterMetadataExtension metaWriter;
+ private final NormalizedMetadata metadata;
+
+ private int absentDepth = 0;
+
+ EmptyListXmlMetadataWriter(final @NonNull NormalizedNodeStreamWriter writer,
+ final @NonNull XMLStreamWriter xmlStreamWriter, final @NonNull StreamWriterMetadataExtension metaWriter,
+ final @NonNull NormalizedMetadata metadata) {
+ this.dataWriterDelegate = new EmptyListXmlWriter(requireNonNull(writer), requireNonNull(xmlStreamWriter));
+ this.metaWriter = requireNonNull(metaWriter);
+ this.metadata = requireNonNull(metadata);
+ }
+
+ @Override
+ protected NormalizedNodeStreamWriter delegate() {
+ return dataWriterDelegate.delegate();
+ }
+
+ @Override
+ public void startLeafNode(final NodeIdentifier name) throws IOException {
+ dataWriterDelegate.startLeafNode(name);
+ enterMetadataNode(name);
+ }
+
+ @Override
+ public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
+ dataWriterDelegate.startLeafSet(name, childSizeHint);
+ enterMetadataNode(name);
+ }
+
+ @Override
+ public void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
+ dataWriterDelegate.startOrderedLeafSet(name, childSizeHint);
+ enterMetadataNode(name);
+ }
+
+ @Override
+ public void startLeafSetEntryNode(final YangInstanceIdentifier.NodeWithValue<?> name) throws IOException {
+ dataWriterDelegate.startLeafSetEntryNode(name);
+ enterMetadataNode(name);
+ }
+
+ @Override
+ public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+ dataWriterDelegate.startContainerNode(name, childSizeHint);
+ enterMetadataNode(name);
+ }
+
+ @Override
+ public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
+ dataWriterDelegate.startUnkeyedList(name, childSizeHint);
+ enterMetadataNode(name);
+ }
+
+ @Override
+ public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
+ dataWriterDelegate.startUnkeyedListItem(name, childSizeHint);
+ enterMetadataNode(name);
+ }
+
+ @Override
+ public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+ dataWriterDelegate.startMapNode(name, childSizeHint);
+ enterMetadataNode(name);
+ }
+
+ @Override
+ public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
+ throws IOException {
+ dataWriterDelegate.startMapEntryNode(identifier, childSizeHint);
+ enterMetadataNode(identifier);
+ }
+
+ @Override
+ public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+ dataWriterDelegate.startOrderedMapNode(name, childSizeHint);
+ enterMetadataNode(name);
+ }
+
+ @Override
+ public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+ dataWriterDelegate.startChoiceNode(name, childSizeHint);
+ enterMetadataNode(name);
+ }
+
+ @Override
+ public void startAugmentationNode(final AugmentationIdentifier identifier) throws IOException {
+ dataWriterDelegate.startAugmentationNode(identifier);
+ enterMetadataNode(identifier);
+ }
+
+ @Override
+ public boolean startAnyxmlNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
+ final boolean ret = dataWriterDelegate.startAnyxmlNode(name, objectModel);
+ if (ret) {
+ enterMetadataNode(name);
+ }
+ return ret;
+ }
+
+ @Override
+ public void endNode() throws IOException {
+ dataWriterDelegate.endNode();
+
+ if (absentDepth > 0) {
+ absentDepth--;
+ } else {
+ stack.pop();
+ }
+ }
+
+ private void enterMetadataNode(final YangInstanceIdentifier.PathArgument name) throws IOException {
+ if (absentDepth > 0) {
+ absentDepth++;
+ return;
+ }
+
+ final NormalizedMetadata current = stack.peek();
+ if (current != null) {
+ final NormalizedMetadata child = current.getChildren().get(name);
+ if (child != null) {
+ enterChild(child);
+ } else {
+ absentDepth = 1;
+ }
+ } else {
+ // Empty stack: enter first entry
+ enterChild(metadata);
+ }
+ }
+
+ private void enterChild(final NormalizedMetadata child) throws IOException {
+ final Map<QName, Object> annotations = child.getAnnotations();
+ if (!annotations.isEmpty()) {
+ metaWriter.metadata(ImmutableMap.copyOf(annotations));
+ }
+ stack.push(child);
+ }
+}
\ No newline at end of file
import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
import org.opendaylight.netconf.api.xml.XmlUtil;
import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadata;
+import org.opendaylight.yangtools.rfc7952.data.api.StreamWriterMetadataExtension;
import org.opendaylight.yangtools.rfc7952.data.util.NormalizedMetadataWriter;
import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext;
import org.opendaylight.yangtools.rfc8528.data.util.EmptyMountPointContext;
}
}
+ /**
+ * Write data specified by {@link YangInstanceIdentifier} into {@link DOMResult}.
+ *
+ * @param query path to the root node
+ * @param result DOM result holder
+ * @param schemaPath schema path of the parent node
+ * @param context mountpoint schema context
+ * @throws IOException when failed to write data into {@link NormalizedNodeStreamWriter}
+ * @throws XMLStreamException when failed to serialize data into XML document
+ */
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public static void writeNormalizedNode(final YangInstanceIdentifier query, final DOMResult result,
+ final SchemaPath schemaPath, final EffectiveModelContext context) throws IOException, XMLStreamException {
+ final XMLStreamWriter xmlWriter = XML_FACTORY.createXMLStreamWriter(result);
+ XML_NAMESPACE_SETTER.initializeNamespace(xmlWriter);
+ try (NormalizedNodeStreamWriter streamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
+ context, schemaPath);
+ EmptyListXmlWriter writer = new EmptyListXmlWriter(streamWriter, xmlWriter)) {
+ final Iterator<PathArgument> it = query.getPathArguments().iterator();
+ final PathArgument first = it.next();
+ StreamingContext.fromSchemaAndQNameChecked(context, first.getNodeType()).streamToWriter(writer, first, it);
+ } finally {
+ try {
+ if (xmlWriter != null) {
+ xmlWriter.close();
+ }
+ } catch (final Exception e) {
+ LOG.warn("Unable to close resource properly", e);
+ }
+ }
+ }
+
+ /**
+ * Write data specified by {@link YangInstanceIdentifier} along with corresponding {@code metadata}
+ * into {@link DOMResult}.
+ *
+ * @param query path to the root node
+ * @param metadata metadata to be written
+ * @param result DOM result holder
+ * @param schemaPath schema path of the parent node
+ * @param context mountpoint schema context
+ * @throws IOException when failed to write data into {@link NormalizedNodeStreamWriter}
+ * @throws XMLStreamException when failed to serialize data into XML document
+ */
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public static void writeNormalizedNode(final YangInstanceIdentifier query,
+ final @Nullable NormalizedMetadata metadata, final DOMResult result, final SchemaPath schemaPath,
+ final EffectiveModelContext context) throws IOException, XMLStreamException {
+ if (metadata == null) {
+ writeNormalizedNode(query, result, schemaPath, context);
+ return;
+ }
+
+ final XMLStreamWriter xmlWriter = XML_FACTORY.createXMLStreamWriter(result);
+ XML_NAMESPACE_SETTER.initializeNamespace(xmlWriter);
+ try (NormalizedNodeStreamWriter streamWriter = XMLStreamNormalizedNodeStreamWriter
+ .create(xmlWriter, context, schemaPath);
+ EmptyListXmlMetadataWriter writer = new EmptyListXmlMetadataWriter(streamWriter, xmlWriter, streamWriter
+ .getExtensions().getInstance(StreamWriterMetadataExtension.class), metadata)) {
+ final Iterator<PathArgument> it = query.getPathArguments().iterator();
+ final PathArgument first = it.next();
+ StreamingContext.fromSchemaAndQNameChecked(context, first.getNodeType()).streamToWriter(writer, first, it);
+ } finally {
+ try {
+ if (xmlWriter != null) {
+ xmlWriter.close();
+ }
+ } catch (final Exception e) {
+ LOG.warn("Unable to close resource properly", e);
+ }
+ }
+ }
+
/**
* Writing subtree filter specified by {@link YangInstanceIdentifier} into {@link DOMResult}.
*
--- /dev/null
+<!--
+ ~ Copyright (c) 2021 PANTHEON.tech s.r.o. 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
+ -->
+<rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <edit-config>
+ <target>
+ <candidate/>
+ </target>
+ <error-option>rollback-on-error</error-option>
+ <config>
+ <c xmlns="test:namespace" xmlns:op="urn:ietf:params:xml:ns:netconf:base:1.0" op:operation="delete"/>
+ </config>
+ </edit-config>
+</rpc>
\ No newline at end of file
--- /dev/null
+<!--
+ ~ Copyright (c) 2021 PANTHEON.tech s.r.o. 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
+ -->
+<rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <edit-config>
+ <target>
+ <candidate/>
+ </target>
+ <error-option>rollback-on-error</error-option>
+ <config>
+ <c xmlns="test:namespace">
+ <a xmlns:op="urn:ietf:params:xml:ns:netconf:base:1.0" op:operation="delete"/>
+ </c>
+ </config>
+ </edit-config>
+</rpc>
\ No newline at end of file
return Builders.containerBuilder().withNodeIdentifier(name).withValue(ImmutableList.copyOf(node)).build();
}
+ /**
+ * Create edit-config structure to invoke {@code operation} with {@code lastChildOverride} data on {@code dataPath}.
+ *
+ * @param ctx {@link EffectiveModelContext} device's model context
+ * @param dataPath {@link YangInstanceIdentifier} path to data in device's data-store
+ * @param operation Optional of {@link ModifyAction} action to be invoked
+ * @param lastChildOverride Optional of {@code NormalizedNode} data on which action will be invoked
+ * @return {@link DOMSourceAnyxmlNode} containing edit-config structure
+ */
public static DOMSourceAnyxmlNode createEditConfigAnyxml(
final EffectiveModelContext ctx, final YangInstanceIdentifier dataPath,
final Optional<ModifyAction> operation, final Optional<NormalizedNode> lastChildOverride) {
- final NormalizedNode configContent;
- final NormalizedMetadata metadata;
if (dataPath.isEmpty()) {
Preconditions.checkArgument(lastChildOverride.isPresent(),
"Data has to be present when creating structure for top level element");
Preconditions.checkArgument(lastChildOverride.get() instanceof DataContainerChild,
"Data has to be either container or a list node when creating structure for top level element, "
+ "but was: %s", lastChildOverride.get());
- configContent = lastChildOverride.get();
- metadata = null;
- } else {
- configContent = ImmutableNodes.fromInstanceId(ctx, dataPath, lastChildOverride);
- metadata = operation.map(oper -> leafMetadata(dataPath, oper)).orElse(null);
}
- final Element element = XmlUtil.createElement(BLANK_DOCUMENT, NETCONF_CONFIG_QNAME.getLocalName(),
+ final var element = XmlUtil.createElement(BLANK_DOCUMENT, NETCONF_CONFIG_QNAME.getLocalName(),
Optional.of(NETCONF_CONFIG_QNAME.getNamespace().toString()));
-
+ final var metadata = operation.map(o -> leafMetadata(dataPath, o)).orElse(null);
try {
- NetconfUtil.writeNormalizedNode(configContent, metadata, new DOMResult(element), SchemaPath.ROOT, ctx);
- } catch (IOException | XMLStreamException e) {
+ if (lastChildOverride.isPresent()) {
+ // FIXME remove ImmutableNodes.fromInstanceId usage
+ final var configContent = ImmutableNodes.fromInstanceId(ctx, dataPath, lastChildOverride.get());
+ NetconfUtil.writeNormalizedNode(configContent, metadata, new DOMResult(element), SchemaPath.ROOT, ctx);
+ } else {
+ NetconfUtil.writeNormalizedNode(dataPath, metadata, new DOMResult(element), SchemaPath.ROOT, ctx);
+ }
+ } catch (final IOException | XMLStreamException e) {
throw new IllegalStateException("Unable to serialize edit config content element for path " + dataPath, e);
}
verifyMessageSent("edit-config-test-module", NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME);
}
+ @Test
+ public void testDeleteContainerNodeCandidate() throws Exception {
+ final YangInstanceIdentifier containerId = YangInstanceIdentifier.builder()
+ .node(CONTAINER_C_QNAME)
+ .build();
+ final DataContainerChild structure = baseOps.createEditConfigStructure(Optional.empty(),
+ Optional.of(ModifyAction.DELETE), containerId);
+ baseOps.editConfigCandidate(callback, structure, true);
+ verifyMessageSent("edit-config-delete-container-node-candidate",
+ NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME);
+ }
+
+ @Test
+ public void testDeleteLeafNodeCandidate() throws Exception {
+ final YangInstanceIdentifier leafId = YangInstanceIdentifier.builder()
+ .node(CONTAINER_C_QNAME)
+ .node(LEAF_A_NID)
+ .build();
+ final DataContainerChild structure = baseOps.createEditConfigStructure(Optional.empty(),
+ Optional.of(ModifyAction.DELETE), leafId);
+ baseOps.editConfigCandidate(callback, structure, true);
+ verifyMessageSent("edit-config-delete-leaf-node-candidate",
+ NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME);
+ }
+
@Test
public void testEditConfigRunning() throws Exception {
final LeafNode<Object> leaf = Builders.leafBuilder()