001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.geoserver.util;
006:
007: import java.io.ByteArrayInputStream;
008: import java.io.ByteArrayOutputStream;
009: import java.io.File;
010: import java.io.FileNotFoundException;
011: import java.io.InputStreamReader;
012: import java.io.Reader;
013: import java.util.ArrayList;
014: import java.util.List;
015: import java.util.logging.Level;
016: import java.util.logging.Logger;
017:
018: import javax.naming.ConfigurationException;
019: import javax.xml.parsers.DocumentBuilderFactory;
020: import javax.xml.parsers.SAXParser;
021: import javax.xml.parsers.SAXParserFactory;
022: import javax.xml.transform.Transformer;
023: import javax.xml.transform.TransformerFactory;
024: import javax.xml.transform.dom.DOMSource;
025: import javax.xml.transform.stream.StreamResult;
026:
027: import org.w3c.dom.Attr;
028: import org.w3c.dom.Document;
029: import org.w3c.dom.Element;
030: import org.w3c.dom.Node;
031: import org.w3c.dom.NodeList;
032: import org.xml.sax.InputSource;
033: import org.xml.sax.helpers.DefaultHandler;
034:
035: /**
036: * ReaderUtils purpose.
037: *
038: * <p>
039: * This class is intended to be used as a library of XML relevant operation for
040: * the XMLConfigReader class.
041: * </p>
042: *
043: * <p></p>
044: *
045: * @author dzwiers, Refractions Research, Inc.
046: * @version $Id: ReaderUtils.java 7958 2007-12-06 16:25:43Z arneke $
047: *
048: * @see XMLConfigReader
049: */
050: public class ReaderUtils {
051: /** Used internally to create log information to detect errors. */
052: private static final Logger LOGGER = org.geotools.util.logging.Logging
053: .getLogger("org.vfny.geoserver.global");
054:
055: /**
056: * ReaderUtils constructor.
057: *
058: * <p>
059: * Static class, this should never be called.
060: * </p>
061: */
062: private ReaderUtils() {
063: }
064:
065: /**
066: * Parses the specified reader into a DOM tree.
067: *
068: * @param xml Reader representing xml stream to parse.
069: *
070: * @return the root element of resulting DOM tree
071: *
072: * @throws RuntimeException If reader failed to parse properly.
073: */
074: public static Element parse(Reader xml) {
075: InputSource in = new InputSource(xml);
076: DocumentBuilderFactory dfactory = DocumentBuilderFactory
077: .newInstance();
078:
079: dfactory.setNamespaceAware(false);
080: dfactory.setValidating(false);
081: dfactory.setIgnoringComments(true);
082: dfactory.setCoalescing(true);
083: dfactory.setIgnoringElementContentWhitespace(true);
084:
085: Document doc;
086:
087: try {
088: doc = dfactory.newDocumentBuilder().parse(in);
089: } catch (Exception e) {
090: String msg = "Error reading : " + xml;
091: throw new RuntimeException(msg, e);
092: }
093:
094: return doc.getDocumentElement();
095: }
096:
097: /**
098: * Checks to ensure the file is valid.
099: *
100: * <p>
101: * Returns the file passed in to allow this to wrap file creations.
102: * </p>
103: *
104: * @param file A file Handle to test.
105: * @param isDir true when the File passed in is expected to be a directory,
106: * false when the handle is expected to be a file.
107: *
108: * @return the File handle passed in
109: *
110: * @throws Exception When the file does not exist or is not
111: * the type specified.
112: */
113: public static File checkFile(File file, boolean isDir)
114: throws FileNotFoundException {
115: if (!file.exists()) {
116: throw new FileNotFoundException((isDir ? "Folder" : "File")
117: + " does not exist: " + file);
118: }
119:
120: if (isDir && !file.isDirectory()) {
121: throw new FileNotFoundException(
122: "File exists but is not a directory:" + file);
123: }
124:
125: if (!isDir && !file.isFile()) {
126: //may it be some sort of OS special file (e.g. /dev/tty1)
127: throw new FileNotFoundException(
128: "File exists but is not a regular file:" + file);
129: }
130:
131: if (LOGGER.isLoggable(Level.FINER)) {
132: LOGGER.finer(new StringBuffer("File is valid: ").append(
133: file).toString());
134: }
135:
136: return file;
137: }
138:
139: /**
140: * getChildElements purpose.
141: *
142: * <p>
143: * Used to help with XML manipulations. Returns *all* child elements of
144: * the specified name.
145: * </p>
146: *
147: * @param root The root element to look for children in.
148: * @param name The name of the child element to look for.
149: *
150: * @return The child element found, null if not found.
151: *
152: * @see getChildElement(Element,String,boolean)
153: */
154: public static Element[] getChildElements(Element root, String name) {
155: try {
156: return getChildElements(root, name, false);
157: } catch (Exception e) {
158: //will never be here.
159: return null;
160: }
161: }
162:
163: /**
164: * getChildElements purpose.
165: *
166: * <p>
167: * Used to help with XML manipulations. Returns *all* child elements of
168: * the specified name. An exception occurs when the node is required and
169: * not found.
170: * </p>
171: *
172: * @param root The root element to look for children in.
173: * @param name The name of the child element to look for.
174: * @param mandatory true when an exception should be thrown if the child
175: * element does not exist.
176: *
177: * @return The child element found, null if not found.
178: *
179: * @throws Exception When a child element is required and not
180: * found.
181: */
182: public static Element[] getChildElements(Element root, String name,
183: boolean mandatory) throws Exception {
184: ArrayList elements = new ArrayList();
185: Node child = root.getFirstChild();
186:
187: while (child != null) {
188: if (child.getNodeType() == Node.ELEMENT_NODE) {
189: if (name.equals(child.getNodeName())) {
190: elements.add((Element) child);
191: }
192: }
193:
194: child = child.getNextSibling();
195: }
196:
197: if (mandatory && (elements.isEmpty())) {
198: throw new Exception(root.getNodeName()
199: + " does not contains a child element named "
200: + name);
201: }
202:
203: return (Element[]) elements.toArray(new Element[0]);
204: }
205:
206: /**
207: * getChildElement purpose.
208: *
209: * <p>
210: * Used to help with XML manipulations. Returns the first child element of
211: * the specified name. An exception occurs when the node is required and
212: * not found.
213: * </p>
214: *
215: * @param root The root element to look for children in.
216: * @param name The name of the child element to look for.
217: * @param mandatory true when an exception should be thrown if the child
218: * element does not exist.
219: *
220: * @return The child element found, null if not found.
221: *
222: * @throws Exception When a child element is required and not
223: * found.
224: */
225: public static Element getChildElement(Element root, String name,
226: boolean mandatory) throws Exception {
227: Node child = root.getFirstChild();
228:
229: while (child != null) {
230: if (child.getNodeType() == Node.ELEMENT_NODE) {
231: if (name.equals(child.getNodeName())) {
232: return (Element) child;
233: }
234: }
235:
236: child = child.getNextSibling();
237: }
238:
239: if (mandatory && (child == null)) {
240: throw new Exception(root.getNodeName()
241: + " does not contains a child element named "
242: + name);
243: }
244:
245: return null;
246: }
247:
248: /**
249: * getChildElement purpose.
250: *
251: * <p>
252: * Used to help with XML manipulations. Returns the first child element of
253: * the specified name.
254: * </p>
255: *
256: * @param root The root element to look for children in.
257: * @param name The name of the child element to look for.
258: *
259: * @return The child element found, null if not found.
260: *
261: * @see getChildElement(Element,String,boolean)
262: */
263: public static Element getChildElement(Element root, String name) {
264: try {
265: return getChildElement(root, name, false);
266: } catch (Exception e) {
267: //will never be here.
268: return null;
269: }
270: }
271:
272: /**
273: * getIntAttribute purpose.
274: *
275: * <p>
276: * Used to help with XML manipulations. Returns the first child integer
277: * attribute of the specified name. An exception occurs when the node is
278: * required and not found.
279: * </p>
280: *
281: * @param elem The root element to look for children in.
282: * @param attName The name of the attribute to look for.
283: * @param mandatory true when an exception should be thrown if the
284: * attribute element does not exist.
285: * @param defaultValue a default value to return incase the attribute was
286: * not found. mutually exclusive with the Exception
287: * thrown.
288: *
289: * @return The int value if the attribute was found, the default otherwise.
290: *
291: * @throws Exception When a attribute element is required and
292: * not found.
293: */
294: public static int getIntAttribute(Element elem, String attName,
295: boolean mandatory, int defaultValue) throws Exception {
296: String attValue = getAttribute(elem, attName, mandatory);
297:
298: if (!mandatory && (attValue == null)) {
299: return defaultValue;
300: }
301:
302: try {
303: return Integer.parseInt(attValue);
304: } catch (Exception ex) {
305: if (mandatory) {
306: throw new Exception(attName + " attribute of element "
307: + elem.getNodeName()
308: + " must be an integer, but it's '" + attValue
309: + "'");
310: } else {
311: return defaultValue;
312: }
313: }
314: }
315:
316: /**
317: * getIntAttribute purpose.
318: *
319: * <p>
320: * Used to help with XML manipulations. Returns the first child integer
321: * attribute of the specified name. An exception occurs when the node is
322: * required and not found.
323: * </p>
324: *
325: * @param elem The root element to look for children in.
326: * @param attName The name of the attribute to look for.
327: * @param mandatory true when an exception should be thrown if the
328: * attribute element does not exist.
329: *
330: * @return The value if the attribute was found, the null otherwise.
331: *
332: * @throws Exception When a child attribute is required and
333: * not found.
334: * @throws NullPointerException DOCUMENT ME!
335: */
336: public static String getAttribute(Element elem, String attName,
337: boolean mandatory) throws Exception {
338: if (elem == null) {
339: if (mandatory) {
340: throw new NullPointerException();
341: }
342:
343: return "";
344: }
345:
346: Attr att = elem.getAttributeNode(attName);
347:
348: String value = null;
349:
350: if (att != null) {
351: value = att.getValue();
352: }
353:
354: if (mandatory) {
355: if (att == null) {
356: throw new Exception("element " + elem.getNodeName()
357: + " does not contains an attribute named "
358: + attName);
359: } else if ("".equals(value)) {
360: throw new Exception("attribute " + attName
361: + "in element " + elem.getNodeName()
362: + " is empty");
363: }
364: }
365:
366: return value;
367: }
368:
369: /**
370: * getBooleanAttribute purpose.
371: *
372: * <p>
373: * Used to help with XML manipulations. Returns the first child integer
374: * attribute of the specified name. An exception occurs when the node is
375: * required and not found.
376: * </p>
377: *
378: * @param elem The root element to look for children in.
379: * @param attName The name of the attribute to look for.
380: * @param mandatory true when an exception should be thrown if the
381: * attribute element does not exist.
382: * @param defaultValue what to return for a non-mandatory that is not
383: * found.
384: *
385: * @return The value if the attribute was found, the false otherwise.
386: *
387: * @throws Exception When a child attribute is required and
388: * not found.
389: */
390: public static boolean getBooleanAttribute(Element elem,
391: String attName, boolean mandatory, boolean defaultValue)
392: throws Exception {
393: String value = getAttribute(elem, attName, mandatory);
394:
395: if ((value == null) || (value == "")) {
396: return defaultValue;
397: }
398:
399: return Boolean.valueOf(value).booleanValue();
400: }
401:
402: /**
403: * getChildText purpose.
404: *
405: * <p>
406: * Used to help with XML manipulations. Returns the first child text value
407: * of the specified element name.
408: * </p>
409: *
410: * @param root The root element to look for children in.
411: * @param childName The name of the attribute to look for.
412: *
413: * @return The value if the child was found, the null otherwise.
414: */
415: public static String getChildText(Element root, String childName) {
416: try {
417: return getChildText(root, childName, false);
418: } catch (Exception ex) {
419: return null;
420: }
421: }
422:
423: /**
424: * getChildText purpose.
425: *
426: * <p>
427: * Used to help with XML manipulations. Returns the first child text value
428: * of the specified element name. An exception occurs when the node is
429: * required and not found.
430: * </p>
431: *
432: * @param root The root element to look for children in.
433: * @param childName The name of the attribute to look for.
434: * @param mandatory true when an exception should be thrown if the text
435: * does not exist.
436: *
437: * @return The value if the child was found, the null otherwise.
438: *
439: * @throws Exception When a child attribute is required and
440: * not found.
441: */
442: public static String getChildText(Element root, String childName,
443: boolean mandatory) throws Exception {
444: Element elem = getChildElement(root, childName, mandatory);
445:
446: if (elem != null) {
447: return getElementText(elem, mandatory);
448: } else {
449: if (mandatory) {
450: String msg = "Mandatory child " + childName
451: + "not found in " + " element: " + root;
452:
453: throw new Exception(msg);
454: }
455:
456: return null;
457: }
458: }
459:
460: /**
461: * getChildText purpose.
462: *
463: * <p>
464: * Used to help with XML manipulations. Returns the text value of the
465: * specified element name.
466: * </p>
467: *
468: * @param elem The root element to look for children in.
469: *
470: * @return The value if the text was found, the null otherwise.
471: */
472: public static String getElementText(Element elem) {
473: try {
474: return getElementText(elem, false);
475: } catch (Exception ex) {
476: return null;
477: }
478: }
479:
480: /**
481: * getChildText purpose.
482: *
483: * <p>
484: * Used to help with XML manipulations. Returns the text value of the
485: * specified element name. An exception occurs when the node is required
486: * and not found.
487: * </p>
488: *
489: * @param elem The root element to look for children in.
490: * @param mandatory true when an exception should be thrown if the text
491: * does not exist.
492: *
493: * @return The value if the text was found, the null otherwise.
494: *
495: * @throws Exception When text is required and not found.
496: */
497: public static String getElementText(Element elem, boolean mandatory)
498: throws Exception {
499: String value = null;
500:
501: if (LOGGER.isLoggable(Level.FINER)) {
502: LOGGER.finer(new StringBuffer("getting element text for ")
503: .append(elem).toString());
504: }
505:
506: if (elem != null) {
507: Node child;
508:
509: NodeList childs = elem.getChildNodes();
510:
511: int nChilds = childs.getLength();
512:
513: for (int i = 0; i < nChilds; i++) {
514: child = childs.item(i);
515:
516: if (child.getNodeType() == Node.TEXT_NODE) {
517: value = child.getNodeValue();
518:
519: if (mandatory && "".equals(value.trim())) {
520: throw new Exception(elem.getNodeName()
521: + " text is empty");
522: }
523:
524: break;
525: }
526: }
527:
528: if (mandatory && (value == null)) {
529: throw new Exception(elem.getNodeName()
530: + " element does not contains text");
531: }
532: } else {
533: throw new Exception("Argument element can't be null");
534: }
535:
536: return unescape(value);
537: }
538:
539: /**
540: * getKeyWords purpose.
541: *
542: * <p>
543: * Used to help with XML manipulations. Returns a list of keywords that
544: * were found.
545: * </p>
546: *
547: * @param keywordsElem The root element to look for children in.
548: *
549: * @return The list of keywords that were found.
550: */
551: public static List getKeyWords(Element keywordsElem) {
552: NodeList klist = keywordsElem.getElementsByTagName("keyword");
553: int kCount = klist.getLength();
554: List keywords = new ArrayList(kCount);
555: String kword;
556: Element kelem;
557:
558: for (int i = 0; i < kCount; i++) {
559: kelem = (Element) klist.item(i);
560: kword = getElementText(kelem);
561:
562: if (kword != null) {
563: keywords.add(kword);
564: }
565: }
566:
567: Object[] s = (Object[]) keywords.toArray();
568:
569: if (s == null) {
570: return new ArrayList();
571: }
572:
573: ArrayList ss = new ArrayList(s.length);
574:
575: for (int i = 0; i < s.length; i++)
576: ss.add(s[i]);
577:
578: return ss;
579: }
580:
581: /**
582: * getFirstChildElement purpose.
583: *
584: * <p>
585: * Used to help with XML manipulations. Returns the element which
586: * represents the first child.
587: * </p>
588: *
589: * @param root The root element to look for children in.
590: *
591: * @return The element if a child was found, the null otherwise.
592: */
593: public static Element getFirstChildElement(Element root) {
594: Node child = root.getFirstChild();
595:
596: while (child != null) {
597: if (child.getNodeType() == Node.ELEMENT_NODE) {
598: return (Element) child;
599: }
600:
601: child = child.getNextSibling();
602: }
603:
604: return null;
605: }
606:
607: /**
608: * getDoubleAttribute purpose.
609: *
610: * <p>
611: * Used to help with XML manipulations. Returns the first child integer
612: * attribute of the specified name. An exception occurs when the node is
613: * required and not found.
614: * </p>
615: *
616: * @param elem The root element to look for children in.
617: * @param attName The name of the attribute to look for.
618: * @param mandatory true when an exception should be thrown if the
619: * attribute element does not exist.
620: *
621: * @return The double value if the attribute was found, the NaN otherwise.
622: *
623: * @throws Exception When a attribute element is required and
624: * not found.
625: */
626: public static double getDoubleAttribute(Element elem,
627: String attName, boolean mandatory) throws Exception {
628: String value = getAttribute(elem, attName, mandatory);
629:
630: if ((value == null) || (value == "")) {
631: return 0.0;
632: }
633:
634: double d = Double.NaN;
635:
636: if (value != null) {
637: try {
638: d = Double.parseDouble(value);
639: } catch (NumberFormatException ex) {
640: throw new ConfigurationException(
641: "Illegal attribute value for " + attName
642: + " in element " + elem.getNodeName()
643: + ". Expected double, but was " + value);
644: }
645: }
646:
647: return d;
648: }
649:
650: /**
651: * Validates an xml document against a specified schema.
652: *
653: * @param xml The document.
654: * @param errorHandler The validation error handler.
655: * @param targetNamespace The target namespace of the schema, may be <code>null</code>
656: * @param schemaLocation The location of the schema to validate against, may be <code>null</code>
657: *
658: * @throws RuntimeException If reader failed to parse properly.
659: */
660: public static void validate(Document xml,
661: DefaultHandler errorHandler, String targetNamespace,
662: String schemaLocation) {
663: try {
664: Transformer tx = TransformerFactory.newInstance()
665: .newTransformer();
666: ByteArrayOutputStream output = new ByteArrayOutputStream();
667: tx.transform(new DOMSource(xml), new StreamResult(output));
668:
669: InputStreamReader reader = new InputStreamReader(
670: new ByteArrayInputStream(output.toByteArray()));
671: validate(reader, errorHandler, targetNamespace,
672: schemaLocation);
673: } catch (Exception e) {
674: throw new RuntimeException(e);
675: }
676: }
677:
678: /**
679: * Validates an xml document against a specified schema.
680: *
681: * @param xml Reader representing xml stream to parse.
682: * @param errorHandler The validation error handler.
683: * @param targetNamespace The target namespace of the schema, may be <code>null</code>
684: * @param schemaLocation The location of the schema to validate against, may be <code>null</code>
685: *
686: * @throws RuntimeException If reader failed to parse properly.
687: */
688: public static void validate(Reader xml,
689: DefaultHandler errorHandler, String targetNamespace,
690: String schemaLocation) {
691: InputSource in = new InputSource(xml);
692:
693: try {
694:
695: //TODO: pretty sure this doesn't actually do validation
696: // ahhh... xml in java....
697: SAXParserFactory sf = SAXParserFactory.newInstance();
698: sf.setNamespaceAware(true);
699: sf.setValidating(true);
700: SAXParser parser = sf.newSAXParser();
701: parser
702: .setProperty(
703: "http://java.sun.com/xml/jaxp/properties/schemaLanguage",
704: "http://www.w3.org/2001/XMLSchema");
705:
706: // SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
707: // parser.setProperty("http://xml.org/sax/features/validation", Boolean.TRUE);
708: //
709: // parser.setProperty("http://apache.org/xml/features/validation/schema",
710: // Boolean.TRUE);
711: // parser.setProperty("http://apache.org/xml/features/validation/schema-full-checking",
712: // Boolean.TRUE);
713:
714: if (schemaLocation != null) {
715: parser
716: .setProperty(
717: "http://java.sun.com/xml/jaxp/properties/schemaSource",
718: schemaLocation);
719: // if ( targetNamespace != null ) {
720: // parser.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation",
721: // targetNamespace + " " + schemaLocation );
722: // }
723: }
724:
725: parser.parse(in, errorHandler);
726:
727: } catch (Exception e) {
728: String msg = "Error reading : " + xml;
729: throw new RuntimeException(msg, e);
730: }
731: }
732:
733: /**
734: * Unescapes the provided text with XML entities,
735: * see (http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Character_entities_in_XML)
736: * @param text
737: * @return
738: */
739: private static String unescape(String text) {
740: String s = text;
741: if (s != null && s.matches(".*&(.*);.*")) {
742: s = s.replaceAll(""", "\"");
743: s = s.replaceAll("&", "&");
744: s = s.replaceAll("'", "'");
745: s = s.replaceAll("<", "<");
746: s = s.replaceAll(">", ">");
747: }
748: return s;
749: }
750: }
|