2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.serializer;
10 import static org.opendaylight.yangtools.yang.data.impl.schema.Builders.augmentationBuilder;
11 import static org.opendaylight.yangtools.yang.data.impl.schema.Builders.choiceBuilder;
12 import static org.opendaylight.yangtools.yang.data.impl.schema.Builders.containerBuilder;
13 import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.leafNode;
14 import com.google.common.base.Preconditions;
15 import com.google.common.collect.Collections2;
16 import com.google.common.collect.Lists;
17 import com.google.common.collect.Maps;
18 import com.google.common.collect.Sets;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.StringWriter;
23 import java.text.ParseException;
24 import java.text.SimpleDateFormat;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.Date;
29 import java.util.List;
32 import javax.xml.parsers.DocumentBuilder;
33 import javax.xml.parsers.DocumentBuilderFactory;
34 import javax.xml.parsers.ParserConfigurationException;
35 import javax.xml.stream.XMLOutputFactory;
36 import javax.xml.stream.XMLStreamException;
37 import javax.xml.stream.XMLStreamWriter;
38 import javax.xml.transform.OutputKeys;
39 import javax.xml.transform.Transformer;
40 import javax.xml.transform.TransformerException;
41 import javax.xml.transform.TransformerFactory;
42 import javax.xml.transform.TransformerFactoryConfigurationError;
43 import javax.xml.transform.dom.DOMResult;
44 import javax.xml.transform.dom.DOMSource;
45 import javax.xml.transform.stream.StreamResult;
46 import org.custommonkey.xmlunit.Diff;
47 import org.custommonkey.xmlunit.XMLUnit;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 import org.junit.runners.Parameterized;
51 import org.opendaylight.yangtools.yang.common.QName;
52 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
53 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
54 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
55 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
56 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
57 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
58 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
59 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
60 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
61 import org.opendaylight.yangtools.yang.data.impl.TestUtils;
62 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter;
63 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils;
64 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
65 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedDataBuilderTest;
66 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
67 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
68 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder;
69 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeBuilder;
70 import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.DomUtils;
71 import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
72 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
73 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
74 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
75 import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
76 import org.slf4j.Logger;
77 import org.slf4j.LoggerFactory;
78 import org.w3c.dom.Document;
79 import org.w3c.dom.Element;
80 import org.xml.sax.SAXException;
82 @RunWith(Parameterized.class)
83 public class NormalizedNodeXmlTranslationTest {
84 private static final Logger logger = LoggerFactory.getLogger(NormalizedNodeXmlTranslationTest.class);
85 private final SchemaContext schema;
87 @Parameterized.Parameters()
88 public static Collection<Object[]> data() {
89 return Arrays.asList(new Object[][] {
90 { "augment_choice_hell.yang", "augment_choice_hell_ok.xml", augmentChoiceHell() },
91 { "augment_choice_hell.yang", "augment_choice_hell_ok2.xml", null },
92 { "augment_choice_hell.yang", "augment_choice_hell_ok3.xml", augmentChoiceHell2() },
93 { "test.yang", "simple.xml", null }, { "test.yang", "simple2.xml", null },
94 // TODO check attributes
95 { "test.yang", "simple_xml_with_attributes.xml", withAttributes() }
99 public static final String NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:test";
100 private static Date revision;
103 revision = new SimpleDateFormat("yyyy-MM-dd").parse("2014-03-13");
104 } catch (final ParseException e) {
105 throw new RuntimeException(e);
109 private static ContainerNode augmentChoiceHell2() {
110 final YangInstanceIdentifier.NodeIdentifier container = getNodeIdentifier("container");
111 QName augmentChoice1QName = QName.create(container.getNodeType(), "augment-choice1");
112 QName augmentChoice2QName = QName.create(augmentChoice1QName, "augment-choice2");
113 final QName containerQName = QName.create(augmentChoice1QName, "case11-choice-case-container");
114 final QName leafQName = QName.create(augmentChoice1QName, "case11-choice-case-leaf");
116 final YangInstanceIdentifier.AugmentationIdentifier aug1Id = new YangInstanceIdentifier.AugmentationIdentifier(
117 Sets.newHashSet(augmentChoice1QName));
118 final YangInstanceIdentifier.AugmentationIdentifier aug2Id = new YangInstanceIdentifier.AugmentationIdentifier(
119 Sets.newHashSet(augmentChoice2QName));
120 final YangInstanceIdentifier.NodeIdentifier augmentChoice1Id = new YangInstanceIdentifier.NodeIdentifier(
121 augmentChoice1QName);
122 final YangInstanceIdentifier.NodeIdentifier augmentChoice2Id = new YangInstanceIdentifier.NodeIdentifier(
123 augmentChoice2QName);
124 final YangInstanceIdentifier.NodeIdentifier containerId = new YangInstanceIdentifier.NodeIdentifier(
127 return containerBuilder().withNodeIdentifier(container)
128 .withChild(augmentationBuilder().withNodeIdentifier(aug1Id)
129 .withChild(choiceBuilder().withNodeIdentifier(augmentChoice1Id)
130 .withChild(augmentationBuilder().withNodeIdentifier(aug2Id)
131 .withChild(choiceBuilder().withNodeIdentifier(augmentChoice2Id)
132 .withChild(containerBuilder().withNodeIdentifier(containerId)
133 .withChild(leafNode(leafQName, "leaf-value"))
141 private static ContainerNode withAttributes() {
142 final DataContainerNodeBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode> b = containerBuilder();
143 b.withNodeIdentifier(getNodeIdentifier("container"));
145 final CollectionNodeBuilder<MapEntryNode, MapNode> listBuilder = Builders.mapBuilder().withNodeIdentifier(
146 getNodeIdentifier("list"));
148 final Map<QName, Object> predicates = Maps.newHashMap();
149 predicates.put(getNodeIdentifier("uint32InList").getNodeType(), 3L);
151 final DataContainerNodeBuilder<YangInstanceIdentifier.NodeIdentifierWithPredicates, MapEntryNode> list1Builder = Builders
152 .mapEntryBuilder().withNodeIdentifier(
153 new YangInstanceIdentifier.NodeIdentifierWithPredicates(
154 getNodeIdentifier("list").getNodeType(), predicates));
155 final NormalizedNodeBuilder<YangInstanceIdentifier.NodeIdentifier, Object, LeafNode<Object>> uint32InListBuilder = Builders
156 .leafBuilder().withNodeIdentifier(getNodeIdentifier("uint32InList"));
158 list1Builder.withChild(uint32InListBuilder.withValue(3L).build());
160 listBuilder.withChild(list1Builder.build());
161 b.withChild(listBuilder.build());
163 final NormalizedNodeBuilder<YangInstanceIdentifier.NodeIdentifier, Object, LeafNode<Object>> booleanBuilder = Builders
164 .leafBuilder().withNodeIdentifier(getNodeIdentifier("boolean"));
165 booleanBuilder.withValue(false);
166 b.withChild(booleanBuilder.build());
168 final ListNodeBuilder<Object, LeafSetEntryNode<Object>> leafListBuilder = Builders.leafSetBuilder()
169 .withNodeIdentifier(getNodeIdentifier("leafList"));
171 final NormalizedNodeBuilder<YangInstanceIdentifier.NodeWithValue, Object, LeafSetEntryNode<Object>> leafList1Builder = Builders
172 .leafSetEntryBuilder().withNodeIdentifier(
173 new YangInstanceIdentifier.NodeWithValue(getNodeIdentifier("leafList").getNodeType(), "a"));
175 leafList1Builder.withValue("a");
177 leafListBuilder.withChild(leafList1Builder.build());
178 b.withChild(leafListBuilder.build());
183 private static ContainerNode augmentChoiceHell() {
185 final DataContainerNodeBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode> b = containerBuilder();
186 b.withNodeIdentifier(getNodeIdentifier("container"));
188 b.withChild(choiceBuilder()
189 .withNodeIdentifier(getNodeIdentifier("ch2"))
191 Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("c2Leaf")).withValue("2").build())
194 .withNodeIdentifier(getNodeIdentifier("c2DeepChoice"))
196 Builders.leafBuilder()
197 .withNodeIdentifier(getNodeIdentifier("c2DeepChoiceCase1Leaf2"))
198 .withValue("2").build()).build()).build());
200 b.withChild(choiceBuilder()
201 .withNodeIdentifier(getNodeIdentifier("ch3"))
203 Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("c3Leaf")).withValue("3").build())
206 b.withChild(augmentationBuilder()
207 .withNodeIdentifier(getAugmentIdentifier("augLeaf"))
209 Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("augLeaf")).withValue("augment")
212 b.withChild(augmentationBuilder()
213 .withNodeIdentifier(getAugmentIdentifier("ch"))
216 .withNodeIdentifier(getNodeIdentifier("ch"))
218 Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("c1Leaf"))
219 .withValue("1").build())
221 augmentationBuilder()
223 getAugmentIdentifier("c1Leaf_AnotherAugment", "deepChoice"))
225 Builders.leafBuilder()
227 getNodeIdentifier("c1Leaf_AnotherAugment"))
228 .withValue("1").build())
231 .withNodeIdentifier(getNodeIdentifier("deepChoice"))
233 Builders.leafBuilder()
235 getNodeIdentifier("deepLeafc1"))
236 .withValue("1").build()).build())
237 .build()).build()).build());
242 private static YangInstanceIdentifier.NodeIdentifier getNodeIdentifier(final String localName) {
243 return new YangInstanceIdentifier.NodeIdentifier(QName.create(URI.create(NAMESPACE), revision, localName));
246 public static YangInstanceIdentifier.AugmentationIdentifier getAugmentIdentifier(final String... childNames) {
247 final Set<QName> qn = Sets.newHashSet();
249 for (final String childName : childNames) {
250 qn.add(getNodeIdentifier(childName).getNodeType());
253 return new YangInstanceIdentifier.AugmentationIdentifier(qn);
256 public NormalizedNodeXmlTranslationTest(final String yangPath, final String xmlPath,
257 final ContainerNode expectedNode) throws ReactorException {
258 schema = parseTestSchema(yangPath);
259 this.xmlPath = xmlPath;
260 this.containerNode = (ContainerSchemaNode) NormalizedDataBuilderTest.getSchemaNode(schema, "test", "container");
261 this.expectedNode = expectedNode;
264 private final ContainerNode expectedNode;
265 private final ContainerSchemaNode containerNode;
266 private final String xmlPath;
268 SchemaContext parseTestSchema(final String... yangPath) throws ReactorException {
269 return TestUtils.parseYangStreams(getTestYangs(yangPath));
272 List<InputStream> getTestYangs(final String... yangPaths) {
274 return Lists.newArrayList(Collections2.transform(Lists.newArrayList(yangPaths),
276 final InputStream resourceAsStream = NormalizedDataBuilderTest.class.getResourceAsStream(input);
277 Preconditions.checkNotNull(resourceAsStream, "File %s was null", resourceAsStream);
278 return resourceAsStream;
283 public void testTranslation() throws Exception {
284 final Document doc = loadDocument(xmlPath);
286 final ContainerNode built = DomToNormalizedNodeParserFactory
287 .getInstance(DomUtils.defaultValueCodecProvider(), schema).getContainerNodeParser()
288 .parse(Collections.singletonList(doc.getDocumentElement()), containerNode);
290 if (expectedNode != null) {
291 org.junit.Assert.assertEquals(expectedNode, built);
294 System.err.println(built);
295 logger.info("{}", built);
297 final Element elementNS = XmlDocumentUtils.getDocument().createElementNS(
298 containerNode.getQName().getNamespace().toString(), containerNode.getQName().getLocalName());
299 writeNormalizedNode(built, new DOMResult(elementNS), SchemaPath.create(true), schema);
301 XMLUnit.setIgnoreWhitespace(true);
302 XMLUnit.setIgnoreComments(true);
303 XMLUnit.setIgnoreAttributeOrder(true);
304 XMLUnit.setNormalize(true);
306 System.err.println(toString(doc.getDocumentElement()));
307 System.err.println(toString(elementNS));
309 final Diff diff = new Diff(XMLUnit.buildControlDocument(toString(doc.getDocumentElement())),
310 XMLUnit.buildTestDocument(toString(elementNS)));
312 // FIXME the comparison cannot be performed, since the qualifiers supplied by XMlUnit do not work correctly in
314 // We need to implement custom qualifier so that the element ordering does not mess the DIFF
315 // dd.overrideElementQualifier(new MultiLevelElementNameAndTextQualifier(100, true));
316 // assertTrue(dd.toString(), dd.similar());
319 static final XMLOutputFactory XML_FACTORY;
321 XML_FACTORY = XMLOutputFactory.newFactory();
322 XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, false);
325 private static void writeNormalizedNode(final NormalizedNode<?, ?> normalized, final DOMResult result,
326 final SchemaPath schemaPath, final SchemaContext context) throws IOException, XMLStreamException {
327 NormalizedNodeWriter normalizedNodeWriter = null;
328 NormalizedNodeStreamWriter normalizedNodeStreamWriter = null;
329 XMLStreamWriter writer = null;
331 writer = XML_FACTORY.createXMLStreamWriter(result);
332 normalizedNodeStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(writer, context, schemaPath);
333 normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter(normalizedNodeStreamWriter);
335 normalizedNodeWriter.write(normalized);
337 normalizedNodeWriter.flush();
339 if (normalizedNodeWriter != null) {
340 normalizedNodeWriter.close();
342 if (normalizedNodeStreamWriter != null) {
343 normalizedNodeStreamWriter.close();
345 if (writer != null) {
351 private static Document loadDocument(final String xmlPath) throws IOException, SAXException {
352 final InputStream resourceAsStream = NormalizedDataBuilderTest.class.getResourceAsStream(xmlPath);
354 final Document currentConfigElement = readXmlToDocument(resourceAsStream);
355 Preconditions.checkNotNull(currentConfigElement);
356 return currentConfigElement;
359 private static final DocumentBuilderFactory BUILDERFACTORY;
362 final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
363 factory.setNamespaceAware(true);
364 factory.setCoalescing(true);
365 factory.setIgnoringElementContentWhitespace(true);
366 factory.setIgnoringComments(true);
367 BUILDERFACTORY = factory;
370 private static Document readXmlToDocument(final InputStream xmlContent) throws IOException, SAXException {
371 final DocumentBuilder dBuilder;
373 dBuilder = BUILDERFACTORY.newDocumentBuilder();
374 } catch (final ParserConfigurationException e) {
375 throw new RuntimeException("Failed to parse XML document", e);
377 final Document doc = dBuilder.parse(xmlContent);
379 doc.getDocumentElement().normalize();
383 public static String toString(final Element xml) {
385 final Transformer transformer = TransformerFactory.newInstance().newTransformer();
386 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
388 final StreamResult result = new StreamResult(new StringWriter());
389 final DOMSource source = new DOMSource(xml);
390 transformer.transform(source, result);
392 return result.getWriter().toString();
393 } catch (IllegalArgumentException | TransformerFactoryConfigurationError | TransformerException e) {
394 throw new RuntimeException("Unable to serialize xml element " + xml, e);