Changed try to try with resource
[so.git] / bpmn / MSOCoreBPMN / src / main / java / org / openecomp / mso / 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  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  * 
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  * 
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.openecomp.mso.bpmn.core.xml;
23
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.io.Reader;
29 import java.io.StringReader;
30 import java.io.StringWriter;
31 import java.util.HashMap;
32 import java.util.Map;
33 import java.util.Optional;
34
35 import javax.xml.parsers.DocumentBuilder;
36 import javax.xml.parsers.DocumentBuilderFactory;
37 import javax.xml.parsers.ParserConfigurationException;
38 import javax.xml.transform.OutputKeys;
39 import javax.xml.transform.Source;
40 import javax.xml.transform.Transformer;
41 import javax.xml.transform.TransformerException;
42 import javax.xml.transform.TransformerFactory;
43 import javax.xml.transform.dom.DOMSource;
44 import javax.xml.transform.stream.StreamResult;
45 import javax.xml.transform.stream.StreamSource;
46 import javax.xml.xpath.XPath;
47 import javax.xml.xpath.XPathConstants;
48 import javax.xml.xpath.XPathExpressionException;
49 import javax.xml.xpath.XPathFactory;
50
51 import org.openecomp.mso.logger.MsoLogger;
52 import org.w3c.dom.Document;
53 import org.w3c.dom.NamedNodeMap;
54 import org.w3c.dom.Node;
55 import org.w3c.dom.NodeList;
56 import org.xml.sax.InputSource;
57 import org.xml.sax.SAXException;
58
59 /**
60  * XML transformation methods and other useful functions.
61  */
62 public final class XmlTool {
63
64         private static final Map<String, Integer> ENTITIES = new HashMap<>();
65         private static final MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.BPEL);
66         static {
67                 ENTITIES.put("amp", new Integer(38));
68                 ENTITIES.put("quot", new Integer(34));
69                 ENTITIES.put("lt", new Integer(60));
70                 ENTITIES.put("gt", new Integer(62));
71         }
72
73         /**
74      * Instantiation is not allowed.
75      */
76     private XmlTool() {
77     }
78     
79         /**
80          * Normalizes and formats XML.  This method consolidates and moves all namespace
81          * declarations to the root element.  The result will not have an XML prolog or
82          * a trailing newline.
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,
91                         ParserConfigurationException, SAXException, XPathExpressionException {
92                 
93                 if (xml == null) {
94                         return null;
95                 }
96
97                 Source xsltSource = new StreamSource(new StringReader(
98                         readResourceFile("normalize-namespaces.xsl")));
99
100                 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
101                 dbFactory.setNamespaceAware(true);
102                 DocumentBuilder db = dbFactory.newDocumentBuilder();
103                 InputSource source = new InputSource(new StringReader(String.valueOf(xml)));
104                 Document doc = db.parse(source);
105
106                 // Start of code to remove whitespace outside of tags
107                 XPath xPath = XPathFactory.newInstance().newXPath();
108                 NodeList nodeList = (NodeList) xPath.evaluate(
109                         "//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                 Transformer transformer = transformerFactory.newTransformer(xsltSource);
120
121                 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
122                 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
123                 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
124                 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
125
126                 StringWriter writer = new StringWriter();
127                 transformer.transform(new DOMSource(doc), new StreamResult(writer));
128                 return writer.toString().trim();
129         }
130
131         /**
132          * Encodes a value so it can be used inside an XML text element.
133          * @param s the string to encode
134          * @return the encoded string
135          */
136         public static String encode(Object value) {
137                 if (value == null) {
138                         return null;
139                 }
140
141                 String s = String.valueOf(value);
142                 StringBuilder out = new StringBuilder();
143                 boolean modified = false;
144
145                 for (int i = 0; i < s.length(); i++) {
146                         char c = s.charAt(i);
147
148                         if (c == '<') {
149                                 out.append("&lt;");
150                                 modified = true;
151                         } else if (c == '>') {
152                                 out.append("&gt;");
153                                 modified = true;
154                         } else if (c == '&') {
155                                 out.append("&amp;");
156                                 modified = true;
157                         } else if (c < 32 || c > 126) {
158                                 out.append("&#" + (int)c + ";");
159                                 modified = true;
160                         } else {
161                                 out.append(c);
162                         }
163                 }
164
165                 if (modified) {
166                         return out.toString();
167                 } else {
168                         return s;
169                 }
170         }
171         
172         /**
173          * Encodes a value so it can be used inside an XML attribute.
174          * @param s the string to encode
175          * @return the encoded string
176          */
177         public static String encodeAttr(Object value) {
178                 if (value == null) {
179                         return null;
180                 }
181
182                 String s = String.valueOf(value);
183                 StringBuilder out = new StringBuilder();
184                 boolean modified = false;
185
186                 for (int i = 0; i < s.length(); i++) {
187                         char c = s.charAt(i);
188
189                         if (c == '<') {
190                                 out.append("&lt;");
191                                 modified = true;
192                         } else if (c == '>') {
193                                 out.append("&gt;");
194                                 modified = true;
195                         } else if (c == '"') {
196                                 out.append("&quot;");
197                                 modified = true;
198                         } else if (c == '&') {
199                                 out.append("&amp;");
200                                 modified = true;
201                         } else if (c < 32 || c > 126) {
202                                 out.append("&#" + (int)c + ";");
203                                 modified = true;
204                         } else {
205                                 out.append(c);
206                         }
207                 }
208
209                 if (modified) {
210                         return out.toString();
211                 } else {
212                         return s;
213                 }
214         }
215         
216         /**
217          * Decodes XML entities in a string value
218          * @param value a value with embedded XML entities
219          * @return the decoded string
220          */
221         public static String decode(Object value) {
222                 if (value == null) {
223                         return null;
224                 }
225                 
226                 String s = String.valueOf(value);
227
228                 StringBuilder out = new StringBuilder(s.length());
229                 int ampIndex = s.indexOf("&");
230                 int lastEnd = 0;
231
232                 while (ampIndex >= 0) {
233                         int nextAmpIndex = s.indexOf("&", ampIndex + 1);
234                         int nextSemiIndex = s.indexOf(";", ampIndex + 1);
235                         if (nextSemiIndex != -1 && (nextAmpIndex == -1 || nextSemiIndex < nextAmpIndex)) {
236                                 int code = -1;
237                                 String entity = s.substring(ampIndex + 1, nextSemiIndex);
238
239                                 try {
240                                         if (entity.startsWith("#")) {
241                                                 code = Integer.parseInt(entity.substring(1), 10);
242                                         } else {
243                                                 if (ENTITIES.containsKey(entity)) {
244                                                         code = ENTITIES.get(entity);
245                                                 }
246                                         }
247                                 } catch (NumberFormatException x) {
248                                         // Do nothing
249                                 }
250
251                                 out.append(s.substring(lastEnd, ampIndex));
252                                 lastEnd = nextSemiIndex + 1;
253                                 if (code >= 0 && code <= 0xffff) {
254                                         out.append((char) code);
255                                 } else {
256                                         out.append("&");
257                                         out.append(entity);
258                                         out.append(";");
259                                 }
260                         }
261
262                         ampIndex = nextAmpIndex;
263                 }
264
265                 out.append(s.substring(lastEnd));
266                 return out.toString();
267         }
268
269         /**
270          * Removes the preamble, if present, from an XML document.
271          * @param xml the XML document
272          * @return a possibly modified document
273          */
274         public static String removePreamble(Object xml) {
275                 if (xml == null) {
276                         return null;
277                 }
278
279                 return String.valueOf(xml).replaceAll("(<\\?[^<]*\\?>\\s*[\\r\\n]*)?", "");
280         }
281
282         /**
283          * Removes namespaces and namespace declarations from an XML document.
284          * @param xml the XML document
285          * @return a possibly modified document
286          */
287         public static String removeNamespaces(Object xml) {
288                 if (xml == null) {
289                 LOGGER.debug("removeNamespaces input object is null , returning null");
290                         return null;
291                 }
292
293                 String text = String.valueOf(xml);
294
295                 // remove xmlns declaration
296                 text = text.replaceAll("xmlns.*?(\"|\').*?(\"|\')", "");
297                 // remove opening tag prefix
298                 text = text.replaceAll("(<)(\\w+:)(.*?>)", "$1$3");
299                 // remove closing tags prefix
300                 text = text.replaceAll("(</)(\\w+:)(.*?>)", "$1$3");
301                 // remove extra spaces left when xmlns declarations are removed
302                 text = text.replaceAll("\\s+>", ">");
303
304                 return text;
305         }
306
307
308         /**
309          * Reads the specified resource file and return the contents as a string.
310          * @param file Name of the resource file
311          * @return the contents of the resource file as a String
312          * @throws IOException if there is a problem reading the file
313          */
314         private static String readResourceFile(String file) throws IOException {
315
316                 try (InputStream stream = XmlTool.class.getClassLoader().getResourceAsStream(file);
317                          Reader reader = new InputStreamReader(stream, "UTF-8")) {
318
319                         StringBuilder out = new StringBuilder();
320                         char[] buf = new char[1024];
321                         int n;
322
323                         while ((n = reader.read(buf)) >= 0) {
324                                 out.append(buf, 0, n);
325                         }
326                         return out.toString();
327                 } catch (Exception e) {
328                         LOGGER.debug("Exception at readResourceFile stream: " + e);
329                         return null;
330                 }
331         }
332         
333         /**
334          * Parses the XML document String for the first occurrence of the specified element tag.
335          * If found, the value associated with that element tag is replaced with the new value
336          * and a String containing the modified XML document is returned. If the XML passed is
337          * null or the element tag is not found in the document, null will be returned.
338          * @param xml String containing the original XML document.
339          * @param elementTag String containing the tag of the element to be modified.
340          * @param newValue String containing the new value to be used to modify the corresponding element.
341          * @return the contents of the modified XML document as a String or null/empty if the modification failed.
342          * @throws IOException, TransformerException, ParserConfigurationException, SAXException
343          */
344         public static Optional<String> modifyElement(String xml, String elementTag, String newValue) throws IOException, TransformerException,
345         ParserConfigurationException, SAXException {
346
347                 if (xml == null || xml.isEmpty()) {
348                         // no XML content to be modified, return empty
349                         return Optional.empty();
350                 }
351                 
352                 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
353                 dbFactory.setNamespaceAware(true);
354                 DocumentBuilder db = dbFactory.newDocumentBuilder();
355                 InputSource source = new InputSource(new StringReader(xml));
356                 Document doc = db.parse(source);
357                 
358                 Node modNode = doc.getElementsByTagName(elementTag).item(0);
359                 if (modNode == null) {
360                         // did not find the specified element to be modified, return empty
361                         //System.out.println("Did not find element tag " + elementTag + " in XML");
362                         return Optional.empty();
363                 } else {
364                         modNode.setTextContent(newValue);                       
365                 }
366                 
367                 TransformerFactory transformerFactory = TransformerFactory.newInstance();
368                 Transformer transformer = transformerFactory.newTransformer();
369                 StringWriter writer = new StringWriter();
370                 transformer.transform(new DOMSource(doc), new StreamResult(writer));
371                 // return the modified String representation of the XML
372                 return Optional.of(writer.toString().trim());
373         }
374 }