Containerization feature of SO
[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                 Transformer transformer = transformerFactory.newTransformer(xsltSource);
124
125                 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
126                 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
127                 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
128                 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
129
130                 StringWriter writer = new StringWriter();
131                 transformer.transform(new DOMSource(doc), new StreamResult(writer));
132                 return writer.toString().trim();
133         }
134
135         /**
136          * Encodes a value so it can be used inside an XML text element.
137          * @param value the string to encode
138          * @return the encoded string
139          */
140         public static String encode(Object value) {
141                 if (value == null) {
142                         return null;
143                 }
144                 return StringEscapeUtils.escapeXml11(value.toString());
145         }
146
147         /**
148          * Removes the preamble, if present, from an XML document.
149          * @param xml the XML document
150          * @return a possibly modified document
151          */
152         public static String removePreamble(Object xml) {
153                 if (xml == null) {
154                         return null;
155                 }
156
157                 return String.valueOf(xml).replaceAll("(<\\?[^<]*\\?>\\s*[\\r\\n]*)?", "");
158         }
159
160         /**
161          * Removes namespaces and namespace declarations from an XML document.
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          * @param file Name of the resource file
189          * @return the contents of the resource file as a String
190          * @throws IOException if there is a problem reading the file
191          */
192         private static String readResourceFile(String file) throws IOException {
193
194                 try (InputStream stream = XmlTool.class.getClassLoader().getResourceAsStream(file);
195                          Reader reader = new InputStreamReader(stream, "UTF-8")) {
196
197                         StringBuilder out = new StringBuilder();
198                         char[] buf = new char[1024];
199                         int n;
200
201                         while ((n = reader.read(buf)) >= 0) {
202                                 out.append(buf, 0, n);
203                         }
204                         return out.toString();
205                 } catch (Exception e) {
206                         LOGGER.debug("Exception at readResourceFile stream: " + e);
207                         return null;
208                 }
209         }
210         
211         /**
212          * Parses the XML document String for the first occurrence of the specified element tag.
213          * If found, the value associated with that element tag is replaced with the new value
214          * and a String containing the modified XML document is returned. If the XML passed is
215          * null or the element tag is not found in the document, null will be returned.
216          * @param xml String containing the original XML document.
217          * @param elementTag String containing the tag of the element to be modified.
218          * @param newValue String containing the new value to be used to modify the corresponding element.
219          * @return the contents of the modified XML document as a String or null/empty if the modification failed.
220          * @throws IOException, TransformerException, ParserConfigurationException, SAXException
221          */
222         public static Optional<String> modifyElement(String xml, String elementTag, String newValue) throws IOException, TransformerException,
223         ParserConfigurationException, SAXException {
224
225                 if (xml == null || xml.isEmpty()) {
226                         // no XML content to be modified, return empty
227                         return Optional.empty();
228                 }
229                 
230                 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
231                 dbFactory.setNamespaceAware(true);
232                 dbFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
233                 dbFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
234                 DocumentBuilder db = dbFactory.newDocumentBuilder();
235                 InputSource source = new InputSource(new StringReader(xml));
236                 Document doc = db.parse(source);
237                 
238                 Node modNode = doc.getElementsByTagName(elementTag).item(0);
239                 if (modNode == null) {
240                         // did not find the specified element to be modified, return empty
241                         //System.out.println("Did not find element tag " + elementTag + " in XML");
242                         return Optional.empty();
243                 } else {
244                         modNode.setTextContent(newValue);                       
245                 }
246                 
247                 TransformerFactory transformerFactory = TransformerFactory.newInstance();
248                 Transformer transformer = transformerFactory.newTransformer();
249                 StringWriter writer = new StringWriter();
250                 transformer.transform(new DOMSource(doc), new StreamResult(writer));
251                 // return the modified String representation of the XML
252                 return Optional.of(writer.toString().trim());
253         }
254 }