View Javadoc

1   /*
2    * Copyright 2005-2007 The Kuali Foundation
3    *
4    *
5    * Licensed under the Educational Community License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.opensource.org/licenses/ecl2.php
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.kuali.rice.kew.util;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.Reader;
23  import java.io.StringReader;
24  import java.io.StringWriter;
25  import java.lang.reflect.Method;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Vector;
29  
30  import javax.xml.XMLConstants;
31  import javax.xml.parsers.DocumentBuilder;
32  import javax.xml.parsers.DocumentBuilderFactory;
33  import javax.xml.parsers.ParserConfigurationException;
34  import javax.xml.transform.OutputKeys;
35  import javax.xml.transform.Result;
36  import javax.xml.transform.Source;
37  import javax.xml.transform.Transformer;
38  import javax.xml.transform.TransformerException;
39  import javax.xml.transform.TransformerFactory;
40  import javax.xml.transform.dom.DOMResult;
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  
48  import org.apache.commons.lang.StringUtils;
49  import org.jdom.Attribute;
50  import org.jdom.Document;
51  import org.jdom.Element;
52  import org.jdom.JDOMException;
53  import org.jdom.Namespace;
54  import org.jdom.input.DOMBuilder;
55  import org.jdom.input.SAXBuilder;
56  import org.jdom.output.Format;
57  import org.jdom.output.XMLOutputter;
58  import org.kuali.rice.kew.exception.InvalidXmlException;
59  import org.kuali.rice.kew.exception.WorkflowRuntimeException;
60  import org.kuali.rice.kew.util.KEWConstants;
61  import org.kuali.rice.kew.xml.ClassLoaderEntityResolver;
62  import org.kuali.rice.kew.xml.XmlConstants;
63  import org.w3c.dom.NamedNodeMap;
64  import org.w3c.dom.Node;
65  import org.w3c.dom.NodeList;
66  import org.xml.sax.EntityResolver;
67  import org.xml.sax.ErrorHandler;
68  import org.xml.sax.InputSource;
69  import org.xml.sax.SAXException;
70  import org.xml.sax.SAXParseException;
71  
72  
73  /**
74   * Provides a set of utilities for XML-related operations.
75   *
76   * @author Kuali Rice Team (rice.collab@kuali.org)
77   */
78  public class XmlHelper {
79  	protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(XmlHelper.class);
80  
81      private static final EntityResolver ENTITY_RESOLVER = new ClassLoaderEntityResolver();
82  
83  	public XmlHelper() {
84  	}
85  
86  	public static org.jdom.Document buildJDocument(StringReader xmlStream) throws InvalidXmlException {
87  		// use SAX Builder
88  		// don't verify for speed reasons
89  		SAXBuilder builder = new SAXBuilder(false);
90  		org.jdom.Document doc = null;
91  		try {
92  			doc = builder.build(xmlStream);
93  		} /*
94  			 * catch (IOException e) { LOG.error("error building jdom
95  			 * document"); throw new InvalidXmlException("Invalid xml string. " +
96  			 * e.getMessage()); }
97  			 */catch (Exception e) {
98  			LOG.error("error building jdom document");
99  			throw new InvalidXmlException("Invalid xml string. " + e.getMessage());
100 		}
101 
102 		return doc;
103 	}
104 
105 	public static Element getAttributeRootElement(Document document) throws InvalidXmlException {
106 		return findElement(document.getRootElement(), KEWConstants.ATTRIBUTE_CONTENT_ELEMENT);
107 	}
108 
109 	public static Element getAttributeElement(Document document, String elementName) throws InvalidXmlException {
110 		return findElement(getAttributeRootElement(document), elementName);
111 	}
112 
113 	public static org.jdom.Document buildJDocument(org.w3c.dom.Document document) {
114 		return new DOMBuilder().build(document);
115 	}
116 
117 	/**
118 	 * Same as above but can specify whether validation is to be performed or
119 	 * not. Would have liked to have added a property that these methods could
120 	 * have used but would force instantiate on an otherwise static class
121 	 *
122 	 * @param xmlSream
123 	 * @param validateXML
124 	 * @return JDOM Document
125 	 * @throws InvalidXMLException
126 	 */
127 	public static org.jdom.Document buildJDocument(StringReader xmlStream, boolean validateXML) throws InvalidXmlException {
128 		SAXBuilder builder = new SAXBuilder(false);
129 		org.jdom.Document doc = null;
130 
131 		try {
132 			doc = builder.build(xmlStream);
133 		} /*
134 			 * catch (IOException e) { LOG.error("error building jdom
135 			 * document"); throw new InvalidXmlException("Invalid xml string. " +
136 			 * e.getMessage()); }
137 			 */catch (Exception e) {
138 			LOG.error("error building jdom document");
139 			throw new InvalidXmlException("Invalid xml string. " + e.getMessage());
140 		}
141 
142 		return doc;
143 	}
144 
145 	public static org.w3c.dom.Document buildDocument(String xml) throws IOException, SAXException, ParserConfigurationException {
146 	    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
147 	    dbf.setCoalescing(true);
148 	    DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
149 	    return documentBuilder.parse(new InputSource(new BufferedReader(new StringReader(xml))));
150 	}
151 	
152 	/**
153 	 * readerToString: read entire content of a Reader into a String
154 	 *
155 	 * @param is
156 	 * @return String
157 	 * @throws IOException
158 	 */
159 	public static String readerToString(Reader is) throws IOException {
160 		// local variables
161 		StringBuffer sb = new StringBuffer();
162 		char[] b = new char[2000];
163 		int n;
164 
165 		// Read a block. If it gets any chars, append them.
166 		while ((n = is.read(b)) > 0) {
167 			sb.append(b, 0, n);
168 		}
169 
170 		// Only construct the String object once, here.
171 		return sb.toString();
172 	}
173 
174 	// end ==> readerToString()
175 
176 	/**
177 	 * Find all Elements in document of a particular name
178 	 *
179 	 * @param root -
180 	 *            the starting Element to scan
181 	 * @param elementName -
182 	 *            name of the Element to scan for
183 	 * @return Vector - a list of the Elements found - return null if none found
184 	 */
185 	public static Vector findElements(Element root, String elementName) {
186 		Vector elementList = new Vector();
187 
188 		if (root == null) {
189 			return elementList;
190 		}
191 
192 		XmlHelper.findElements(root, elementName, elementList);
193 
194 		return elementList;
195 	}
196 
197 	// end ==> findElements()
198 
199 	/**
200 	 * returns the one element in root according to elementName. If more than
201 	 * one or none of element exists InvalidXmlException is thrown. This method
202 	 * is based on the assumption you have validated your xml and know the
203 	 * element is there. It provides a way to make code independent of the xml
204 	 * structure i.e. it digs for the element.
205 	 *
206 	 * @param root
207 	 * @param elementName
208 	 * @return Element
209 	 * @throws InvalidXmlException
210 	 */
211 	public static Element findElement(Element root, String elementName) throws InvalidXmlException {
212 		Vector elementList = XmlHelper.findElements(root, elementName);
213 
214 		if (elementList.size() < 1) {
215 			return null;
216 		}
217 
218 		if (elementList.size() > 1) {
219 			throw new InvalidXmlException("More than one element in root");
220 		}
221 
222 		return (Element) elementList.get(0);
223 	}
224 
225 	/**
226 	 * makes a properly formed element in the manner of <elementName value=""/>
227 	 * if value is null value attribute is given an empty value
228 	 *
229 	 * @param elementName
230 	 * @param value
231 	 * @return Element
232 	 * @throws Exception
233 	 */
234 	public static Element makeElement(String elementName, String value) throws Exception {
235 		if ((elementName == null) || elementName.trim().equals("")) {
236 			throw new Exception("Programmatic error:  Element Name passed in null or blank");
237 		}
238 
239 		Element element = new Element(elementName);
240 
241 		if ((value == null) || value.trim().equals("")) {
242 			element.setAttribute("value", "");
243 		} else {
244 			element.setAttribute("value", value);
245 		}
246 
247 		return element;
248 	}
249 
250 	/**
251 	 * Returns the value of the given element names given attribute tag based
252 	 * upon the root element passed in.
253 	 *
254 	 * @param root
255 	 * @param elementName
256 	 * @param attributeName
257 	 * @return value of the Element's attribute who's name matches the passed
258 	 *         element name and attribute matches the attribute name passes
259 	 * @throws InvalidXmlException
260 	 *             if element or attribute are not present
261 	 */
262 	public static String getElementAttributeValue(Element root, String elementName, String attributeName) throws InvalidXmlException {
263 		Element element = XmlHelper.findElement(root, elementName);
264 		Attribute attribute = element.getAttribute(attributeName);
265 
266 		if (attribute == null) {
267 			throw new InvalidXmlException("The Attribute name given is not present in the element " + element.getName());
268 		}
269 
270 		return attribute.getValue();
271 	}
272 
273 	/**
274 	 * This function is tail-recursive and just adds the root to the list if it
275 	 * matches and checks the children.
276 	 *
277 	 * @param root
278 	 * @param elementName
279 	 * @param list
280 	 */
281 	private static void findElements(Element root, String elementName, List list) {
282 		if (root != null) {
283 			if (root.getName().equals(elementName)) {
284 				list.add(root);
285 			}
286 
287 			Iterator iter = root.getChildren().iterator();
288 
289 			while (iter.hasNext()) {
290 				Element item = (Element) iter.next();
291 
292 				if (item != null) {
293 					XmlHelper.findElements(item, elementName, list);
294 				}
295 			}
296 		}
297 	}
298 
299 	public static String getTextContent(org.w3c.dom.Element element) {
300 		NodeList children = element.getChildNodes();
301 		Node node = children.item(0);
302 		return node.getNodeValue();
303 	}
304 
305 	public static String jotDocument(org.jdom.Document document) {
306 		XMLOutputter outputer = new XMLOutputter(Format.getPrettyFormat());
307 		StringWriter writer = new StringWriter();
308 		try {
309 			outputer.output(document, writer);
310 		} catch (IOException e) {
311 			throw new WorkflowRuntimeException("Could not write XML data export.", e);
312 		}
313 		return writer.toString();
314 	}
315 
316 	public static String jotNode(org.jdom.Element element) {
317 		XMLOutputter outputer = new XMLOutputter(Format.getPrettyFormat());
318 		StringWriter writer = new StringWriter();
319 		try {
320 			outputer.output(element, writer);
321 		} catch (IOException e) {
322 			throw new WorkflowRuntimeException("Could not write XML data export.", e);
323 		}
324 		return writer.toString();
325 	}
326 
327 	public static String jotNode(org.w3c.dom.Node node) {
328 		// default to true since this is used mostly for debugging
329 		return jotNode(node, true);
330 	}
331 
332 	public static String jotNode(org.w3c.dom.Node node, boolean indent) {
333 		try {
334 			return writeNode(node, indent);
335 		} catch (TransformerException te) {
336 			return Utilities.collectStackTrace(te);
337 		}
338 	}
339 
340 	public static String writeNode(org.w3c.dom.Node node) throws TransformerException {
341 		return writeNode(node, false);
342 	}
343 
344 	public static String writeNode(org.w3c.dom.Node node, boolean indent) throws TransformerException {
345 		Source source = new DOMSource(node);
346 		StringWriter writer = new StringWriter();
347 		Result result = new StreamResult(writer);
348 		Transformer transformer = TransformerFactory.newInstance().newTransformer();
349 		transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
350 		if (indent) {
351 			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
352 		}
353 		transformer.transform(source, result);
354 		return writer.toString();
355 	}
356 
357 	public static void appendXml(Node parentNode, String xml) throws SAXException, IOException, ParserConfigurationException {
358 		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
359 		factory.setValidating(false);
360 		org.w3c.dom.Document xmlDocument = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
361 		org.w3c.dom.Element xmlDocumentElement = xmlDocument.getDocumentElement();
362 		Node importedNode = parentNode.getOwnerDocument().importNode(xmlDocumentElement, true);
363 		parentNode.appendChild(importedNode);
364 	}
365 
366 	public static org.w3c.dom.Document readXml(String xml) throws TransformerException {
367 		Source source = new StreamSource(new BufferedReader(new StringReader(xml)));
368 		DOMResult result = new DOMResult();
369 		Transformer transformer = TransformerFactory.newInstance().newTransformer();
370 		transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
371 		transformer.transform(source, result);
372 		return (org.w3c.dom.Document) result.getNode();
373 	}
374 
375 	public static void propogateNamespace(Element element, Namespace namespace) {
376 		element.setNamespace(namespace);
377 		for (Iterator iterator = element.getChildren().iterator(); iterator.hasNext();) {
378 			Element childElement = (Element) iterator.next();
379 			propogateNamespace(childElement, namespace);
380 		}
381 	}
382 
383 	public static org.w3c.dom.Document trimXml(InputStream input) throws SAXException, IOException, ParserConfigurationException {
384 		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
385 		factory.setIgnoringElementContentWhitespace(true);
386 		DocumentBuilder builder = factory.newDocumentBuilder();
387 		org.w3c.dom.Document oldDocument = builder.parse(input);
388 		org.w3c.dom.Element naviElement = oldDocument.getDocumentElement();
389 		trimElement(naviElement);
390 		return oldDocument;
391 	}
392 
393 	public static void trimElement(Node node) throws SAXException, IOException, ParserConfigurationException {
394 
395 		if (node.hasChildNodes()) {
396 			// System.out.println(node.getNodeType()+"; "+node.getNodeName()+";
397 			// "+node.getNodeValue());
398 			NodeList children = node.getChildNodes();
399 			for (int i = 0; i < children.getLength(); i++) {
400 				Node child = children.item(i);
401 				if (child != null) {
402 					// System.out.println(child.getNodeType()+";
403 					// "+child.getNodeName()+"; "+child.getNodeValue());
404 					trimElement(child);
405 				}
406 			}
407 		} else {
408 			// System.out.println(node.getNodeType()+"; "+node.getNodeName()+";
409 			// "+node.getNodeValue());
410 			if (node.getNodeType() == Node.TEXT_NODE) {
411 				String text = node.getNodeValue();
412 				if (Utilities.isEmpty(text)) {
413 					text = "";
414 				} else {
415 					text = text.trim();
416 				}
417 				node.setNodeValue(text);
418 			}
419 		}
420 	}
421 
422 	public static void printDocumentStructure(org.w3c.dom.Document doc) {
423 		org.w3c.dom.Element naviElement = doc.getDocumentElement();
424 		printNode(naviElement, 0);
425 	}
426 
427 	public static void printNode(Node node, int level) {
428 		if (node.getNodeType() == Node.TEXT_NODE) {
429 			if (LOG.isInfoEnabled()) {
430 				LOG.info(node.getNodeValue());
431 			}
432 			return;
433 		} else {
434 			final StringBuilder log = new StringBuilder();
435 			/*
436 			 * System.out.println("\n"); for(int i=0;i<level;i++){
437 			 * System.out.print(" "); }
438 			 */
439 			log.append("<" + node.getNodeName());
440 			if (node.hasAttributes()) {
441 				NamedNodeMap attrMap = node.getAttributes();
442 				for (int i = 0; i < attrMap.getLength(); i++) {
443 					org.w3c.dom.Attr attribute = (org.w3c.dom.Attr) attrMap.item(i);
444 					log.append(" " + attribute.getName().trim() + "=\"" + attribute.getValue() + "\"");
445 				}
446 			}
447 			log.append(">");
448 			if (node.hasChildNodes()) {
449 				NodeList children = node.getChildNodes();
450 				for (int i = 0; i < children.getLength(); i++) {
451 					Node child = children.item(i);
452 					if (child != null) {
453 						// System.out.println(child.getNodeType()+";
454 						// "+child.getNodeName()+"; "+child.getNodeValue());
455 						printNode(child, level + 1);
456 					}
457 				}
458 			}
459 			log.append("</" + node.getNodeName() + ">");
460 			if (LOG.isInfoEnabled()) {
461 				LOG.info(log);
462 			}
463 		}
464 	}
465 
466 	public static Document trimSAXXml(InputStream input) throws JDOMException, SAXException, IOException, ParserConfigurationException {
467 		SAXBuilder builder = new SAXBuilder(false);
468 		Document oldDocument = builder.build(input);
469 		Element naviElement = oldDocument.getRootElement();
470 		trimSAXElement(naviElement);
471 		return oldDocument;
472 	}
473 
474 	public static void trimSAXElement(Element element) throws SAXException, IOException, ParserConfigurationException {
475 
476 		if (! element.getChildren().isEmpty()) {
477 
478 			java.util.List children = element.getChildren();
479 			for (int i = 0; i < children.size(); i++) {
480 				Element child = (Element) children.get(i);
481 				if (child != null) {
482 					// System.out.println(child.getNodeType()+";
483 					// "+child.getNodeName()+"; "+child.getNodeValue());
484 					trimSAXElement(child);
485 				}
486 			}
487 		} else {
488 			// System.out.println(node.getNodeType()+"; "+node.getNodeName()+";
489 			// "+node.getNodeValue());
490 			// System.out.println(element.getName());
491 			String text = element.getTextTrim();
492 			if (Utilities.isEmpty(text)) {
493 				text = "";
494 			}
495 			element.setText(text);
496 
497 		}
498 	}
499 
500 	public static void printSAXDocumentStructure(Document doc) {
501 		Element naviElement = doc.getRootElement();
502 		printSAXNode(naviElement, 0);
503 	}
504 
505 	public static void printSAXNode(Element element, int level) {
506 		if (element.getChildren().isEmpty()) {
507 			if (LOG.isInfoEnabled()) {
508 				LOG.info("<" + element.getName().trim() + ">" + element.getText() + "</" + element.getName().trim() + ">");
509 			}
510 			return;
511 		} else {
512 			final StringBuilder log = new StringBuilder();
513 			/*
514 			 * System.out.println("\n"); for(int i=0;i<level;i++){
515 			 * System.out.print(" "); }
516 			 */
517 			log.append("<" + element.getName());
518 			org.jdom.Namespace ns = element.getNamespace();
519 			if (ns != null) {
520 				log.append(" xmlns=\"" + ns.getURI() + "\"");
521 
522 			}
523 			ns = element.getNamespace("xsi");
524 			if (ns != null) {
525 				log.append(" xmlns:" + ns.getPrefix() + "=\"" + ns.getURI() + "\"");
526 			}
527 			if (element.getAttributes() != null && element.getAttributes().size() > 0) {
528 				List attrMap = element.getAttributes();
529 				for (int i = 0; i < attrMap.size(); i++) {
530 					Attribute attribute = (Attribute) attrMap.get(i);
531 					ns = attribute.getNamespace();
532 					log.append(" ");
533 					if (ns != null) {
534 						log.append(ns.getPrefix() + ":");
535 					}
536 					log.append(attribute.getName().trim() + "=\"" + attribute.getValue() + "\"");
537 				}
538 			}
539 			log.append(">");
540 			List children = element.getChildren();
541 			for (int i = 0; i < children.size(); i++) {
542 				Element child = (Element) children.get(i);
543 				if (child != null) {
544 					// System.out.println(child.getNodeType()+";
545 					// "+child.getNodeName()+"; "+child.getNodeValue());
546 					printSAXNode(child, level + 1);
547 				}
548 			}
549 
550 			log.append("</" + element.getName() + ">");
551 			if (LOG.isInfoEnabled()) {
552 				LOG.info(log);
553 			}
554 		}
555 	}
556 
557     /**
558      * Convenience method that performs an xpath evaluation to determine whether the expression
559      * evaluates to true (a node exists).
560      * This is method exists only to disambiguate the cases of determining the *presence* of a node
561      * and determining the *boolean value of the node as converted from a string*, as the syntaxes
562      * are very similar and could be misleading.
563      * @param xpath the XPath object
564      * @param expression the XPath expression
565      * @param object the object on which to evaluate the expression as required by the XPath API, typically a Node
566      * @return whether the result of the expression evaluation, which is whether or not a node was present
567      * @throws XPathExpressionException
568      */
569     public static boolean pathExists(XPath xpath, String expression, Object object) throws XPathExpressionException {
570         return ((Boolean) xpath.evaluate(expression, object, XPathConstants.BOOLEAN)).booleanValue();
571     }
572 
573     public static void validate(final InputSource source) throws ParserConfigurationException, IOException, SAXException {
574         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
575         dbf.setValidating(true);
576         dbf.setNamespaceAware( true );
577         dbf.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", XMLConstants.W3C_XML_SCHEMA_NS_URI);
578         DocumentBuilder db = dbf.newDocumentBuilder();
579         db.setEntityResolver(ENTITY_RESOLVER);
580         db.setErrorHandler(new ErrorHandler() {
581             public void warning(SAXParseException se) {
582                 LOG.warn("Warning parsing xml doc " + source, se);
583             }
584             public void error(SAXParseException se) throws SAXException {
585                 LOG.error("Error parsing xml doc " + source, se);
586                 throw se;
587             }
588             public void fatalError(SAXParseException se) throws SAXException {
589                 LOG.error("Fatal error parsing xml doc " + source, se);
590                 throw se;
591             }
592         });
593         db.parse(source);
594     }
595 
596     public static org.w3c.dom.Element propertiesToXml(org.w3c.dom.Document doc, Object o, String elementName) throws Exception {
597         Class c = o.getClass();
598         org.w3c.dom.Element wrapper = doc.createElement(elementName);
599         Method[] methods = c.getMethods();
600         for (Method method : methods) {
601             String name = method.getName();
602             if ("getClass".equals(name)) continue;
603             if (!name.startsWith("get") ||
604                     method.getParameterTypes().length > 0) continue;
605             name = name.substring("get".length());
606             name = StringUtils.uncapitalize(name);
607             String value = null;
608             try {
609                 Object result = method.invoke(o, null);
610                 if (result == null) {
611                     LOG.debug("value of " + name + " method on object " + o.getClass() + " is null");
612                     value = "";
613                 } else {
614                     value = result.toString();
615                 }
616                 org.w3c.dom.Element fieldE = doc.createElement(name);
617                 fieldE.appendChild(doc.createTextNode(value));
618                 wrapper.appendChild(fieldE);
619             } catch (RuntimeException e) {
620                 LOG.error("Error accessing method '" + method.getName() + "' of instance of " + c);
621                 throw e;
622             } catch (Exception e) {
623                 LOG.error("Error accessing method '" + method.getName() + "' of instance of " + c);
624             }
625         }
626         return wrapper;
627     }
628 
629     public static String getChildElementText(org.w3c.dom.Element parent, String childElementName) {
630 	NodeList childNodes = parent.getChildNodes();
631 	for (int index = 0; index < childNodes.getLength(); index++) {
632 	    org.w3c.dom.Node node = childNodes.item(index);
633 	    if (Node.ELEMENT_NODE == node.getNodeType()) {
634 		org.w3c.dom.Element element = (org.w3c.dom.Element)node;
635 		if (XmlConstants.DOCUMENT_TYPE.equals(element.getNodeName())) {
636 		    return element.getTextContent();
637 		}
638 	    }
639 	}
640 	return null;
641     }
642 }