Merge Casablanca
[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  * 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.onap.so.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.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
52 import org.apache.commons.lang3.StringEscapeUtils;
53 import org.onap.so.logger.MsoLogger;
54 import org.w3c.dom.Document;
55 import org.w3c.dom.NamedNodeMap;
56 import org.w3c.dom.Node;
57 import org.w3c.dom.NodeList;
58 import org.xml.sax.InputSource;
59 import org.xml.sax.SAXException;
60
61 /**
62  * XML transformation methods and other useful functions.
63  */
64 public final class XmlTool {
65
66         private static final Map<String, Integer> ENTITIES = new HashMap<>();
67         private static final MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.BPEL, XmlTool.class);
68         static {
69                 ENTITIES.put("amp", 38);
70                 ENTITIES.put("quot", 34);
71                 ENTITIES.put("lt", 60);
72                 ENTITIES.put("gt", 62);
73         }
74
75         /**
76      * Instantiation is not allowed.
77      */
78     private XmlTool() {
79     }
80     
81         /**
82          * Normalizes and formats XML.  This method consolidates and moves all namespace
83          * declarations to the root element.  The result will not have an XML prolog or
84          * a trailing newline.
85          * @param xml the XML to normalize
86          * @throws IOException 
87          * @throws TransformerException 
88          * @throws ParserConfigurationException 
89          * @throws SAXException 
90          * @throws XPathExpressionException 
91          */
92         public static String normalize(Object xml) throws IOException, TransformerException,
93                         ParserConfigurationException, SAXException, XPathExpressionException {
94                 
95                 if (xml == null) {
96                         return null;
97                 }
98
99                 Source xsltSource = new StreamSource(new StringReader(
100                         readResourceFile("normalize-namespaces.xsl")));
101
102                 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
103                 dbFactory.setNamespaceAware(true);
104                 dbFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
105                 dbFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
106                 DocumentBuilder db = dbFactory.newDocumentBuilder();
107                 InputSource source = new InputSource(new StringReader(String.valueOf(xml)));
108                 Document doc = db.parse(source);
109
110                 // Start of code to remove whitespace outside of tags
111                 XPath xPath = XPathFactory.newInstance().newXPath();
112                 NodeList nodeList = (NodeList) xPath.evaluate(
113                         "//text()[normalize-space()='']", doc, XPathConstants.NODESET);
114
115                 for (int i = 0; i < nodeList.getLength(); ++i) {
116                         Node node = nodeList.item(i);
117                         node.getParentNode().removeChild(node);
118                 }
119                 // End of code to remove whitespace outside of tags
120
121                 // the factory pattern supports different XSLT processors
122                 TransformerFactory transformerFactory = TransformerFactory.newInstance();               
123                 transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
124                 Transformer transformer = transformerFactory.newTransformer(xsltSource);
125
126                 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
127                 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
128                 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
129                 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
130
131                 StringWriter writer = new StringWriter();
132                 transformer.transform(new DOMSource(doc), new StreamResult(writer));
133                 return writer.toString().trim();
134         }
135
136         /**
137          * Encodes a value so it can be used inside an XML text element.
138          * @param value the string to encode
139          * @return the encoded string
140          */
141         public static String encode(Object value) {
142                 if (value == null) {
143                         return null;
144                 }
145                 return StringEscapeUtils.escapeXml11(value.toString());
146         }
147
148         /**
149          * Removes the preamble, if present, from an XML document.
150          * @param xml the XML document
151          * @return a possibly modified document
152          */
153         public static String removePreamble(Object xml) {
154                 if (xml == null) {
155                         return null;
156                 }
157
158                 return String.valueOf(xml).replaceAll("(<\\?[^<]*\\?>\\s*[\\r\\n]*)?", "");
159         }
160
161         /**
162          * Removes namespaces and namespace declarations from an XML document.
163          * @param xml the XML document
164          * @return a possibly modified document
165          */
166         public static String removeNamespaces(Object xml) {
167                 if (xml == null) {
168                 LOGGER.debug("removeNamespaces input object is null , returning null");
169                         return null;
170                 }
171
172                 String text = String.valueOf(xml);
173
174                 // remove xmlns declaration
175                 text = text.replaceAll("xmlns.*?(\"|\').*?(\"|\')", "");
176                 // remove opening tag prefix
177                 text = text.replaceAll("(<)(\\w+:)(.*?>)", "$1$3");
178                 // remove closing tags prefix
179                 text = text.replaceAll("(</)(\\w+:)(.*?>)", "$1$3");
180                 // remove extra spaces left when xmlns declarations are removed
181                 text = text.replaceAll("\\s+>", ">");
182
183                 return text;
184         }
185
186
187         /**
188          * Reads the specified resource file and return the contents as a string.
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.
214          * If found, the value associated with that element tag is replaced with the new value
215          * and a String containing the modified XML document is returned. If the XML passed is
216          * null or the element tag is not found in the document, null will be returned.
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) throws IOException, TransformerException,
224         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 }