26268bdd2594fbcf6b2571bfdfe346adba47f5d8
[yangtools.git] / yang / yang-data-codec-xml / src / test / java / org / opendaylight / yangtools / yang / data / codec / xml / NormalizedNodeXmlTranslationTest.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.yangtools.yang.data.codec.xml;
10
11 import static org.junit.Assert.assertNotNull;
12 import static org.opendaylight.yangtools.yang.data.impl.schema.Builders.augmentationBuilder;
13 import static org.opendaylight.yangtools.yang.data.impl.schema.Builders.choiceBuilder;
14 import static org.opendaylight.yangtools.yang.data.impl.schema.Builders.containerBuilder;
15 import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.leafNode;
16
17 import com.google.common.base.Preconditions;
18 import com.google.common.collect.ImmutableSet;
19 import java.io.FileNotFoundException;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.StringWriter;
23 import java.net.URI;
24 import java.net.URISyntaxException;
25 import java.text.ParseException;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collection;
29 import java.util.Date;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import javax.xml.stream.XMLInputFactory;
36 import javax.xml.stream.XMLOutputFactory;
37 import javax.xml.stream.XMLStreamReader;
38 import javax.xml.stream.XMLStreamWriter;
39 import javax.xml.transform.OutputKeys;
40 import javax.xml.transform.Transformer;
41 import javax.xml.transform.TransformerException;
42 import javax.xml.transform.TransformerFactory;
43 import javax.xml.transform.TransformerFactoryConfigurationError;
44 import javax.xml.transform.dom.DOMResult;
45 import javax.xml.transform.dom.DOMSource;
46 import javax.xml.transform.stream.StreamResult;
47 import org.custommonkey.xmlunit.Diff;
48 import org.custommonkey.xmlunit.ElementNameAndTextQualifier;
49 import org.custommonkey.xmlunit.IgnoreTextAndAttributeValuesDifferenceListener;
50 import org.custommonkey.xmlunit.XMLUnit;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 import org.junit.runners.Parameterized;
54 import org.opendaylight.yangtools.util.xml.UntrustedXML;
55 import org.opendaylight.yangtools.yang.common.QName;
56 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
57 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
58 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
59 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
60 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
61 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
62 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
63 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
64 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
65 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
66 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
67 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
68 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
69 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
70 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
71 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
72 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
73 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
74 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder;
75 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeBuilder;
76 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
77 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
78 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
79 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
80 import org.opendaylight.yangtools.yang.model.api.Module;
81 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
82 import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
83 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
84 import org.w3c.dom.Document;
85 import org.w3c.dom.Node;
86 import org.xml.sax.SAXException;
87
88 @RunWith(Parameterized.class)
89 public class NormalizedNodeXmlTranslationTest {
90     private final SchemaContext schema;
91
92     @Parameterized.Parameters()
93     public static Collection<Object[]> data() {
94         return Arrays.asList(new Object[][] {
95                 { "/schema/augment_choice_hell.yang", "/schema/augment_choice_hell_ok.xml", augmentChoiceHell() },
96                 { "/schema/augment_choice_hell.yang", "/schema/augment_choice_hell_ok2.xml", null },
97                 { "/schema/augment_choice_hell.yang", "/schema/augment_choice_hell_ok3.xml", augmentChoiceHell2() },
98                 { "/schema/test.yang", "/schema/simple.xml", null },
99                 { "/schema/test.yang", "/schema/simple2.xml", null },
100                 // TODO check attributes
101                 { "/schema/test.yang", "/schema/simple_xml_with_attributes.xml", withAttributes() }
102         });
103     }
104
105     private static final String NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:test";
106     private static final Date REVISION;
107
108     static {
109         try {
110             REVISION = SimpleDateFormatUtil.getRevisionFormat().parse("2014-03-13");
111         } catch (final ParseException e) {
112             throw new ExceptionInInitializerError(e);
113         }
114     }
115
116     static final XMLOutputFactory XML_FACTORY;
117
118     static {
119         XML_FACTORY = XMLOutputFactory.newFactory();
120         XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.FALSE);
121     }
122
123     private static ContainerNode augmentChoiceHell2() {
124         final NodeIdentifier container = getNodeIdentifier("container");
125         final QName augmentChoice1QName = QName.create(container.getNodeType(), "augment-choice1");
126         final QName augmentChoice2QName = QName.create(augmentChoice1QName, "augment-choice2");
127         final QName containerQName = QName.create(augmentChoice1QName, "case11-choice-case-container");
128         final QName leafQName = QName.create(augmentChoice1QName, "case11-choice-case-leaf");
129
130         final AugmentationIdentifier aug1Id = new AugmentationIdentifier(ImmutableSet.of(augmentChoice1QName));
131         final AugmentationIdentifier aug2Id = new AugmentationIdentifier(ImmutableSet.of(augmentChoice2QName));
132         final NodeIdentifier augmentChoice1Id = new NodeIdentifier(augmentChoice1QName);
133         final NodeIdentifier augmentChoice2Id = new NodeIdentifier(augmentChoice2QName);
134         final NodeIdentifier containerId = new NodeIdentifier(containerQName);
135
136         return containerBuilder().withNodeIdentifier(container)
137                 .withChild(augmentationBuilder().withNodeIdentifier(aug1Id)
138                         .withChild(choiceBuilder().withNodeIdentifier(augmentChoice1Id)
139                                 .withChild(augmentationBuilder().withNodeIdentifier(aug2Id)
140                                         .withChild(choiceBuilder().withNodeIdentifier(augmentChoice2Id)
141                                                 .withChild(containerBuilder().withNodeIdentifier(containerId)
142                                                         .withChild(leafNode(leafQName, "leaf-value"))
143                                                         .build())
144                                                 .build())
145                                         .build())
146                                 .build())
147                         .build()).build();
148     }
149
150     private static ContainerNode withAttributes() {
151         final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> b = containerBuilder();
152         b.withNodeIdentifier(getNodeIdentifier("container"));
153
154         final CollectionNodeBuilder<MapEntryNode, MapNode> listBuilder = Builders.mapBuilder().withNodeIdentifier(
155                 getNodeIdentifier("list"));
156
157         final Map<QName, Object> predicates = new HashMap<>();
158         predicates.put(getNodeIdentifier("uint32InList").getNodeType(), 3L);
159
160         final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> list1Builder = Builders
161                 .mapEntryBuilder().withNodeIdentifier(
162                         new NodeIdentifierWithPredicates(
163                                 getNodeIdentifier("list").getNodeType(), predicates));
164         final NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>> uint32InListBuilder = Builders
165                 .leafBuilder().withNodeIdentifier(getNodeIdentifier("uint32InList"));
166
167         list1Builder.withChild(uint32InListBuilder.withValue(3L).build());
168
169         listBuilder.withChild(list1Builder.build());
170         b.withChild(listBuilder.build());
171
172         final NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>> booleanBuilder = Builders
173                 .leafBuilder().withNodeIdentifier(getNodeIdentifier("boolean"));
174         booleanBuilder.withValue(Boolean.FALSE);
175         b.withChild(booleanBuilder.build());
176
177         final ListNodeBuilder<Object, LeafSetEntryNode<Object>> leafListBuilder = Builders.leafSetBuilder()
178                 .withNodeIdentifier(getNodeIdentifier("leafList"));
179
180         final NormalizedNodeBuilder<NodeWithValue, Object, LeafSetEntryNode<Object>> leafList1Builder = Builders
181                 .leafSetEntryBuilder().withNodeIdentifier(
182                         new NodeWithValue(getNodeIdentifier("leafList").getNodeType(), "a"));
183
184         leafList1Builder.withValue("a");
185
186         leafListBuilder.withChild(leafList1Builder.build());
187         b.withChild(leafListBuilder.build());
188
189         return b.build();
190     }
191
192     private static ContainerNode augmentChoiceHell() {
193
194         final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> b = containerBuilder();
195         b.withNodeIdentifier(getNodeIdentifier("container"));
196
197         b.withChild(choiceBuilder()
198                 .withNodeIdentifier(getNodeIdentifier("ch2"))
199                 .withChild(
200                         Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("c2Leaf")).withValue("2").build())
201                 .withChild(
202                         choiceBuilder()
203                                 .withNodeIdentifier(getNodeIdentifier("c2DeepChoice"))
204                                 .withChild(
205                                         Builders.leafBuilder()
206                                                 .withNodeIdentifier(getNodeIdentifier("c2DeepChoiceCase1Leaf2"))
207                                                 .withValue("2").build()).build()).build());
208
209         b.withChild(choiceBuilder()
210                 .withNodeIdentifier(getNodeIdentifier("ch3"))
211                 .withChild(
212                         Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("c3Leaf")).withValue("3").build())
213                 .build());
214
215         b.withChild(augmentationBuilder()
216                 .withNodeIdentifier(getAugmentIdentifier("augLeaf"))
217                 .withChild(
218                         Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("augLeaf")).withValue("augment")
219                                 .build()).build());
220
221         b.withChild(augmentationBuilder()
222                 .withNodeIdentifier(getAugmentIdentifier("ch"))
223                 .withChild(
224                         choiceBuilder()
225                                 .withNodeIdentifier(getNodeIdentifier("ch"))
226                                 .withChild(
227                                         Builders.leafBuilder().withNodeIdentifier(getNodeIdentifier("c1Leaf"))
228                                                 .withValue("1").build())
229                                 .withChild(
230                                         augmentationBuilder()
231                                                 .withNodeIdentifier(
232                                                         getAugmentIdentifier("c1Leaf_AnotherAugment", "deepChoice"))
233                                                 .withChild(
234                                                         Builders.leafBuilder()
235                                                                 .withNodeIdentifier(
236                                                                         getNodeIdentifier("c1Leaf_AnotherAugment"))
237                                                                 .withValue("1").build())
238                                                 .withChild(
239                                                         choiceBuilder()
240                                                                 .withNodeIdentifier(getNodeIdentifier("deepChoice"))
241                                                                 .withChild(
242                                                                         Builders.leafBuilder()
243                                                                                 .withNodeIdentifier(
244                                                                                         getNodeIdentifier("deepLeafc1"))
245                                                                                 .withValue("1").build()).build())
246                                                 .build()).build()).build());
247
248         return b.build();
249     }
250
251     private static NodeIdentifier getNodeIdentifier(final String localName) {
252         return new NodeIdentifier(QName.create(URI.create(NAMESPACE), REVISION, localName));
253     }
254
255     private static AugmentationIdentifier getAugmentIdentifier(final String... childNames) {
256         final Set<QName> qn = new HashSet<>();
257
258         for (final String childName : childNames) {
259             qn.add(getNodeIdentifier(childName).getNodeType());
260         }
261
262         return new AugmentationIdentifier(qn);
263     }
264
265     public NormalizedNodeXmlTranslationTest(final String yangPath, final String xmlPath,
266             final ContainerNode expectedNode) throws ReactorException, FileNotFoundException, URISyntaxException {
267         this.schema = YangParserTestUtils.parseYangSource(yangPath);
268         this.xmlPath = xmlPath;
269         this.containerNode = (ContainerSchemaNode) getSchemaNode(schema, "test", "container");
270         this.expectedNode = expectedNode;
271     }
272
273     private final ContainerNode expectedNode;
274     private final ContainerSchemaNode containerNode;
275     private final String xmlPath;
276
277     @Test
278     public void testTranslation() throws Exception {
279         final InputStream resourceAsStream = XmlToNormalizedNodesTest.class.getResourceAsStream(xmlPath);
280
281         final XMLInputFactory factory = XMLInputFactory.newInstance();
282         final XMLStreamReader reader = factory.createXMLStreamReader(resourceAsStream);
283
284         final NormalizedNodeResult result = new NormalizedNodeResult();
285         final NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result);
286
287         final XmlParserStream xmlParser = XmlParserStream.create(streamWriter, schema, containerNode);
288         xmlParser.parse(reader);
289
290         final NormalizedNode<?, ?> built = result.getResult();
291         assertNotNull(built);
292
293         if (expectedNode != null) {
294             org.junit.Assert.assertEquals(expectedNode, built);
295         }
296
297         final Document document = UntrustedXML.newDocumentBuilder().newDocument();
298         final DOMResult domResult = new DOMResult(document);
299
300         final XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
301         outputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE);
302
303         final XMLStreamWriter xmlStreamWriter = outputFactory.createXMLStreamWriter(domResult);
304
305         final NormalizedNodeStreamWriter xmlNormalizedNodeStreamWriter = XMLStreamNormalizedNodeStreamWriter
306                 .create(xmlStreamWriter, schema);
307
308         final NormalizedNodeWriter normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter(
309                 xmlNormalizedNodeStreamWriter);
310
311         normalizedNodeWriter.write(built);
312
313         final Document doc = loadDocument(xmlPath);
314
315         XMLUnit.setIgnoreWhitespace(true);
316         XMLUnit.setIgnoreComments(true);
317         XMLUnit.setIgnoreAttributeOrder(true);
318         XMLUnit.setNormalize(true);
319
320         final String expectedXml = toString(doc.getDocumentElement());
321         final String serializedXml = toString(domResult.getNode());
322
323         final Diff diff = new Diff(expectedXml, serializedXml);
324         diff.overrideDifferenceListener(new IgnoreTextAndAttributeValuesDifferenceListener());
325         diff.overrideElementQualifier(new ElementNameAndTextQualifier());
326
327         // FIXME the comparison cannot be performed, since the qualifiers supplied by XMlUnit do not work correctly in
328         // this case
329         // We need to implement custom qualifier so that the element ordering does not mess the DIFF
330         // dd.overrideElementQualifier(new MultiLevelElementNameAndTextQualifier(100, true));
331         // assertTrue(dd.toString(), dd.similar());
332
333         //new XMLTestCase() {}.assertXMLEqual(diff, true);
334     }
335
336     private static Document loadDocument(final String xmlPath) throws IOException, SAXException {
337         final InputStream resourceAsStream = NormalizedNodeXmlTranslationTest.class.getResourceAsStream(xmlPath);
338
339         final Document currentConfigElement = readXmlToDocument(resourceAsStream);
340         Preconditions.checkNotNull(currentConfigElement);
341         return currentConfigElement;
342     }
343
344     private static Document readXmlToDocument(final InputStream xmlContent) throws IOException, SAXException {
345         final Document doc = UntrustedXML.newDocumentBuilder().parse(xmlContent);
346         doc.getDocumentElement().normalize();
347         return doc;
348     }
349
350     private static String toString(final Node xml) {
351         try {
352             final Transformer transformer = TransformerFactory.newInstance().newTransformer();
353             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
354             transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
355
356             final StreamResult result = new StreamResult(new StringWriter());
357             final DOMSource source = new DOMSource(xml);
358             transformer.transform(source, result);
359
360             return result.getWriter().toString();
361         } catch (IllegalArgumentException | TransformerFactoryConfigurationError | TransformerException e) {
362             throw new RuntimeException("Unable to serialize xml element " + xml, e);
363         }
364     }
365
366     private static DataSchemaNode getSchemaNode(final SchemaContext context, final String moduleName,
367                                                final String childNodeName) {
368         for (Module module : context.getModules()) {
369             if (module.getName().equals(moduleName)) {
370                 DataSchemaNode found = findChildNode(module.getChildNodes(), childNodeName);
371                 Preconditions.checkState(found != null, "Unable to find %s", childNodeName);
372                 return found;
373             }
374         }
375         throw new IllegalStateException("Unable to find child node " + childNodeName);
376     }
377
378     private static DataSchemaNode findChildNode(final Iterable<DataSchemaNode> children, final String name) {
379         List<DataNodeContainer> containers = new ArrayList<>();
380
381         for (DataSchemaNode dataSchemaNode : children) {
382             if (dataSchemaNode.getQName().getLocalName().equals(name)) {
383                 return dataSchemaNode;
384             }
385             if (dataSchemaNode instanceof DataNodeContainer) {
386                 containers.add((DataNodeContainer) dataSchemaNode);
387             } else if (dataSchemaNode instanceof ChoiceSchemaNode) {
388                 containers.addAll(((ChoiceSchemaNode) dataSchemaNode).getCases());
389             }
390         }
391
392         for (DataNodeContainer container : containers) {
393             DataSchemaNode retVal = findChildNode(container.getChildNodes(), name);
394             if (retVal != null) {
395                 return retVal;
396             }
397         }
398
399         return null;
400     }
401
402 }