da096e5461cdcb3c5eb0557ef045e1f28a74bd06
[so.git] / bpmn / MSOCoreBPMN / src / main / java / org / onap / so / bpmn / core / xml / XmlTool.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright (C) 2017 Huawei Technologies Co., Ltd. All rights reserved.
7  * ================================================================================
8  * Modifications Copyright (c) 2019 Samsung
9  * ================================================================================
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  * 
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  * 
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  * ============LICENSE_END=========================================================
22  */
23
24 package org.onap.so.bpmn.core.xml;
25
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.io.Reader;
30 import java.io.StringReader;
31 import java.io.StringWriter;
32 import java.util.HashMap;
33 import java.util.Map;
34 import java.util.Optional;
35 import javax.xml.XMLConstants;
36 import javax.xml.parsers.DocumentBuilder;
37 import javax.xml.parsers.DocumentBuilderFactory;
38 import javax.xml.parsers.ParserConfigurationException;
39 import javax.xml.transform.OutputKeys;
40 import javax.xml.transform.Source;
41 import javax.xml.transform.Transformer;
42 import javax.xml.transform.TransformerException;
43 import javax.xml.transform.TransformerFactory;
44 import javax.xml.transform.dom.DOMSource;
45 import javax.xml.transform.stream.StreamResult;
46 import javax.xml.transform.stream.StreamSource;
47 import javax.xml.xpath.XPath;
48 import javax.xml.xpath.XPathConstants;
49 import javax.xml.xpath.XPathExpressionException;
50 import javax.xml.xpath.XPathFactory;
51 import org.apache.commons.lang3.StringEscapeUtils;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54 import org.w3c.dom.Document;
55 import org.w3c.dom.Node;
56 import org.w3c.dom.NodeList;
57 import org.xml.sax.InputSource;
58 import org.xml.sax.SAXException;
59
60 /**
61  * XML transformation methods and other useful functions.
62  */
63 public final class XmlTool {
64
65     private static final Map<String, Integer> ENTITIES = new HashMap<>();
66     private static final Logger logger = LoggerFactory.getLogger(XmlTool.class);
67     static {
68         ENTITIES.put("amp", 38);
69         ENTITIES.put("quot", 34);
70         ENTITIES.put("lt", 60);
71         ENTITIES.put("gt", 62);
72     }
73
74     /**
75      * Instantiation is not allowed.
76      */
77     private XmlTool() {}
78
79     /**
80      * Normalizes and formats XML. This method consolidates and moves all namespace declarations to the root element.
81      * The result will not have an XML prolog or a trailing newline.
82      * 
83      * @param xml the XML to normalize
84      * @throws IOException
85      * @throws TransformerException
86      * @throws ParserConfigurationException
87      * @throws SAXException
88      * @throws XPathExpressionException
89      */
90     public static String normalize(Object xml) throws IOException, TransformerException, ParserConfigurationException,
91             SAXException, XPathExpressionException {
92
93         if (xml == null) {
94             return null;
95         }
96
97         Source xsltSource = new StreamSource(new StringReader(readResourceFile("normalize-namespaces.xsl")));
98
99         DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
100         dbFactory.setNamespaceAware(true);
101         dbFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
102         dbFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
103         DocumentBuilder db = dbFactory.newDocumentBuilder();
104         InputSource source = new InputSource(new StringReader(String.valueOf(xml)));
105         Document doc = db.parse(source);
106
107         // Start of code to remove whitespace outside of tags
108         XPath xPath = XPathFactory.newInstance().newXPath();
109         NodeList nodeList = (NodeList) xPath.evaluate("//text()[normalize-space()='']", doc, XPathConstants.NODESET);
110
111         for (int i = 0; i < nodeList.getLength(); ++i) {
112             Node node = nodeList.item(i);
113             node.getParentNode().removeChild(node);
114         }
115         // End of code to remove whitespace outside of tags
116
117         // the factory pattern supports different XSLT processors
118         TransformerFactory transformerFactory = TransformerFactory.newInstance();
119         transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
120         Transformer transformer = transformerFactory.newTransformer(xsltSource);
121
122         transformer.setOutputProperty(OutputKeys.INDENT, "yes");
123         transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
124         transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
125         transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
126
127         StringWriter writer = new StringWriter();
128         transformer.transform(new DOMSource(doc), new StreamResult(writer));
129         return writer.toString().trim();
130     }
131
132     /**
133      * Encodes a value so it can be used inside an XML text element.
134      * 
135      * @param value the string to encode
136      * @return the encoded string
137      */
138     public static String encode(Object value) {
139         if (value == null) {
140             return null;
141         }
142         return StringEscapeUtils.escapeXml11(value.toString());
143     }
144
145     /**
146      * Removes the preamble, if present, from an XML document.
147      * 
148      * @param xml the XML document
149      * @return a possibly modified document
150      */
151     public static String removePreamble(Object xml) {
152         if (xml == null) {
153             return null;
154         }
155
156         return String.valueOf(xml).replaceAll("(<\\?[^<]*\\?>\\s*[\\r\\n]*)?", "");
157     }
158
159     /**
160      * Removes namespaces and namespace declarations from an XML document.
161      * 
162      * @param xml the XML document
163      * @return a possibly modified document
164      */
165     public static String removeNamespaces(Object xml) {
166         if (xml == null) {
167             logger.debug("removeNamespaces input object is null , returning null");
168             return null;
169         }
170
171         String text = String.valueOf(xml);
172
173         // remove xmlns declaration
174         text = text.replaceAll("xmlns.*?(\"|\').*?(\"|\')", "");
175         // remove opening tag prefix
176         text = text.replaceAll("(<)(\\w+:)(.*?>)", "$1$3");
177         // remove closing tags prefix
178         text = text.replaceAll("(</)(\\w+:)(.*?>)", "$1$3");
179         // remove extra spaces left when xmlns declarations are removed
180         text = text.replaceAll("\\s+>", ">");
181
182         return text;
183     }
184
185
186     /**
187      * Reads the specified resource file and return the contents as a string.
188      * 
189      * @param file Name of the resource file
190      * @return the contents of the resource file as a String
191      * @throws IOException if there is a problem reading the file
192      */
193     private static String readResourceFile(String file) throws IOException {
194
195         try (InputStream stream = XmlTool.class.getClassLoader().getResourceAsStream(file);
196                 Reader reader = new InputStreamReader(stream, "UTF-8")) {
197
198             StringBuilder out = new StringBuilder();
199             char[] buf = new char[1024];
200             int n;
201
202             while ((n = reader.read(buf)) >= 0) {
203                 out.append(buf, 0, n);
204             }
205             return out.toString();
206         } catch (Exception e) {
207             logger.debug("Exception at readResourceFile stream: " + e);
208             return null;
209         }
210     }
211
212     /**
213      * Parses the XML document String for the first occurrence of the specified element tag. If found, the value
214      * associated with that element tag is replaced with the new value and a String containing the modified XML document
215      * is returned. If the XML passed is null or the element tag is not found in the document, null will be returned.
216      * 
217      * @param xml String containing the original XML document.
218      * @param elementTag String containing the tag of the element to be modified.
219      * @param newValue String containing the new value to be used to modify the corresponding element.
220      * @return the contents of the modified XML document as a String or null/empty if the modification failed.
221      * @throws IOException, TransformerException, ParserConfigurationException, SAXException
222      */
223     public static Optional<String> modifyElement(String xml, String elementTag, String newValue)
224             throws IOException, TransformerException, ParserConfigurationException, SAXException {
225
226         if (xml == null || xml.isEmpty()) {
227             // no XML content to be modified, return empty
228             return Optional.empty();
229         }
230
231         DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
232         dbFactory.setNamespaceAware(true);
233         dbFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
234         dbFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
235         DocumentBuilder db = dbFactory.newDocumentBuilder();
236         InputSource source = new InputSource(new StringReader(xml));
237         Document doc = db.parse(source);
238
239         Node modNode = doc.getElementsByTagName(elementTag).item(0);
240         if (modNode == null) {
241             // did not find the specified element to be modified, return empty
242             // System.out.println("Did not find element tag " + elementTag + " in XML");
243             return Optional.empty();
244         } else {
245             modNode.setTextContent(newValue);
246         }
247
248         TransformerFactory transformerFactory = TransformerFactory.newInstance();
249         Transformer transformer = transformerFactory.newTransformer();
250         StringWriter writer = new StringWriter();
251         transformer.transform(new DOMSource(doc), new StreamResult(writer));
252         // return the modified String representation of the XML
253         return Optional.of(writer.toString().trim());
254     }
255 }