afcccda9a791db013fbe769ef8bfce6e40da72a0
[transportpce.git] / common / src / main / java / org / opendaylight / transportpce / common / converter / XMLDataObjectConverter.java
1 /*
2  * Copyright © 2016 AT&T 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 package org.opendaylight.transportpce.common.converter;
9
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.io.Reader;
13 import java.io.StringWriter;
14 import java.io.Writer;
15 import java.net.URISyntaxException;
16 import java.util.Optional;
17 import javax.annotation.Nonnull;
18 import javax.xml.XMLConstants;
19 import javax.xml.parsers.FactoryConfigurationError;
20 import javax.xml.stream.XMLInputFactory;
21 import javax.xml.stream.XMLOutputFactory;
22 import javax.xml.stream.XMLStreamException;
23 import javax.xml.stream.XMLStreamReader;
24 import javax.xml.stream.XMLStreamWriter;
25 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
26 import org.opendaylight.transportpce.common.DataStoreContext;
27 import org.opendaylight.yangtools.yang.binding.DataObject;
28 import org.opendaylight.yangtools.yang.common.QName;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
33 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
34 import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
35 import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
36 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
37 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
38 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
39 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43 import org.xml.sax.SAXException;
44
45 public final class XMLDataObjectConverter extends AbstractDataObjectConverter {
46
47     private static final Logger LOG = LoggerFactory.getLogger(XMLDataObjectConverter.class);
48
49     private final XMLInputFactory xmlInputFactory;
50
51     /**
52      * This is the default constructor, which should be used.
53      *
54      * @param schemaContext schema context for converter
55      * @param codecRegistry codec registry used for converting
56      *
57      */
58     private XMLDataObjectConverter(SchemaContext schemaContext, BindingNormalizedNodeSerializer codecRegistry) {
59         super(schemaContext, codecRegistry);
60         this.xmlInputFactory = XMLInputFactory.newInstance();
61         // set external DTD and schema to null to avoid vulnerability (sonar report)
62         this.xmlInputFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
63         this.xmlInputFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
64     }
65
66     /**
67      * Extract codec and schema context (?).
68      *
69      * @param dataStoreContextUtil datastore context util used to extract codec and schema context
70      * @return {@link AbstractDataObjectConverter}
71      */
72     public static XMLDataObjectConverter createWithDataStoreUtil(@Nonnull DataStoreContext dataStoreContextUtil) {
73         BindingNormalizedNodeSerializer bindingToNormalizedNodeCodec =
74                 dataStoreContextUtil.getBindingToNormalizedNodeCodec();
75         return new XMLDataObjectConverter(dataStoreContextUtil.getSchemaContext(), bindingToNormalizedNodeCodec);
76     }
77
78     /**
79      * Extract codec and schema context (?).
80      *
81      * @param schemaContext schema context for converter
82      * @param codecRegistry codec registry used for converting
83      * @return new {@link XMLDataObjectConverter}
84      */
85     public static XMLDataObjectConverter createWithSchemaContext(@Nonnull SchemaContext schemaContext,
86             @Nonnull BindingNormalizedNodeSerializer codecRegistry) {
87         return new XMLDataObjectConverter(schemaContext, codecRegistry);
88     }
89
90     /**
91      * Transforms the XML input stream into normalized nodes.
92      *
93      * @param inputStream of the given XML
94      * @return {@link Optional} instance of {@link NormalizedNode}.
95      */
96     @Override
97     public Optional<NormalizedNode<? extends YangInstanceIdentifier.PathArgument, ?>> transformIntoNormalizedNode(
98             @Nonnull InputStream inputStream) {
99         try {
100             XMLStreamReader reader = this.xmlInputFactory.createXMLStreamReader(inputStream);
101             return parseInputXML(reader);
102         } catch (XMLStreamException e) {
103             LOG.warn("XMLStreamException: {}", e.getMessage());
104             return Optional.empty();
105         }
106     }
107
108     public Optional<NormalizedNode<? extends YangInstanceIdentifier.PathArgument, ?>> transformIntoNormalizedNode(
109             @Nonnull Reader inputReader, SchemaNode parentSchema) {
110         try {
111             XMLStreamReader reader = this.xmlInputFactory.createXMLStreamReader(inputReader);
112             return parseInputXML(reader, parentSchema);
113         } catch (XMLStreamException e) {
114             LOG.warn("XMLStreamException: {}", e.getMessage());
115             return Optional.empty();
116         }
117     }
118
119     /**
120      * Transforms the XML input stream into normalized nodes.
121      *
122      * @param inputReader of the given XML
123      * @return {@link Optional} instance of {@link NormalizedNode}.
124      */
125     @Override
126     public Optional<NormalizedNode<? extends YangInstanceIdentifier.PathArgument, ?>> transformIntoNormalizedNode(
127             @Nonnull Reader inputReader) {
128         try {
129             XMLStreamReader reader = this.xmlInputFactory.createXMLStreamReader(inputReader);
130             return parseInputXML(reader);
131         } catch (XMLStreamException e) {
132             LOG.warn("XMLStreamException: {}", e.getMessage());
133             return Optional.empty();
134         }
135     }
136
137     @Override
138     public <T extends DataObject> Writer writerFromRpcDataObject(@Nonnull DataObject object, Class<T> dataObjectClass,
139             ConvertType<T> convertType, QName rpcOutputQName, String rpcName) {
140         Writer writer = new StringWriter();
141         XMLStreamWriter xmlStreamWriter = createXmlStreamWriter(writer);
142         SchemaPath rpcOutputSchemaPath = SchemaPath.create(true, QName.create(rpcOutputQName.getModule(), rpcName),
143                 rpcOutputQName);
144         try (NormalizedNodeWriter normalizedNodeWriter = createWriterBackedNormalizedNodeWriter(xmlStreamWriter,
145                 rpcOutputSchemaPath)) {
146             xmlStreamWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX,
147                     rpcOutputQName.getLocalName(), rpcOutputQName.getNamespace().toString());
148             xmlStreamWriter.writeDefaultNamespace(rpcOutputQName.getNamespace().toString());
149             NormalizedNode<?, ?> rpcOutputNormalizedNode = convertType.toNormalizedNodes(dataObjectClass.cast(object),
150                     dataObjectClass).get();
151             for (final NormalizedNode<?, ?> child : ((ContainerNode)rpcOutputNormalizedNode).getValue()) {
152                 normalizedNodeWriter.write(child);
153             }
154             normalizedNodeWriter.flush();
155             xmlStreamWriter.writeEndElement();
156             xmlStreamWriter.flush();
157         } catch (IOException | XMLStreamException ioe) {
158             throw new IllegalStateException(ioe);
159         }
160         return writer;
161     }
162
163     /**
164      * Returns a {@link Writer}.
165      *
166      * @param convertType converter used of converting into normalized node
167      * @param dataObjectClass class of converting object
168      * @param object object you want to convert
169      *
170      */
171     @Override
172     public <T extends DataObject> Writer writerFromDataObject(@Nonnull DataObject object, Class<T> dataObjectClass,
173             ConvertType<T> convertType) {
174         Writer writer = new StringWriter();
175
176         try (NormalizedNodeWriter normalizedNodeWriter = createWriterBackedNormalizedNodeWriter(writer, null)) {
177             normalizedNodeWriter
178                     .write(convertType.toNormalizedNodes(dataObjectClass.cast(object), dataObjectClass).get());
179             normalizedNodeWriter.flush();
180         } catch (IOException ioe) {
181             throw new IllegalStateException(ioe);
182         }
183         return writer;
184     }
185
186     private Optional<NormalizedNode<? extends YangInstanceIdentifier.PathArgument, ?>> parseInputXML(
187             XMLStreamReader reader) {
188         return parseInputXML(reader, getSchemaContext());
189     }
190
191     private Optional<NormalizedNode<? extends YangInstanceIdentifier.PathArgument, ?>> parseInputXML(
192             XMLStreamReader reader, SchemaNode parentSchemaNode) {
193         NormalizedNodeResult result = new NormalizedNodeResult();
194         try (NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result);
195              XmlParserStream xmlParser = XmlParserStream.create(streamWriter, getSchemaContext(), parentSchemaNode)) {
196             xmlParser.parse(reader);
197         } catch (XMLStreamException | URISyntaxException | IOException | SAXException e) {
198             LOG.warn("An error occured during parsing XML input stream", e);
199             return Optional.empty();
200         }
201         return Optional.ofNullable(result.getResult());
202     }
203
204     private NormalizedNodeWriter createWriterBackedNormalizedNodeWriter(Writer backingWriter, SchemaPath pathToParent) {
205         XMLStreamWriter createXMLStreamWriter = createXmlStreamWriter(backingWriter);
206         NormalizedNodeStreamWriter streamWriter;
207         if (pathToParent == null) {
208             streamWriter = XMLStreamNormalizedNodeStreamWriter.create(createXMLStreamWriter,
209                     getSchemaContext());
210         } else {
211             streamWriter = XMLStreamNormalizedNodeStreamWriter.create(createXMLStreamWriter,
212                     getSchemaContext(), pathToParent);
213         }
214         return NormalizedNodeWriter.forStreamWriter(streamWriter);
215     }
216
217     private NormalizedNodeWriter createWriterBackedNormalizedNodeWriter(XMLStreamWriter backingWriter,
218             SchemaPath pathToParent) {
219         NormalizedNodeStreamWriter streamWriter;
220         if (pathToParent == null) {
221             streamWriter = XMLStreamNormalizedNodeStreamWriter.create(backingWriter,
222                     getSchemaContext());
223         } else {
224             streamWriter = XMLStreamNormalizedNodeStreamWriter.create(backingWriter,
225                     getSchemaContext(), pathToParent);
226         }
227         return NormalizedNodeWriter.forStreamWriter(streamWriter);
228     }
229
230     private static XMLStreamWriter createXmlStreamWriter(Writer backingWriter) {
231         XMLStreamWriter xmlStreamWriter;
232         try {
233             XMLOutputFactory factory = XMLOutputFactory.newFactory();
234             factory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
235             xmlStreamWriter = factory.createXMLStreamWriter(backingWriter);
236         } catch (XMLStreamException | FactoryConfigurationError e) {
237             LOG.error("Error while creating XML writer: ", e);
238             throw new IllegalStateException(e);
239         }
240         return xmlStreamWriter;
241     }
242 }