--- /dev/null
+/*
+ * Copyright © 2021 FRINX 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
+ */
+package org.opendaylight.netconf.util;
+
+import java.io.IOException;
+import javax.xml.XMLConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.ForwardingNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+
+/**
+ * Proxy {@link NormalizedNodeStreamWriter} that is responsible for serialization of empty leaf-list and list
+ * nodes to output {@link XMLStreamWriter} as empty XML elements. Other operations are proxied
+ * to delegated {@link NormalizedNodeStreamWriter}.
+ */
+final class EmptyListXmlWriter extends ForwardingNormalizedNodeStreamWriter {
+ private final NormalizedNodeStreamWriter delegatedWriter;
+ private final XMLStreamWriter xmlStreamWriter;
+
+ private boolean isInsideEmptyList = false;
+
+ EmptyListXmlWriter(final NormalizedNodeStreamWriter delegatedWriter, final XMLStreamWriter xmlStreamWriter) {
+ this.delegatedWriter = delegatedWriter;
+ this.xmlStreamWriter = xmlStreamWriter;
+ }
+
+ @Override
+ protected NormalizedNodeStreamWriter delegate() {
+ return delegatedWriter;
+ }
+
+ @Override
+ public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
+ if (childSizeHint == 0) {
+ writeEmptyElement(name);
+ } else {
+ super.startUnkeyedList(name, childSizeHint);
+ }
+ }
+
+ @Override
+ public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+ if (childSizeHint == 0) {
+ writeEmptyElement(name);
+ } else {
+ super.startMapNode(name, childSizeHint);
+ }
+ }
+
+ @Override
+ public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
+ if (childSizeHint == 0) {
+ writeEmptyElement(name);
+ } else {
+ super.startOrderedMapNode(name, childSizeHint);
+ }
+ }
+
+ @Override
+ public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
+ if (childSizeHint == 0) {
+ writeEmptyElement(name);
+ } else {
+ super.startLeafSet(name, childSizeHint);
+ }
+ }
+
+ @Override
+ public void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
+ if (childSizeHint == 0) {
+ writeEmptyElement(name);
+ } else {
+ super.startOrderedLeafSet(name, childSizeHint);
+ }
+ }
+
+ @Override
+ public void endNode() throws IOException {
+ if (isInsideEmptyList) {
+ isInsideEmptyList = false;
+ } else {
+ super.endNode();
+ }
+ }
+
+ private void writeEmptyElement(final NodeIdentifier identifier) throws IOException {
+ final QName nodeType = identifier.getNodeType();
+ try {
+ xmlStreamWriter.writeEmptyElement(XMLConstants.DEFAULT_NS_PREFIX, nodeType.getLocalName(),
+ nodeType.getNamespace().toString());
+ } catch (XMLStreamException e) {
+ throw new IOException("Failed to serialize empty element to XML: " + identifier, e);
+ }
+ isInsideEmptyList = true;
+ }
+}
\ No newline at end of file
final XMLStreamWriter xmlWriter = XML_FACTORY.createXMLStreamWriter(result);
try {
- try (NormalizedNodeStreamWriter writer =
- XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, context, schemaPath)) {
+ 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,
final XMLStreamWriter xmlWriter = XML_FACTORY.createXMLStreamWriter(result);
try {
- try (NormalizedNodeStreamWriter writer = XMLStreamNormalizedNodeStreamWriter.create(
- xmlWriter, context, schemaPath)) {
+ try (NormalizedNodeStreamWriter streamWriter =
+ XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, context, schemaPath);
+ EmptyListXmlWriter writer = new EmptyListXmlWriter(streamWriter, xmlWriter)) {
final PathArgument first = rootNode.element();
StreamingContext.fromSchemaAndQNameChecked(context, first.getNodeType())
.streamToWriter(writer, first, rootNode);
package org.opendaylight.netconf.util;
import static com.google.common.base.Preconditions.checkArgument;
-import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import java.io.IOException;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
final Iterator<PathArgument> others) throws IOException {
verifyActualPathArgument(first);
- emitElementStart(writer, first);
+ emitElementStart(writer, first, others.hasNext() ? 1 : 0);
if (others.hasNext()) {
final PathArgument childPath = others.next();
final StreamingContext<?> childOp = getChildOperation(childPath);
final PathNode subtree) throws IOException {
verifyActualPathArgument(first);
- emitElementStart(writer, first);
+ final Collection<PathNode> children = subtree.children();
+ emitElementStart(writer, first, children.size());
for (final PathNode node : subtree.children()) {
emitChildTreeNode(writer, node);
}
}
}
- abstract void emitElementStart(NormalizedNodeStreamWriter writer, PathArgument arg) throws IOException;
+ abstract void emitElementStart(NormalizedNodeStreamWriter writer, PathArgument arg,
+ int childSizeHint) throws IOException;
@SuppressWarnings("checkstyle:illegalCatch")
StreamingContext<?> getChildOperation(final PathArgument childPath) {
}
@Override
- void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
- writer.startChoiceNode(getIdentifier(), UNKNOWN_SIZE);
+ void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
+ final int childSizeHint) throws IOException {
+ writer.startChoiceNode(getIdentifier(), childSizeHint);
}
}
}
@Override
- void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
+ void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
+ final int childSizeHint) throws IOException {
final NodeIdentifierWithPredicates identifier = (NodeIdentifierWithPredicates) arg;
- writer.startMapEntryNode(identifier, UNKNOWN_SIZE);
+ writer.startMapEntryNode(identifier, childSizeHint);
for (Entry<QName, Object> entry : identifier.entrySet()) {
writer.startLeafNode(new NodeIdentifier(entry.getKey()));
}
@Override
- void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
- writer.startUnkeyedListItem(getIdentifier(), UNKNOWN_SIZE);
+ void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
+ final int childSizeHint) throws IOException {
+ writer.startUnkeyedListItem(getIdentifier(), childSizeHint);
}
}
}
@Override
- void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
- writer.startContainerNode(getIdentifier(), UNKNOWN_SIZE);
+ void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
+ final int childSizeHint) throws IOException {
+ writer.startContainerNode(getIdentifier(), childSizeHint);
}
}
}
@Override
- void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
- writer.startOrderedLeafSet(getIdentifier(), UNKNOWN_SIZE);
+ void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
+ final int childSizeHint) throws IOException {
+ writer.startOrderedLeafSet(getIdentifier(), childSizeHint);
}
}
}
@Override
- void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
- writer.startLeafSet(getIdentifier(), UNKNOWN_SIZE);
+ void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
+ final int childSizeHint) throws IOException {
+ writer.startLeafSet(getIdentifier(), childSizeHint);
}
}
}
@Override
- void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
+ void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
+ final int childSizeHint) throws IOException {
writer.startAugmentationNode(getIdentifier());
}
}
}
@Override
- void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
- writer.startMapNode(getIdentifier(), UNKNOWN_SIZE);
+ void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
+ final int childSizeHint) throws IOException {
+ writer.startMapNode(getIdentifier(), childSizeHint);
}
}
}
@Override
- void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
- writer.startOrderedMapNode(getIdentifier(), UNKNOWN_SIZE);
+ void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
+ final int childSizeHint) throws IOException {
+ writer.startOrderedMapNode(getIdentifier(), childSizeHint);
}
}
}
@Override
- void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
- writer.startUnkeyedList(getIdentifier(), UNKNOWN_SIZE);
+ void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg,
+ final int childSizeHint) throws IOException {
+ writer.startUnkeyedList(getIdentifier(), childSizeHint);
}
}
}
import com.google.common.collect.Lists;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+ "</rpc>");
}
+ @Test
+ public void testGetLeafList() throws IOException, SAXException {
+ final YangInstanceIdentifier path = YangInstanceIdentifier.create(
+ toId(NetconfState.QNAME),
+ toId(Capabilities.QNAME),
+ toId(QName.create(Capabilities.QNAME, "capability")));
+ final DataContainerChild<?, ?> filter = toFilterStructure(path, SCHEMA);
+ final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
+ NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filter));
+
+ assertSimilarXml(netconfMessage, "<rpc message-id=\"m-0\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+ + "<get>\n"
+ + "<filter xmlns:ns0=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ns0:type=\"subtree\">\n"
+ + "<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\">\n"
+ + "<capabilities>\n"
+ + "<capability/>\n"
+ + "</capabilities>\n"
+ + "</netconf-state>\n"
+ + "</filter>\n"
+ + "</get>\n"
+ + "</rpc>\n");
+ }
+
+ @Test
+ public void testGetList() throws IOException, SAXException {
+ final YangInstanceIdentifier path = YangInstanceIdentifier.create(
+ toId(NetconfState.QNAME),
+ toId(Datastores.QNAME),
+ toId(QName.create(Datastores.QNAME, "datastore")));
+ final DataContainerChild<?, ?> filter = toFilterStructure(path, SCHEMA);
+ final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
+ NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filter));
+
+ assertSimilarXml(netconfMessage, "<rpc message-id=\"m-0\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+ + "<get>\n"
+ + "<filter xmlns:ns0=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ns0:type=\"subtree\">\n"
+ + "<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\">\n"
+ + "<datastores>\n"
+ + "<datastore/>\n"
+ + "</datastores>\n"
+ + "</netconf-state>\n"
+ + "</filter>\n"
+ + "</get>\n"
+ + "</rpc>\n");
+ }
+
private static NetconfMessageTransformer getTransformer(final EffectiveModelContext schema) {
return new NetconfMessageTransformer(new EmptyMountPointContext(schema), true, BASE_SCHEMAS.getBaseSchema());
}
@Test
public void toActionRequestContainerTopLevelTest() {
- List<PathArgument> nodeIdentifiers = Collections.singletonList(NodeIdentifier.create(DEVICE_QNAME));
+ List<PathArgument> nodeIdentifiers = List.of(NodeIdentifier.create(DEVICE_QNAME));
DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers);
NormalizedNode<?, ?> payload = initInputAction(QName.create(DEVICE_QNAME, "start-at"), "now");
List<PathArgument> nodeIdentifiers = new ArrayList<>();
nodeIdentifiers.add(NodeIdentifier.create(SERVER_QNAME));
nodeIdentifiers.add(NodeIdentifierWithPredicates.of(SERVER_QNAME, serverNameQname, "testServer"));
- nodeIdentifiers.add(new AugmentationIdentifier(Collections.singleton(APPLICATIONS_QNAME)));
+ nodeIdentifiers.add(new AugmentationIdentifier(Set.of(APPLICATIONS_QNAME)));
nodeIdentifiers.add(NodeIdentifier.create(APPLICATIONS_QNAME));
nodeIdentifiers.add(NodeIdentifier.create(APPLICATION_QNAME));
nodeIdentifiers.add(NodeIdentifierWithPredicates.of(APPLICATION_QNAME,
final YangInstanceIdentifier datastoresField = YangInstanceIdentifier.create(toId(Datastores.QNAME));
// building filter structure and NETCONF message
- final DataContainerChild<?, ?> filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of(
- parentYiid, List.of(netconfStartTimeField, datastoresField))), SCHEMA);
+ final DataContainerChild<?, ?> filterStructure = toFilterStructure(
+ List.of(FieldsFilter.of(parentYiid, List.of(netconfStartTimeField, datastoresField))), SCHEMA);
final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure));
toId(Datastore.QNAME), NodeIdentifierWithPredicates.of(Datastore.QNAME), toId(Locks.QNAME));
// building filter structure and NETCONF message
- final DataContainerChild<?, ?> filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of(
- parentYiid, List.of(capabilitiesField, capabilityField, datastoreField, locksFields))), SCHEMA);
+ final DataContainerChild<?, ?> filterStructure = toFilterStructure(
+ List.of(FieldsFilter.of(parentYiid,
+ List.of(capabilitiesField, capabilityField, datastoreField, locksFields))),
+ SCHEMA);
final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure));
final YangInstanceIdentifier capabilitiesField = YangInstanceIdentifier.create(toId(Capabilities.QNAME));
// building filter structure and NETCONF message
- final DataContainerChild<?, ?> filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of(
- parentYiid, List.of(capabilitiesField, YangInstanceIdentifier.empty()))), SCHEMA);
+ final DataContainerChild<?, ?> filterStructure = toFilterStructure(
+ List.of(FieldsFilter.of(parentYiid, List.of(capabilitiesField, YangInstanceIdentifier.empty()))),
+ SCHEMA);
final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure));
toId(QName.create(Schema.QNAME, "namespace").intern()));
// building filter structure and NETCONF message
- final DataContainerChild<?, ?> filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of(
- parentYiid, List.of(versionField, identifierField))), SCHEMA);
+ final DataContainerChild<?, ?> filterStructure = toFilterStructure(
+ List.of(FieldsFilter.of(parentYiid, List.of(versionField, identifierField))), SCHEMA);
final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure));
toId(Session.QNAME), NodeIdentifierWithPredicates.of(Session.QNAME));
// building filter structure and NETCONF message
- final DataContainerChild<?, ?> filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of(
- parentYiid, List.of(datastoreListField, sessionListField))), SCHEMA);
+ final DataContainerChild<?, ?> filterStructure = toFilterStructure(
+ List.of(FieldsFilter.of(parentYiid, List.of(datastoreListField, sessionListField))), SCHEMA);
final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure));
toId(QName.create(Session.QNAME, "transport").intern()));
// building filter structure and NETCONF message
- final DataContainerChild<?, ?> filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of(
- parentYiid, List.of(session1Field, session2TransportField))), SCHEMA);
+ final DataContainerChild<?, ?> filterStructure = toFilterStructure(
+ List.of(FieldsFilter.of(parentYiid, List.of(session1Field, session2TransportField))), SCHEMA);
final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure));