001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 package org.apache.oozie.util; 019 020 import java.io.ByteArrayInputStream; 021 import java.io.IOException; 022 import java.io.InputStream; 023 import java.io.StringReader; 024 import java.io.StringWriter; 025 import java.text.CharacterIterator; 026 import java.text.StringCharacterIterator; 027 import java.util.Enumeration; 028 import java.util.Iterator; 029 import java.util.List; 030 import java.util.Map; 031 import java.util.Properties; 032 033 import javax.xml.XMLConstants; 034 import javax.xml.parsers.DocumentBuilderFactory; 035 import javax.xml.transform.Result; 036 import javax.xml.transform.Source; 037 import javax.xml.transform.Transformer; 038 import javax.xml.transform.TransformerFactory; 039 import javax.xml.transform.dom.DOMSource; 040 import javax.xml.transform.stream.StreamResult; 041 import javax.xml.transform.stream.StreamSource; 042 import javax.xml.validation.Schema; 043 import javax.xml.validation.SchemaFactory; 044 import javax.xml.validation.Validator; 045 046 import org.apache.hadoop.conf.Configuration; 047 import org.apache.oozie.service.SchemaService; 048 import org.apache.oozie.service.SchemaService.SchemaName; 049 import org.apache.oozie.service.Services; 050 import org.jdom.Comment; 051 import org.jdom.Document; 052 import org.jdom.Element; 053 import org.jdom.JDOMException; 054 import org.jdom.Namespace; 055 import org.jdom.input.SAXBuilder; 056 import org.jdom.output.Format; 057 import org.jdom.output.XMLOutputter; 058 import org.xml.sax.EntityResolver; 059 import org.xml.sax.InputSource; 060 import org.xml.sax.SAXException; 061 062 /** 063 * XML utility methods. 064 */ 065 public class XmlUtils { 066 067 private static class NoExternalEntityEntityResolver implements EntityResolver { 068 069 @Override 070 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { 071 return new InputSource(new ByteArrayInputStream(new byte[0])); 072 } 073 074 } 075 076 private static SAXBuilder createSAXBuilder() { 077 SAXBuilder saxBuilder = new SAXBuilder(); 078 079 //THIS IS NOT WORKING 080 //saxBuilder.setFeature("http://xml.org/sax/features/external-general-entities", false); 081 082 //INSTEAD WE ARE JUST SETTING AN EntityResolver that does not resolve entities 083 saxBuilder.setEntityResolver(new NoExternalEntityEntityResolver()); 084 return saxBuilder; 085 } 086 087 /** 088 * Remove comments from any Xml String. 089 * 090 * @param xmlStr XML string to remove comments. 091 * @return String after removing comments. 092 * @throws JDOMException thrown if an error happend while XML parsing. 093 */ 094 public static String removeComments(String xmlStr) throws JDOMException { 095 if (xmlStr == null) { 096 return null; 097 } 098 try { 099 SAXBuilder saxBuilder = createSAXBuilder(); 100 Document document = saxBuilder.build(new StringReader(xmlStr)); 101 removeComments(document); 102 return prettyPrint(document.getRootElement()).toString(); 103 } 104 catch (IOException ex) { 105 throw new RuntimeException("It should not happen, " + ex.getMessage(), ex); 106 } 107 } 108 109 private static void removeComments(List l) { 110 for (Iterator i = l.iterator(); i.hasNext();) { 111 Object node = i.next(); 112 if (node instanceof Comment) { 113 i.remove(); 114 } 115 else { 116 if (node instanceof Element) { 117 removeComments(((Element) node).getContent()); 118 } 119 } 120 } 121 } 122 123 private static void removeComments(Document doc) { 124 removeComments(doc.getContent()); 125 } 126 127 /** 128 * Parse a string assuming it is a valid XML document and return an JDOM Element for it. 129 * 130 * @param xmlStr XML string to parse. 131 * @return JDOM element for the parsed XML string. 132 * @throws JDOMException thrown if an error happend while XML parsing. 133 */ 134 public static Element parseXml(String xmlStr) throws JDOMException { 135 ParamChecker.notNull(xmlStr, "xmlStr"); 136 try { 137 SAXBuilder saxBuilder = createSAXBuilder(); 138 Document document = saxBuilder.build(new StringReader(xmlStr)); 139 return document.getRootElement(); 140 } 141 catch (IOException ex) { 142 throw new RuntimeException("It should not happen, " + ex.getMessage(), ex); 143 } 144 } 145 146 /** 147 * Parse a inputstream assuming it is a valid XML document and return an JDOM Element for it. 148 * 149 * @param is inputstream to parse. 150 * @return JDOM element for the parsed XML string. 151 * @throws JDOMException thrown if an error happend while XML parsing. 152 * @throws IOException thrown if an IO error occurred. 153 */ 154 public static Element parseXml(InputStream is) throws JDOMException, IOException { 155 ParamChecker.notNull(is, "is"); 156 SAXBuilder saxBuilder = createSAXBuilder(); 157 Document document = saxBuilder.build(is); 158 return document.getRootElement(); 159 } 160 161 /** 162 * //TODO move this to action registry method Return the value of an attribute from the root element of an XML 163 * document. 164 * 165 * @param filePath path of the XML document. 166 * @param attributeName attribute to retrieve value for. 167 * @return value of the specified attribute. 168 */ 169 public static String getRootAttribute(String filePath, String attributeName) { 170 ParamChecker.notNull(filePath, "filePath"); 171 ParamChecker.notNull(attributeName, "attributeName"); 172 SAXBuilder saxBuilder = createSAXBuilder(); 173 try { 174 Document doc = saxBuilder.build(Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath)); 175 return doc.getRootElement().getAttributeValue(attributeName); 176 } 177 catch (JDOMException e) { 178 throw new RuntimeException(); 179 } 180 catch (IOException e) { 181 throw new RuntimeException(); 182 } 183 } 184 185 /** 186 * Pretty print string representation of an XML document that generates the pretty print on lazy mode when the 187 * {@link #toString} method is invoked. 188 */ 189 public static class PrettyPrint { 190 private String str; 191 private Element element; 192 193 private PrettyPrint(String str) { 194 this.str = str; 195 } 196 197 private PrettyPrint(Element element) { 198 this.element = ParamChecker.notNull(element, "element"); 199 } 200 201 /** 202 * Return the pretty print representation of an XML document. 203 * 204 * @return the pretty print representation of an XML document. 205 */ 206 @Override 207 public String toString() { 208 if (str != null) { 209 return str; 210 } 211 else { 212 XMLOutputter outputter = new XMLOutputter(); 213 StringWriter stringWriter = new StringWriter(); 214 outputter.setFormat(Format.getPrettyFormat()); 215 try { 216 outputter.output(element, stringWriter); 217 } 218 catch (Exception ex) { 219 throw new RuntimeException(ex); 220 } 221 return stringWriter.toString(); 222 } 223 } 224 } 225 226 /** 227 * Return a pretty print string for a JDOM Element. 228 * 229 * @param element JDOM element. 230 * @return pretty print of the given JDOM Element. 231 */ 232 public static PrettyPrint prettyPrint(Element element) { 233 return new PrettyPrint(element); 234 235 } 236 237 /** 238 * Return a pretty print string for a XML string. If the given string is not valid XML it returns the original 239 * string. 240 * 241 * @param xmlStr XML string. 242 * @return prettyprint of the given XML string or the original string if the given string is not valid XML. 243 */ 244 public static PrettyPrint prettyPrint(String xmlStr) { 245 try { 246 return new PrettyPrint(parseXml(xmlStr)); 247 } 248 catch (Exception e) { 249 return new PrettyPrint(xmlStr); 250 } 251 } 252 253 /** 254 * Return a pretty print string for a Configuration object. 255 * 256 * @param conf Configuration object. 257 * @return prettyprint of the given Configuration object. 258 */ 259 public static PrettyPrint prettyPrint(Configuration conf) { 260 Element root = new Element("configuration"); 261 for (Map.Entry<String, String> entry : conf) { 262 Element property = new Element("property"); 263 Element name = new Element("name"); 264 name.setText(entry.getKey()); 265 Element value = new Element("value"); 266 value.setText(entry.getValue()); 267 property.addContent(name); 268 property.addContent(value); 269 root.addContent(property); 270 } 271 return new PrettyPrint(root); 272 } 273 274 /** 275 * Schema validation for a given xml. <p/> 276 * 277 * @param schema for validation 278 * @param xml to be validated 279 */ 280 public static void validateXml(Schema schema, String xml) throws SAXException, IOException { 281 282 Validator validator = schema.newValidator(); 283 validator.validate(new StreamSource(new ByteArrayInputStream(xml.getBytes()))); 284 } 285 286 /** 287 * Create schema object for the given xsd 288 * 289 * @param is inputstream to schema. 290 * @return the schema object. 291 */ 292 public static Schema createSchema(InputStream is) { 293 SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 294 StreamSource src = new StreamSource(is); 295 try { 296 return factory.newSchema(src); 297 } 298 catch (SAXException e) { 299 throw new RuntimeException(e.getMessage(), e); 300 } 301 } 302 303 public static void validateData(String xmlData, SchemaName xsdFile) throws SAXException, IOException { 304 if (xmlData == null || xmlData.length() == 0) { 305 return; 306 } 307 javax.xml.validation.Schema schema = Services.get().get(SchemaService.class).getSchema(xsdFile); 308 validateXml(schema, xmlData); 309 } 310 311 /** 312 * Convert Properties to string 313 * 314 * @param props 315 * @return xml string 316 * @throws IOException 317 */ 318 public static String writePropToString(Properties props) throws IOException { 319 try { 320 org.w3c.dom.Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 321 org.w3c.dom.Element conf = doc.createElement("configuration"); 322 doc.appendChild(conf); 323 conf.appendChild(doc.createTextNode("\n")); 324 for (Enumeration e = props.keys(); e.hasMoreElements();) { 325 String name = (String) e.nextElement(); 326 Object object = props.get(name); 327 String value; 328 if (object instanceof String) { 329 value = (String) object; 330 } 331 else { 332 continue; 333 } 334 org.w3c.dom.Element propNode = doc.createElement("property"); 335 conf.appendChild(propNode); 336 337 org.w3c.dom.Element nameNode = doc.createElement("name"); 338 nameNode.appendChild(doc.createTextNode(name.trim())); 339 propNode.appendChild(nameNode); 340 341 org.w3c.dom.Element valueNode = doc.createElement("value"); 342 valueNode.appendChild(doc.createTextNode(value.trim())); 343 propNode.appendChild(valueNode); 344 345 conf.appendChild(doc.createTextNode("\n")); 346 } 347 348 Source source = new DOMSource(doc); 349 StringWriter stringWriter = new StringWriter(); 350 Result result = new StreamResult(stringWriter); 351 TransformerFactory factory = TransformerFactory.newInstance(); 352 Transformer transformer = factory.newTransformer(); 353 transformer.transform(source, result); 354 355 return stringWriter.getBuffer().toString(); 356 } 357 catch (Exception e) { 358 throw new IOException(e); 359 } 360 } 361 362 /** 363 * Escape characters for text appearing as XML data, between tags. 364 * <P/> 365 * The following characters are replaced with corresponding character entities : 366 * '<' to '<'; 367 * '>' to '>'; 368 * '&' to '&' 369 * '"' to '"' 370 * "'" to "'" 371 * <P/> 372 * Note that JSTL's {@code <c:out>} escapes the exact same set of characters as this method. 373 */ 374 public static String escapeCharsForXML(String aText) { 375 final StringBuilder result = new StringBuilder(); 376 final StringCharacterIterator iterator = new StringCharacterIterator(aText); 377 char character = iterator.current(); 378 while (character != CharacterIterator.DONE) { 379 if (character == '<') { 380 result.append("<"); 381 } 382 else if (character == '>') { 383 result.append(">"); 384 } 385 else if (character == '\"') { 386 result.append("""); 387 } 388 else if (character == '\'') { 389 result.append("'"); 390 } 391 else if (character == '&') { 392 result.append("&"); 393 } 394 else { 395 // the char is not a special one 396 // add it to the result as is 397 result.append(character); 398 } 399 character = iterator.next(); 400 } 401 return result.toString(); 402 } 403 404 public static Element getSLAElement(Element elem) { 405 Element eSla_1 = elem.getChild("info", Namespace.getNamespace(SchemaService.SLA_NAME_SPACE_URI)); 406 Element eSla_2 = elem.getChild("info", Namespace.getNamespace(SchemaService.SLA_NAMESPACE_URI_2)); 407 Element eSla = (eSla_2 != null) ? eSla_2 : eSla_1; 408 409 return eSla; 410 } 411 412 }