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