import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stax.StAXSource;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.yangtools.odlext.model.api.YangModeledAnyXmlSchemaNode;
+import org.opendaylight.yangtools.rfc7952.data.util.AbstractImmutableOpaqueAnydataStreamWriter;
+import org.opendaylight.yangtools.rfc7952.data.util.ImmutableNormalizedMetadata;
import org.opendaylight.yangtools.rfc7952.model.api.AnnotationSchemaNode;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.opaque.OpaqueData;
import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.util.AbstractNodeDataWithSchema;
import org.opendaylight.yangtools.yang.data.util.AnyXmlNodeDataWithSchema;
import org.opendaylight.yangtools.yang.data.util.LeafNodeDataWithSchema;
import org.opendaylight.yangtools.yang.data.util.ListEntryNodeDataWithSchema;
import org.opendaylight.yangtools.yang.data.util.ListNodeDataWithSchema;
+import org.opendaylight.yangtools.yang.data.util.OpaqueAnydataNodeDataWithSchema;
import org.opendaylight.yangtools.yang.data.util.OperationAsContainer;
import org.opendaylight.yangtools.yang.data.util.ParserStreamUtils;
import org.opendaylight.yangtools.yang.data.util.SimpleNodeDataWithSchema;
import org.opendaylight.yangtools.yang.data.util.YangModeledAnyXmlNodeDataWithSchema;
+import org.opendaylight.yangtools.yang.model.api.AnyDataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
nodeDataWithSchema = new LeafNodeDataWithSchema((LeafSchemaNode) parentNode);
} else if (parentNode instanceof LeafListSchemaNode) {
nodeDataWithSchema = new LeafListNodeDataWithSchema((LeafListSchemaNode) parentNode);
+ } else if (parentNode instanceof AnyDataSchemaNode) {
+ nodeDataWithSchema = new OpaqueAnydataNodeDataWithSchema((AnyDataSchemaNode) parentNode);
} else {
throw new IllegalStateException("Unsupported schema node type " + parentNode.getClass() + ".");
}
return (Document) result.getNode();
}
+ private void readOpaqueAnydataValue(final XMLStreamReader in, final OpaqueAnydataNodeDataWithSchema parent)
+ throws XMLStreamException {
+ final DefaultOpaqueAnydataStreamWriter opaqueWriter = new DefaultOpaqueAnydataStreamWriter();
+ final Entry<OpaqueData, ImmutableNormalizedMetadata> result;
+ while (true) {
+ final int event = in.next();
+ try {
+ switch (event) {
+ case XMLStreamConstants.START_ELEMENT:
+ final String nsUri = in.getNamespaceURI();
+ final QNameModule module = resolveXmlNamespace(nsUri).orElseGet(() -> rawXmlNamespace(nsUri));
+ opaqueWriter.startOpaqueContainer(NodeIdentifier.create(QName.create(module,
+ in.getLocalName())));
+ break;
+ case XMLStreamConstants.END_ELEMENT:
+ opaqueWriter.endOpaqueNode();
+ break;
+ case XMLStreamConstants.CHARACTERS:
+ opaqueWriter.opaqueValue(in.getText());
+ break;
+ default:
+ LOG.debug("Ignoring event {}", event);
+ continue;
+ }
+ } catch (IOException e) {
+ throw new XMLStreamException("Inconsistent anydata stream", e);
+ }
+
+ final Entry<OpaqueData, ImmutableNormalizedMetadata> optResult = opaqueWriter.result();
+ if (optResult != null) {
+ result = optResult;
+ break;
+ }
+ }
+
+ setValue(parent, result.getKey(), in.getNamespaceContext());
+ parent.setMetadata(result.getValue());
+ }
+
private void read(final XMLStreamReader in, final AbstractNodeDataWithSchema<?> parent, final String rootElement)
throws XMLStreamException {
if (!in.hasNext()) {
return;
}
+ if (parent instanceof OpaqueAnydataNodeDataWithSchema) {
+ parent.setAttributes(getElementAttributes(in));
+ readOpaqueAnydataValue(in, (OpaqueAnydataNodeDataWithSchema) parent);
+ if (isNextEndDocument(in)) {
+ return;
+ }
+
+ if (!isAtElement(in)) {
+ in.nextTag();
+ }
+
+ return;
+ }
+
if (parent instanceof YangModeledAnyXmlSchemaNode) {
parent.setAttributes(getElementAttributes(in));
}
private Object translateValueByType(final Object value, final DataSchemaNode node,
final NamespaceContext namespaceCtx) {
if (node instanceof AnyXmlSchemaNode) {
-
checkArgument(value instanceof Document);
/*
- * FIXME: Figure out some YANG extension dispatch, which will
- * reuse JSON parsing or XML parsing - anyxml is not well-defined in
- * JSON.
+ * FIXME: Figure out some YANG extension dispatch, which will reuse JSON parsing or XML parsing -
+ * anyxml is not well-defined in JSON.
*/
return new DOMSource(((Document) value).getDocumentElement());
}
+ if (node instanceof AnyDataSchemaNode) {
+ checkArgument(value instanceof OpaqueData, "Unexpected anydata value %s", value);
+ return value;
+ }
checkArgument(node instanceof TypedDataSchemaNode);
checkArgument(value instanceof String);
private QNameModule rawXmlNamespace(final String xmlNamespace) {
return rawNamespaces.computeIfAbsent(xmlNamespace, nsUri -> QNameModule.create(URI.create(nsUri)));
}
+
+ // Utility writer just to keep the event model intact. This requires us to build an intermediate representation,
+ // simply because we are required to perform list coalescence.
+ //
+ // FIXME: 5.0.0: we should be able to negotiate downstream's tolerance to reentrant lists, so that we are not
+ // not required to keep all the book keeping. Immutable builder should not care as long as structure
+ // is kept...
+ private static final class DefaultOpaqueAnydataStreamWriter extends AbstractImmutableOpaqueAnydataStreamWriter {
+ private Entry<@NonNull OpaqueData, @Nullable ImmutableNormalizedMetadata> result;
+
+ DefaultOpaqueAnydataStreamWriter() {
+ super(false);
+ }
+
+ Entry<@NonNull OpaqueData, @Nullable ImmutableNormalizedMetadata> result() {
+ return result;
+ }
+
+ @Override
+ protected void finishAnydata(final OpaqueData opaqueData, final ImmutableNormalizedMetadata metadata) {
+ checkState(result == null, "Result already set to %s", result);
+ result = new SimpleImmutableEntry<>(requireNonNull(opaqueData), metadata);
+ }
+ }
}
--- /dev/null
+/*
+ * Copyright (c) 2019 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.yangtools.yang.data.codec.xml;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import org.junit.Test;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.data.util.schema.opaque.OpaqueDataBuilder;
+import org.opendaylight.yangtools.yang.data.util.schema.opaque.OpaqueDataContainerBuilder;
+import org.xml.sax.SAXException;
+
+public class AnydataParseTest extends AbstractAnydataTest {
+
+ @Test
+ public void testOpaqueAnydata() throws XMLStreamException, IOException, URISyntaxException, SAXException {
+ final XMLStreamReader reader = UntrustedXML.createXMLStreamReader(
+ toInputStream("<foo xmlns=\"test-anydata\"><bar/></foo>"));
+
+ final NormalizedNodeResult result = new NormalizedNodeResult();
+ final NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result);
+ final XmlParserStream xmlParser = XmlParserStream.create(streamWriter, SCHEMA_CONTEXT,
+ SCHEMA_CONTEXT.findDataChildByName(FOO_QNAME).get(), true);
+ xmlParser.parse(reader);
+
+ final NormalizedNode<?, ?> parsed = result.getResult();
+ assertEquals(Builders.opaqueAnydataBuilder().withNodeIdentifier(FOO_NODEID)
+ .withValue(new OpaqueDataBuilder().withAccurateLists(false)
+ .withRoot(new OpaqueDataContainerBuilder().withIdentifier(BAR_NODEID).build()).build())
+ .build(), parsed);
+ }
+
+ private static InputStream toInputStream(final String str) {
+ return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2019 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.yangtools.yang.data.util;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.Beta;
+import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadata;
+import org.opendaylight.yangtools.yang.model.api.AnyDataSchemaNode;
+
+@Beta
+public abstract class AbstractAnydataNodeDataWithSchema<T> extends SimpleNodeDataWithSchema<AnyDataSchemaNode> {
+ private NormalizedMetadata metadata;
+
+ protected AbstractAnydataNodeDataWithSchema(final AnyDataSchemaNode dataSchemaNode) {
+ super(dataSchemaNode);
+ }
+
+ @Override
+ public T getValue() {
+ return objectModelClass().cast(super.getValue());
+ }
+
+ @Override
+ public void setValue(final Object value) {
+ final Class<T> clazz = objectModelClass();
+ checkArgument(clazz.isInstance(value), "Value %s is not compatible with %s", clazz);
+ super.setValue(value);
+ }
+
+ public final NormalizedMetadata getMetadata() {
+ return metadata;
+ }
+
+ public final void setMetadata(final NormalizedMetadata value) {
+ checkState(metadata == null, "Metadata already set to %s", metadata);
+ metadata = value;
+ }
+
+ protected abstract Class<T> objectModelClass();
+}
--- /dev/null
+/*
+ * Copyright (c) 2019 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.yangtools.yang.data.util;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.util.List;
+import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadata;
+import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadataStreamWriter;
+import org.opendaylight.yangtools.rfc7952.data.api.OpaqueAnydataStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.opaque.OpaqueData;
+import org.opendaylight.yangtools.yang.data.api.schema.opaque.OpaqueDataContainer;
+import org.opendaylight.yangtools.yang.data.api.schema.opaque.OpaqueDataList;
+import org.opendaylight.yangtools.yang.data.api.schema.opaque.OpaqueDataNode;
+import org.opendaylight.yangtools.yang.data.api.schema.opaque.OpaqueDataValue;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.OpaqueAnydataExtension;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.OpaqueAnydataExtension.StreamWriter;
+import org.opendaylight.yangtools.yang.model.api.AnyDataSchemaNode;
+
+@Beta
+public final class OpaqueAnydataNodeDataWithSchema extends AbstractAnydataNodeDataWithSchema<OpaqueData> {
+ public OpaqueAnydataNodeDataWithSchema(final AnyDataSchemaNode dataSchemaNode) {
+ super(dataSchemaNode);
+ }
+
+ @Override
+ protected Class<OpaqueData> objectModelClass() {
+ return OpaqueData.class;
+ }
+
+ @Override
+ protected void write(final NormalizedNodeStreamWriter writer, final NormalizedMetadataStreamWriter metaWriter)
+ throws IOException {
+ final OpaqueAnydataExtension ext = writer.getExtensions().getInstance(OpaqueAnydataExtension.class);
+ if (ext != null) {
+ writer.nextDataSchemaNode(getSchema());
+ streamOpaqueAnydataNode(ext, provideNodeIdentifier(), getValue(), getMetadata());
+ writer.endNode();
+ }
+ }
+
+ private static void streamOpaqueAnydataNode(final OpaqueAnydataExtension ext, final NodeIdentifier name,
+ final OpaqueData data, final NormalizedMetadata metadata) throws IOException {
+ if (metadata != null) {
+ final StreamWriter dataWriter = ext.startOpaqueAnydataNode(name, data.hasAccurateLists());
+ final OpaqueDataNode node = data.getRoot();
+ if (dataWriter instanceof OpaqueAnydataStreamWriter) {
+ streamOpaqueDataNode((OpaqueAnydataStreamWriter) dataWriter, node, metadata);
+ } else {
+ dataWriter.streamOpaqueDataNode(node);
+ }
+ } else {
+ ext.streamOpaqueAnydataNode(name, data);
+ }
+ }
+
+ private static void streamOpaqueDataNode(final OpaqueAnydataStreamWriter writer, final OpaqueDataNode node,
+ final NormalizedMetadata metadata) throws IOException {
+ if (node instanceof OpaqueDataValue) {
+ writer.startOpaqueContainer(node.getIdentifier(), 0);
+ writeMetadata(writer, metadata);
+ writer.opaqueValue(((OpaqueDataValue) node).getValue());
+ writer.endOpaqueNode();
+ return;
+ }
+ final List<? extends OpaqueDataNode> children;
+ if (node instanceof OpaqueDataList) {
+ children = ((OpaqueDataList) node).getChildren();
+ writer.startOpaqueList(node.getIdentifier(), children.size());
+ } else if (node instanceof OpaqueDataContainer) {
+ children = ((OpaqueDataContainer) node).getChildren();
+ writer.startOpaqueContainer(node.getIdentifier(), children.size());
+ } else {
+ throw new IllegalStateException("Unhandled node " + node);
+ }
+
+ writeMetadata(writer, metadata);
+ for (OpaqueDataNode child : children) {
+ streamOpaqueDataNode(writer, child, metadata.getChildren().get(child.getIdentifier()));
+ }
+ writer.endOpaqueNode();
+ }
+
+ private static void writeMetadata(final OpaqueAnydataStreamWriter writer, final NormalizedMetadata metadata)
+ throws IOException {
+ if (metadata != null) {
+ writer.metadata(ImmutableMap.copyOf(metadata.getAnnotations()));
+ }
+ }
+}