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