001: /*
002: * Copyright 2005-2006 The Kuali Foundation.
003: *
004: *
005: * Licensed under the Educational Community License, Version 1.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.opensource.org/licenses/ecl1.php
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package edu.iu.uis.eden.util;
018:
019: import java.io.BufferedReader;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.io.Reader;
023: import java.io.StringReader;
024: import java.io.StringWriter;
025: import java.lang.reflect.Method;
026: import java.util.Iterator;
027: import java.util.List;
028: import java.util.Vector;
029:
030: import javax.xml.XMLConstants;
031: import javax.xml.parsers.DocumentBuilder;
032: import javax.xml.parsers.DocumentBuilderFactory;
033: import javax.xml.parsers.ParserConfigurationException;
034: import javax.xml.transform.OutputKeys;
035: import javax.xml.transform.Result;
036: import javax.xml.transform.Source;
037: import javax.xml.transform.Transformer;
038: import javax.xml.transform.TransformerException;
039: import javax.xml.transform.TransformerFactory;
040: import javax.xml.transform.dom.DOMResult;
041: import javax.xml.transform.dom.DOMSource;
042: import javax.xml.transform.stream.StreamResult;
043: import javax.xml.transform.stream.StreamSource;
044: import javax.xml.xpath.XPath;
045: import javax.xml.xpath.XPathConstants;
046: import javax.xml.xpath.XPathExpressionException;
047:
048: import org.apache.commons.lang.StringUtils;
049: import org.jdom.Attribute;
050: import org.jdom.Document;
051: import org.jdom.Element;
052: import org.jdom.JDOMException;
053: import org.jdom.Namespace;
054: import org.jdom.input.DOMBuilder;
055: import org.jdom.input.SAXBuilder;
056: import org.jdom.output.Format;
057: import org.jdom.output.XMLOutputter;
058: import org.w3c.dom.NamedNodeMap;
059: import org.w3c.dom.Node;
060: import org.w3c.dom.NodeList;
061: import org.xml.sax.EntityResolver;
062: import org.xml.sax.ErrorHandler;
063: import org.xml.sax.InputSource;
064: import org.xml.sax.SAXException;
065: import org.xml.sax.SAXParseException;
066:
067: import edu.iu.uis.eden.EdenConstants;
068: import edu.iu.uis.eden.exception.InvalidXmlException;
069: import edu.iu.uis.eden.exception.WorkflowRuntimeException;
070: import edu.iu.uis.eden.xml.ClassLoaderEntityResolver;
071:
072: /**
073: * Provides a set of utilities for XML-related operations.
074: *
075: * @author rkirkend
076: * @author ewestfal
077: * @author ahamid
078: */
079: public class XmlHelper {
080: protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
081: .getLogger(XmlHelper.class);
082:
083: private static final EntityResolver ENTITY_RESOLVER = new ClassLoaderEntityResolver();
084:
085: public XmlHelper() {
086: }
087:
088: public static org.jdom.Document buildJDocument(
089: StringReader xmlStream) throws InvalidXmlException {
090: // use SAX Builder
091: // don't verify for speed reasons
092: SAXBuilder builder = new SAXBuilder(false);
093: org.jdom.Document doc = null;
094:
095: try {
096: doc = builder.build(xmlStream);
097: } /*
098: * catch (IOException e) { LOG.error("error building jdom
099: * document"); throw new InvalidXmlException("Invalid xml string. " +
100: * e.getMessage()); }
101: */catch (Exception e) {
102: LOG.error("error building jdom document");
103: throw new InvalidXmlException("Invalid xml string. "
104: + e.getMessage());
105: }
106:
107: return doc;
108: }
109:
110: public static Element getAttributeRootElement(Document document)
111: throws InvalidXmlException {
112: return findElement(document.getRootElement(),
113: EdenConstants.ATTRIBUTE_CONTENT_ELEMENT);
114: }
115:
116: public static Element getAttributeElement(Document document,
117: String elementName) throws InvalidXmlException {
118: return findElement(getAttributeRootElement(document),
119: elementName);
120: }
121:
122: public static org.jdom.Document buildJDocument(
123: org.w3c.dom.Document document) {
124: return new DOMBuilder().build(document);
125: }
126:
127: /**
128: * Same as above but can specify whether validation is to be performed or
129: * not. Would have liked to have added a property that these methods could
130: * have used but would force instantiate on an otherwise static class
131: *
132: * @param xmlSream
133: * @param validateXML
134: * @return JDOM Document
135: * @throws InvalidXMLException
136: */
137: public static org.jdom.Document buildJDocument(
138: StringReader xmlStream, boolean validateXML)
139: throws InvalidXmlException {
140: SAXBuilder builder = new SAXBuilder(false);
141: org.jdom.Document doc = null;
142:
143: try {
144: doc = builder.build(xmlStream);
145: } /*
146: * catch (IOException e) { LOG.error("error building jdom
147: * document"); throw new InvalidXmlException("Invalid xml string. " +
148: * e.getMessage()); }
149: */catch (Exception e) {
150: LOG.error("error building jdom document");
151: throw new InvalidXmlException("Invalid xml string. "
152: + e.getMessage());
153: }
154:
155: return doc;
156: }
157:
158: /**
159: * readerToString: read entire content of a Reader into a String
160: *
161: * @param is
162: * @return String
163: * @throws IOException
164: */
165: public static String readerToString(Reader is) throws IOException {
166: // local variables
167: StringBuffer sb = new StringBuffer();
168: char[] b = new char[2000];
169: int n;
170:
171: // Read a block. If it gets any chars, append them.
172: while ((n = is.read(b)) > 0) {
173: sb.append(b, 0, n);
174: }
175:
176: // Only construct the String object once, here.
177: return sb.toString();
178: }
179:
180: // end ==> readerToString()
181:
182: /**
183: * Find all Elements in document of a particular name
184: *
185: * @param root -
186: * the starting Element to scan
187: * @param elementName -
188: * name of the Element to scan for
189: * @return Vector - a list of the Elements found - return null if none found
190: */
191: public static Vector findElements(Element root, String elementName) {
192: Vector elementList = new Vector();
193:
194: if (root == null) {
195: return elementList;
196: }
197:
198: XmlHelper.findElements(root, elementName, elementList);
199:
200: return elementList;
201: }
202:
203: // end ==> findElements()
204:
205: /**
206: * returns the one element in root according to elementName. If more than
207: * one or none of element exists InvalidXmlException is thrown. This method
208: * is based on the assumption you have validated your xml and know the
209: * element is there. It provides a way to make code independent of the xml
210: * structure i.e. it digs for the element.
211: *
212: * @param root
213: * @param elementName
214: * @return Element
215: * @throws InvalidXmlException
216: */
217: public static Element findElement(Element root, String elementName)
218: throws InvalidXmlException {
219: Vector elementList = XmlHelper.findElements(root, elementName);
220:
221: if (elementList.size() < 1) {
222: return null;
223: }
224:
225: if (elementList.size() > 1) {
226: throw new InvalidXmlException(
227: "More than one element in root");
228: }
229:
230: return (Element) elementList.get(0);
231: }
232:
233: /**
234: * makes a properly formed element in the manner of <elementName value=""/>
235: * if value is null value attribute is given an empty value
236: *
237: * @param elementName
238: * @param value
239: * @return Element
240: * @throws Exception
241: */
242: public static Element makeElement(String elementName, String value)
243: throws Exception {
244: if ((elementName == null) || elementName.trim().equals("")) {
245: throw new Exception(
246: "Programmatic error: Element Name passed in null or blank");
247: }
248:
249: Element element = new Element(elementName);
250:
251: if ((value == null) || value.trim().equals("")) {
252: element.setAttribute("value", "");
253: } else {
254: element.setAttribute("value", value);
255: }
256:
257: return element;
258: }
259:
260: /**
261: * Returns the value of the given element names given attribute tag based
262: * upon the root element passed in.
263: *
264: * @param root
265: * @param elementName
266: * @param attributeName
267: * @return value of the Element's attribute who's name matches the passed
268: * element name and attribute matches the attribute name passes
269: * @throws InvalidXmlException
270: * if element or attribute are not present
271: */
272: public static String getElementAttributeValue(Element root,
273: String elementName, String attributeName)
274: throws InvalidXmlException {
275: Element element = XmlHelper.findElement(root, elementName);
276: Attribute attribute = element.getAttribute(attributeName);
277:
278: if (attribute == null) {
279: throw new InvalidXmlException(
280: "The Attribute name given is not present in the element "
281: + element.getName());
282: }
283:
284: return attribute.getValue();
285: }
286:
287: /**
288: * This function is tail-recursive and just adds the root to the list if it
289: * matches and checks the children.
290: *
291: * @param root
292: * @param elementName
293: * @param list
294: */
295: private static void findElements(Element root, String elementName,
296: List list) {
297: if (root != null) {
298: if (root.getName().equals(elementName)) {
299: list.add(root);
300: }
301:
302: Iterator iter = root.getChildren().iterator();
303:
304: while (iter.hasNext()) {
305: Element item = (Element) iter.next();
306:
307: if (item != null) {
308: XmlHelper.findElements(item, elementName, list);
309: }
310: }
311: }
312: }
313:
314: public static String getTextContent(org.w3c.dom.Element element) {
315: NodeList children = element.getChildNodes();
316: Node node = children.item(0);
317: return node.getNodeValue();
318: }
319:
320: public static String jotDocument(org.jdom.Document document) {
321: XMLOutputter outputer = new XMLOutputter(Format
322: .getPrettyFormat());
323: StringWriter writer = new StringWriter();
324: try {
325: outputer.output(document, writer);
326: } catch (IOException e) {
327: throw new WorkflowRuntimeException(
328: "Could not write XML data export.", e);
329: }
330: return writer.toString();
331: }
332:
333: public static String jotNode(org.jdom.Element element) {
334: XMLOutputter outputer = new XMLOutputter(Format
335: .getPrettyFormat());
336: StringWriter writer = new StringWriter();
337: try {
338: outputer.output(element, writer);
339: } catch (IOException e) {
340: throw new WorkflowRuntimeException(
341: "Could not write XML data export.", e);
342: }
343: return writer.toString();
344: }
345:
346: public static String jotNode(org.w3c.dom.Node node) {
347: // default to true since this is used mostly for debugging
348: return jotNode(node, true);
349: }
350:
351: public static String jotNode(org.w3c.dom.Node node, boolean indent) {
352: try {
353: return writeNode(node, indent);
354: } catch (TransformerException te) {
355: return Utilities.collectStackTrace(te);
356: }
357: }
358:
359: public static String writeNode(org.w3c.dom.Node node)
360: throws TransformerException {
361: return writeNode(node, false);
362: }
363:
364: public static String writeNode(org.w3c.dom.Node node, boolean indent)
365: throws TransformerException {
366: Source source = new DOMSource(node);
367: StringWriter writer = new StringWriter();
368: Result result = new StreamResult(writer);
369: Transformer transformer = TransformerFactory.newInstance()
370: .newTransformer();
371: transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,
372: "yes");
373: if (indent) {
374: transformer.setOutputProperty(OutputKeys.INDENT, "yes");
375: }
376: transformer.transform(source, result);
377: return writer.toString();
378: }
379:
380: public static void appendXml(Node parentNode, String xml)
381: throws TransformerException {
382: Source source = new StreamSource(new BufferedReader(
383: new StringReader(xml)));
384: DOMResult result = new DOMResult(parentNode);
385: Transformer transformer = TransformerFactory.newInstance()
386: .newTransformer();
387: transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,
388: "yes");
389: transformer.transform(source, result);
390: }
391:
392: public static org.w3c.dom.Document readXml(String xml)
393: throws TransformerException {
394: Source source = new StreamSource(new BufferedReader(
395: new StringReader(xml)));
396: DOMResult result = new DOMResult();
397: Transformer transformer = TransformerFactory.newInstance()
398: .newTransformer();
399: transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,
400: "yes");
401: transformer.transform(source, result);
402: return (org.w3c.dom.Document) result.getNode();
403: }
404:
405: public static void propogateNamespace(Element element,
406: Namespace namespace) {
407: element.setNamespace(namespace);
408: for (Iterator iterator = element.getChildren().iterator(); iterator
409: .hasNext();) {
410: Element childElement = (Element) iterator.next();
411: propogateNamespace(childElement, namespace);
412: }
413: }
414:
415: public static org.w3c.dom.Document trimXml(InputStream input)
416: throws SAXException, IOException,
417: ParserConfigurationException {
418: DocumentBuilderFactory factory = DocumentBuilderFactory
419: .newInstance();
420: factory.setIgnoringElementContentWhitespace(true);
421: DocumentBuilder builder = factory.newDocumentBuilder();
422: org.w3c.dom.Document oldDocument = builder.parse(input);
423: org.w3c.dom.Element naviElement = oldDocument
424: .getDocumentElement();
425: trimElement(naviElement);
426: return oldDocument;
427: }
428:
429: public static void trimElement(Node node) throws SAXException,
430: IOException, ParserConfigurationException {
431:
432: if (node.hasChildNodes()) {
433: // System.out.println(node.getNodeType()+"; "+node.getNodeName()+";
434: // "+node.getNodeValue());
435: NodeList children = node.getChildNodes();
436: for (int i = 0; i < children.getLength(); i++) {
437: Node child = children.item(i);
438: if (child != null) {
439: // System.out.println(child.getNodeType()+";
440: // "+child.getNodeName()+"; "+child.getNodeValue());
441: trimElement(child);
442: }
443: }
444: } else {
445: // System.out.println(node.getNodeType()+"; "+node.getNodeName()+";
446: // "+node.getNodeValue());
447: if (node.getNodeType() == Node.TEXT_NODE) {
448: String text = node.getNodeValue();
449: if (Utilities.isEmpty(text)) {
450: text = "";
451: } else {
452: text = text.trim();
453: }
454: node.setNodeValue(text);
455: }
456: }
457: }
458:
459: public static void printDocumentStructure(org.w3c.dom.Document doc) {
460: org.w3c.dom.Element naviElement = doc.getDocumentElement();
461: printNode(naviElement, 0);
462: }
463:
464: public static void printNode(Node node, int level) {
465: if (node.getNodeType() == Node.TEXT_NODE) {
466: System.out.print(node.getNodeValue());
467: return;
468: } else {
469: /*
470: * System.out.println("\n"); for(int i=0;i<level;i++){
471: * System.out.print(" "); }
472: */
473: System.out.print("<" + node.getNodeName());
474: if (node.hasAttributes()) {
475: NamedNodeMap attrMap = node.getAttributes();
476: for (int i = 0; i < attrMap.getLength(); i++) {
477: org.w3c.dom.Attr attribute = (org.w3c.dom.Attr) attrMap
478: .item(i);
479: System.out.print(" " + attribute.getName().trim()
480: + "=\"" + attribute.getValue() + "\"");
481: }
482: }
483: System.out.print(">");
484: if (node.hasChildNodes()) {
485: NodeList children = node.getChildNodes();
486: for (int i = 0; i < children.getLength(); i++) {
487: Node child = children.item(i);
488: if (child != null) {
489: // System.out.println(child.getNodeType()+";
490: // "+child.getNodeName()+"; "+child.getNodeValue());
491: printNode(child, level + 1);
492: }
493: }
494: }
495: System.out.print("</" + node.getNodeName() + ">");
496:
497: }
498: }
499:
500: public static Document trimSAXXml(InputStream input)
501: throws JDOMException, SAXException, IOException,
502: ParserConfigurationException {
503: SAXBuilder builder = new SAXBuilder(false);
504: Document oldDocument = builder.build(input);
505: Element naviElement = oldDocument.getRootElement();
506: trimSAXElement(naviElement);
507: return oldDocument;
508: }
509:
510: public static void trimSAXElement(Element element)
511: throws SAXException, IOException,
512: ParserConfigurationException {
513:
514: if (!element.getChildren().isEmpty()) {
515:
516: java.util.List children = element.getChildren();
517: for (int i = 0; i < children.size(); i++) {
518: Element child = (Element) children.get(i);
519: if (child != null) {
520: // System.out.println(child.getNodeType()+";
521: // "+child.getNodeName()+"; "+child.getNodeValue());
522: trimSAXElement(child);
523: }
524: }
525: } else {
526: // System.out.println(node.getNodeType()+"; "+node.getNodeName()+";
527: // "+node.getNodeValue());
528: // System.out.println(element.getName());
529: String text = element.getTextTrim();
530: if (Utilities.isEmpty(text)) {
531: text = "";
532: }
533: element.setText(text);
534:
535: }
536: }
537:
538: public static void printSAXDocumentStructure(Document doc) {
539: Element naviElement = doc.getRootElement();
540: printSAXNode(naviElement, 0);
541: }
542:
543: public static void printSAXNode(Element element, int level) {
544: if (element.getChildren().isEmpty()) {
545: System.out.print("<" + element.getName().trim() + ">"
546: + element.getText() + "</"
547: + element.getName().trim() + ">");
548: return;
549: } else {
550: /*
551: * System.out.println("\n"); for(int i=0;i<level;i++){
552: * System.out.print(" "); }
553: */
554: System.out.print("<" + element.getName());
555: org.jdom.Namespace ns = element.getNamespace();
556: if (ns != null) {
557: System.out.print(" xmlns=\"" + ns.getURI() + "\"");
558:
559: }
560: ns = element.getNamespace("xsi");
561: if (ns != null) {
562: System.out.print(" xmlns:" + ns.getPrefix() + "=\""
563: + ns.getURI() + "\"");
564: }
565: if (element.getAttributes() != null
566: && element.getAttributes().size() > 0) {
567: List attrMap = element.getAttributes();
568: for (int i = 0; i < attrMap.size(); i++) {
569: Attribute attribute = (Attribute) attrMap.get(i);
570: ns = attribute.getNamespace();
571: System.out.print(" ");
572: if (ns != null) {
573: System.out.print(ns.getPrefix() + ":");
574: }
575: System.out.print(attribute.getName().trim() + "=\""
576: + attribute.getValue() + "\"");
577: }
578: }
579: System.out.print(">");
580: List children = element.getChildren();
581: for (int i = 0; i < children.size(); i++) {
582: Element child = (Element) children.get(i);
583: if (child != null) {
584: // System.out.println(child.getNodeType()+";
585: // "+child.getNodeName()+"; "+child.getNodeValue());
586: printSAXNode(child, level + 1);
587: }
588: }
589:
590: System.out.print("</" + element.getName() + ">");
591:
592: }
593: }
594:
595: /**
596: * Convenience method that performs an xpath evaluation to determine whether the expression
597: * evaluates to true (a node exists).
598: * This is method exists only to disambiguate the cases of determining the *presence* of a node
599: * and determining the *boolean value of the node as converted from a string*, as the syntaxes
600: * are very similar and could be misleading.
601: * @param xpath the XPath object
602: * @param expression the XPath expression
603: * @param object the object on which to evaluate the expression as required by the XPath API, typically a Node
604: * @return whether the result of the expression evaluation, which is whether or not a node was present
605: * @throws XPathExpressionException
606: */
607: public static boolean pathExists(XPath xpath, String expression,
608: Object object) throws XPathExpressionException {
609: return ((Boolean) xpath.evaluate(expression, object,
610: XPathConstants.BOOLEAN)).booleanValue();
611: }
612:
613: public static void validate(final InputSource source)
614: throws ParserConfigurationException, IOException,
615: SAXException {
616: DocumentBuilderFactory dbf = DocumentBuilderFactory
617: .newInstance();
618: dbf.setValidating(true);
619: dbf.setNamespaceAware(true);
620: dbf
621: .setAttribute(
622: "http://java.sun.com/xml/jaxp/properties/schemaLanguage",
623: XMLConstants.W3C_XML_SCHEMA_NS_URI);
624: DocumentBuilder db = dbf.newDocumentBuilder();
625: db.setEntityResolver(ENTITY_RESOLVER);
626: db.setErrorHandler(new ErrorHandler() {
627: public void warning(SAXParseException se) {
628: LOG.warn("Warning parsing xml doc " + source, se);
629: }
630:
631: public void error(SAXParseException se) throws SAXException {
632: LOG.error("Error parsing xml doc " + source, se);
633: throw se;
634: }
635:
636: public void fatalError(SAXParseException se)
637: throws SAXException {
638: LOG.error("Fatal error parsing xml doc " + source, se);
639: throw se;
640: }
641: });
642: db.parse(source);
643: }
644:
645: public static org.w3c.dom.Element propertiesToXml(
646: org.w3c.dom.Document doc, Object o, String elementName)
647: throws Exception {
648: Class c = o.getClass();
649: org.w3c.dom.Element wrapper = doc.createElement(elementName);
650: Method[] methods = c.getMethods();
651: for (int i = 0; i < methods.length; i++) {
652: String name = methods[i].getName();
653: if ("getClass".equals(name))
654: continue;
655: if (!name.startsWith("get")
656: || methods[i].getParameterTypes().length > 0)
657: continue;
658: name = name.substring("get".length());
659: name = StringUtils.uncapitalize(name);
660: String value = null;
661: try {
662: Object result = methods[i].invoke(o, null);
663: if (result == null) {
664: LOG.debug("value of " + name + " method on object "
665: + o.getClass() + " is null");
666: value = "";
667: } else {
668: value = result.toString();
669: }
670: org.w3c.dom.Element fieldE = doc.createElement(name);
671: fieldE.appendChild(doc.createTextNode(value));
672: wrapper.appendChild(fieldE);
673: } catch (RuntimeException e) {
674: LOG
675: .error("Error accessing method '"
676: + methods[i].getName()
677: + " of instance of " + c);
678: throw e;
679: } catch (Exception e) {
680: LOG
681: .error("Error accessing method '"
682: + methods[i].getName()
683: + " of instance of " + c);
684: }
685: }
686: return wrapper;
687: }
688: }
|