/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * XMLPersistenceHelper.java * Creation date: (30-Apr-02 12:19:35 PM) * By: Edward Lam */ package org.openquark.util.xml; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.openquark.util.Messages; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.CharacterData; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.Text; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; /** /** * A non-instantiable helper class to facilitate conversions between objects and their XML representations. * @author Edward Lam */ public final class XMLPersistenceHelper { /** Use this message bundle to dig up localized messages */ private static final Messages messages = PackageMessages.instance; /** All output will use this encoding */ private static final String OUTPUT_ENCODING = "UTF-8"; //$NON-NLS-1$ /** The DocumentBuilderFactory object that can be used to build DocumentsBuilders */ private static final DocumentBuilderFactory defaultDocumentBuilderFactory = DocumentBuilderFactory.newInstance(); /** * A thread-local DocumentBuilder object that can be used to build Documents. * This builder is created by the default document builder factory. * We use thread-local because they are not thread-safe. */ private static final ThreadLocal<DocumentBuilder> threadLocalDocumentBuilder = new ThreadLocal<DocumentBuilder>() { protected synchronized DocumentBuilder initialValue() { // Create the document builder DocumentBuilder builder = null; try { // Note that because this in initialValue(), this should happen after the static initializer, // where the factory is configured. builder = defaultDocumentBuilderFactory.newDocumentBuilder(); } catch (ParserConfigurationException pce) { pce.printStackTrace(); } return builder; } }; /** The TransformerFactory is used to create XML transformers */ private static final TransformerFactory transformerFactory; /* * Static initializer - set document builder factory options */ static { // Set namespaceAware to true to get a DOM Level 2 tree with nodes containing namespace information. // This is necessary because the default value from JAXP 1.0 was defined to be false. defaultDocumentBuilderFactory.setNamespaceAware(true); // For JDK 5.0 (different from name for JDK 1.4) Class<?> transformerFactoryClass = classForName ("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"); //$NON-NLS-1$ TransformerFactory factory = null; if (transformerFactoryClass != null) try { factory = (TransformerFactory)transformerFactoryClass.newInstance (); // Set the indent amount for 5.0 // Note that for 1.4 it is set when the transformer is created factory.setAttribute("indent-number", Integer.valueOf(2)); //$NON-NLS-1$ } catch (Exception e) { e.printStackTrace (); } transformerFactory = factory; } /** * Method classForName * * @param className * * @return Returns the Class for the given name, or null */ private static Class<?> classForName (String className) { try { return Class.forName (className); } catch (ClassNotFoundException e) { return null; } } /** * Error handler to report errors and warnings * Creation date: (30-Apr-02 1:46:52 PM) * @author Edward Lam */ private static class SaxErrorHandler implements ErrorHandler { /** Error handler output goes here */ private PrintWriter out; /** * Default constructor * @param out where the errors should go. */ SaxErrorHandler(PrintWriter out) { this.out = out; } /** * Returns a string describing parse exception details * @param spe the relevant exception * @return the string with the exception info. */ private String getParseExceptionInfo(SAXParseException spe) { String systemId = spe.getSystemId(); if (systemId == null) { systemId = "null"; //$NON-NLS-1$ } String info = "URI=" + systemId + " Line=" + spe.getLineNumber() + ": " + spe.getMessage(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ return info; } /* * Methods implementing ErrorHandler ************************************************************ */ /** * Handle a warning. * @param spe the relevant exception * @throws SAXException */ public void warning(SAXParseException spe) throws SAXException { out.println(messages.getString("Warning", getParseExceptionInfo(spe))); //$NON-NLS-1$ } /** * Handle an error. * @param spe the relevant exception * @throws SAXException */ public void error(SAXParseException spe) throws SAXException { String message = messages.getString("Error", getParseExceptionInfo(spe)); //$NON-NLS-1$ throw new SAXException(message); } /** * Handle a fatal error. * @param spe the relevant exception * @throws SAXException */ public void fatalError(SAXParseException spe) throws SAXException { String message = messages.getString("FatalError", getParseExceptionInfo(spe)); //$NON-NLS-1$ throw new SAXException(message); } } /** * An exception class representing a failure to construct a document from its input. * Creation date: (Sep 16, 2002 2:54:04 PM) * @author Edward Lam */ public static final class DocumentConstructionException extends Exception { private static final long serialVersionUID = -7815248783833112814L; /** * Default constructor for a DocumentConstructionException * @param cause */ private DocumentConstructionException(Throwable cause) { super(cause); } } /** * A StateNotPersistableException is thrown when an attempt is made to persist a persistable object that * is not in a persistable state. * @author Edward Lam */ public static final class StateNotPersistableException extends IllegalStateException { private static final long serialVersionUID = -3423092696888926367L; /** * Constructor for a StateNotPersistableException. * @param message String the exception message. */ public StateNotPersistableException(String message) { super(message); } } /** This class is not intended to be instantiated. */ private XMLPersistenceHelper() { } /** * @return a document builder that can be used to build documents. */ private static DocumentBuilder getDocumentBuilder() { return threadLocalDocumentBuilder.get(); } /** * Get an empty, rootless document. * @return Document an empty, rootless document. */ public static Document getEmptyDocument() { return getEmptyDocument(getDocumentBuilder()); } /** * Get an empty, rootless document using the document builder factory provided * @param builder * @return Document an empty, rootless document. */ public static Document getEmptyDocument(DocumentBuilder builder) { // Create a new document. return builder.newDocument(); } /** * Converts a DOM Tree to a string. This method can be used to do a proper * comparison of two xml documents. Helper method that converts the document * to xml using the StringWriter. * @param document Document the XML document in the form of a DOM Tree to convert * @param indentOutput * @return a string containing the written XML for the document */ public static String documentToString(Node document, boolean indentOutput) { StringWriter writer = new StringWriter(); documentToXML(document, writer, indentOutput); return writer.toString(); } /** * Convert a DOM Tree to an XML document. * This method can be used, for example, to write a DOM tree to a file. * @param document Document the XML document in the form of a DOM Tree to convert * @param outputStream OutputStream the OutputStream into which to write the document * @param indentOutput */ public static void documentToXML(Node document, OutputStream outputStream, boolean indentOutput) { // Create a StreamResult object to take the results of the transformation StreamResult resultHolder = new StreamResult(outputStream); documentToXML(document, resultHolder, indentOutput); } /** * Convert a DOM Tree to an XML document. * This method can be used, for example, to write a DOM tree to a file. * @param document Document the XML document in the form of a DOM Tree to convert * @param writer Writer the Writer into which to write the document * @param indentOutput */ public static void documentToXML(Node document, Writer writer, boolean indentOutput) { // Create a StreamResult object to take the results of the transformation StreamResult resultHolder = new StreamResult(writer); documentToXML(document, resultHolder, indentOutput); } /** * Convert a DOM Tree to an XML document. This method can be used, for example, to write a DOM tree to a String. * * @param document * Document the XML document in the form of a DOM Tree to convert * @param resultHolder * StreamResult receives he results of the transformation * @param indentOutput */ private static void documentToXML(Node document, StreamResult resultHolder, boolean indentOutput) { try { // Create a transformer object to perform the transformation Transformer transformer = null; synchronized (transformerFactory) { transformer = transformerFactory.newTransformer(); } if (indentOutput) { // Set the indent property transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ // Try to change the default indent amount using an undocumented option supported by // the apache XML parser (for JDK 1.4) try { transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); //$NON-NLS-1$ //$NON-NLS-2$ } catch (IllegalArgumentException iae) { // The indent option is not standardized and is not available in all XML parsers. // Specifically, the Oracle parser doesn't like it. System.out.println(messages.getString("IndentFailedOnSave")); //$NON-NLS-1$ } } // Construct a DOMSource to act as the source of the transformation DOMSource source = new DOMSource(document); // Transform the tree to XML transformer.transform(source, resultHolder); } catch (TransformerException te) { // shouldn't happen throw new Error(messages.getString("ProgrammingError"), te); //$NON-NLS-1$ } } /** * Convert an XML document into its DOM Tree representation * * @param inputStream InputStream the stream from which to read the XML document. * @return Document the resulting document. Null if the parse failed. * @throws DocumentConstructionException */ public static Document documentFromXML(InputStream inputStream) throws DocumentConstructionException { DocumentBuilder builder = getDocumentBuilder(); return documentFromXML(builder, inputStream); } /** * Convert an XML document into its DOM Tree representation * @param documentBuilder * @param inputStream InputStream the stream from which to read the XML document. * @return Document the resulting document. Null if the parse failed. * @throws DocumentConstructionException */ public static Document documentFromXML(DocumentBuilder documentBuilder, InputStream inputStream) throws DocumentConstructionException { try { // Set an ErrorHandler before parsing OutputStreamWriter errorWriter = new OutputStreamWriter(System.err, OUTPUT_ENCODING); documentBuilder.setErrorHandler(new SaxErrorHandler(new PrintWriter(errorWriter, true))); // Parse the input file Document document = documentBuilder.parse(inputStream); return document; } catch (IOException ioe) { throw new DocumentConstructionException(ioe); } catch (SAXException se) { throw new DocumentConstructionException(se); } } /** * Loads the specified file as an XML DOM document. * * @param pathname * @return Document * @throws DocumentConstructionException */ public static Document documentFromXMLFile(String pathname) throws DocumentConstructionException { InputStream inputStream = null; // Create a DOM document from the XML data. final Document document; try { inputStream = new BufferedInputStream(new FileInputStream(pathname)); document = documentFromXML(inputStream); } catch (IOException ioe) { throw new DocumentConstructionException(ioe); } finally { if (inputStream != null) { try { inputStream.close(); } catch (Exception e) { } } } return document; } /** * Convert an XML document into its DOM Tree representation * * @param xmlString String the XML string data. * @return Document the resulting document. Null if the parse failed. * @throws DocumentConstructionException */ public static Document documentFromXML(String xmlString) throws DocumentConstructionException { DocumentBuilder builder = getDocumentBuilder(); return documentFromXML(builder, xmlString); } /** * Convert an XML document into its DOM Tree representation * @param documentBuilder * @param xmlString String the XML string data. * @return Document the resulting document. Null if the parse failed. * @throws DocumentConstructionException */ public static Document documentFromXML(DocumentBuilder documentBuilder, String xmlString) throws DocumentConstructionException { try { // Set an ErrorHandler before parsing OutputStreamWriter errorWriter = new OutputStreamWriter(System.err, OUTPUT_ENCODING); documentBuilder.setErrorHandler(new SaxErrorHandler(new PrintWriter(errorWriter, true))); // Parse the input file Document document = documentBuilder.parse(new InputSource (new StringReader (xmlString))); return document; } catch (IOException ioe) { throw new DocumentConstructionException(ioe); } catch (SAXException se) { throw new DocumentConstructionException(se); } } /** * Check that a given element has the expected tag name. * Note: only the local part of the element name will be checked. * @param element Element the element to check. * @param expectedTag the tag that the element is expected to have. * @throws BadXMLDocumentException */ public static void checkTag(Element element, String expectedTag) throws BadXMLDocumentException { if (element == null || !element.getLocalName().equals(expectedTag)) { String string = "Null"; //$NON-NLS-1$ handleBadDocument(element, messages.getString("XMLStructureError", expectedTag, //$NON-NLS-1$ (element == null ? messages.getString(string) : element.getTagName()))); } } /** * Check that a given node has the expected prefix. * @param node Node the node to check. * @param expectedPrefix String the prefix that the element is expected to have. * @throws BadXMLDocumentException */ public static void checkPrefix(Node node, String expectedPrefix) throws BadXMLDocumentException { String prefix = node.getPrefix(); if (prefix == null) { if (expectedPrefix != null && expectedPrefix.length() != 0) { handleBadDocument(node, messages.getString("ExpectingPrefix", expectedPrefix)); //$NON-NLS-1$ } } else { if (!prefix.equals(expectedPrefix)) { handleBadDocument(node, messages.getString("ExpectingPrefix2", expectedPrefix, prefix)); //$NON-NLS-1$ } } } /** * Check that a given node is actually an element. * @param node Node the node to check. * @throws BadXMLDocumentException */ public static void checkIsElement(Node node) throws BadXMLDocumentException { if (!(node instanceof Element)) { handleBadDocument(node, (node == null ? messages.getString("XMLStructureError2") : //$NON-NLS-1$ messages.getString("XMLStructureError3", node.getClass().toString()))); //$NON-NLS-1$ } } /** * Check that a given node is actually an element of the specified tag. * @param node Node the node to check. * @param tag * @throws BadXMLDocumentException */ public static void checkIsTagElement(Node node, String tag) throws BadXMLDocumentException { checkIsElement(node); checkTag((Element) node, tag); } /** * Convert instances of a newline in a string to '\n'. * This is particularly necessary when creating a CDATA node from Windows, as the newline separator on this * platform is "\r\n", which (unbelievably) is serialized to "\r\r\n" as a "feature"! Of course, when this is * read back in, it is converted to "\n\n", meaning that the text was converted from single to double spaced. * To guard against this, all instances of "\r\n" and "\r" are converted to "\n". * @param stringToConvert the string to convert. * @return the converted string. */ private static String convertNewLines(String stringToConvert) { return stringToConvert.replaceAll("\r\n", "\n").replaceAll("\r", "\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ } /** * Creates a <code>CDATASection</code> node whose value is the specified string. This is different from * Document.createCDATASection() in that this method first converts all "\r\n" and "\r" to "\n" to guard * against XML "features" that transform "\r\n" to "\r\r\n" on output. * @param document * @param data The data for the <code>CDATASection</code> contents. * @return The new <code>CDATASection</code> object. * @exception org.w3c.dom.DOMException * NOT_SUPPORTED_ERR: Raised if this document is an HTML document. */ public static CDATASection createCDATASection(Document document, String data) { String convertedData = convertNewLines(data); return document.createCDATASection(convertedData); } /** * Get the text from adjacent CharacterData nodes. This will take the text from the passed in node, * and merge it with the text from the next siblings until it reaches a node which is not CharacterData. * * Note that this is necessary during loading, as the result of saving a CDATA node may in fact * be more than one CDATA node in the output. This will happen because any time the substring "]]>" * (the substring to end a CDATA node) appears while outputting the CDATA text, the node transformer * will end the current node at "]]" and start a new one CDATA node to avoid screwing up the output. * * Note: CDATA extends CharacterData (so CDATA != CharacterData) * * @param firstNode the first of [0..n] CharacterData nodes whose text to merge and return. * @param resultBuffer the buffer into which to add the result of merging the CharacterData text. * Warning: anything in this buffer will be overwritten! * @return the first sibling node which is not a CharacterData node, or null is there isn't any. */ public static Node getAdjacentCharacterData(Node firstNode, StringBuilder resultBuffer) { // Clear the result buffer resultBuffer.setLength(0); // merge adjacent CDATA nodes Node codeNode = firstNode; while (codeNode != null && codeNode instanceof CharacterData) { resultBuffer.append(((CharacterData)codeNode).getData()); codeNode = codeNode.getNextSibling(); } return codeNode; } /** * Method setIntegerAttribute * * @param element * @param attributeName * @param value */ public static void setIntegerAttribute (Element element, String attributeName, int value) { element.setAttribute(attributeName, Integer.toString(value)); } /** * Get the Integer representation of an attribute value. * Throw an appropriate throwable if the string doesn't have a good integer representation. * @param element Element the element whose attribute to convert to an Integer value * @param attributeName String the name of the attribute whose value represents an Integer. * @return Integer the Integer representation * @throws BadXMLDocumentException */ public static Integer getIntegerAttribute(Element element, String attributeName) throws BadXMLDocumentException { String intString = element.getAttribute(attributeName); try { Integer result = new Integer(intString); return result; } catch (NumberFormatException nfe) { handleBadDocument(element, messages.getString("IntExpectedForAttr", attributeName, intString)); //$NON-NLS-1$ } return null; } /** * Get the Integer representation of an attribute value. * Throw an appropriate throwable if the string doesn't have a good integer representation. * @param element Element the element whose attribute to convert to an Integer value * @param attributeName String the name of the attribute whose value represents an Integer. * @param defaultValue the default value to be returned if the attribute is missing * @return the integer representation of the attribute value, or the default value * @throws BadXMLDocumentException */ public static int getIntegerAttributeWithDefault(Element element, String attributeName, int defaultValue) throws BadXMLDocumentException { String intString = element.getAttribute(attributeName); if (intString == null || intString.length() == 0) { return defaultValue; } try { return Integer.parseInt(intString); } catch (NumberFormatException nfe) { handleBadDocument(element, messages.getString("IntExpectedForAttr", attributeName, intString)); //$NON-NLS-1$ return defaultValue; } } /** * Adds an attribute with the list of integer values encoded as a string. * @param element the element to which the attribute will be added * @param attributeName the name of the attribute * @param intValues the list of Integer values to set */ public static void setIntegerListAttribute(Element element, String attributeName, List<Integer> intValues) { StringBuilder sb = new StringBuilder(); for (Integer intValue : intValues) { if (sb.length() == 0) { sb.append(" "); //$NON-NLS-1$ } sb.append(intValue); } element.setAttribute(attributeName, sb.toString()); } /** * Extracts a list of integer values from the specified attribute. * @param element the element to fetch the integer list from * @param attributeName the name of the attribute containing the integer list * @return a list of integer values from the specified attribute */ public static List<Integer> getIntegerListAttribute(Element element, String attributeName) { String intListString = element.getAttribute(attributeName); if (intListString == null || intListString.length() == 0) { return Collections.emptyList(); } String [] intStrings = intListString.split (" "); //$NON-NLS-1$ List<Integer> intValues = new ArrayList<Integer>(intStrings.length); for (String intString : intStrings) { Integer intValue = Integer.valueOf(intString); intValues.add(intValue); } return intValues; } /** * Method setDoubleAttribute * * @param element * @param attributeName * @param value */ public static void setDoubleAttribute (Element element, String attributeName, double value) { element.setAttribute (attributeName, Double.toString(value)); } /** * Method getDoubleAttribute * * @param element * @param attributeName * @return The Double representation of an attribute value * @throws BadXMLDocumentException */ public static Double getDoubleAttribute(Element element, String attributeName) throws BadXMLDocumentException { String doubleString = (element).getAttribute(attributeName); try { Double result = new Double(doubleString); return result; } catch (NumberFormatException nfe) { handleBadDocument(element, messages.getString("DoubleExpectedForAttr", attributeName, doubleString)); //$NON-NLS-1$ } return null; } /** * Get the double representation of an attribute value. * Throw an appropriate throwable if the string doesn't have a good double representation. * @param element Element the element whose attribute to convert to an double value * @param attributeName String the name of the attribute whose value represents an double. * @param defaultValue the default value to be returned if the attribute is missing * @return the double representation of the value, or the default value * @throws BadXMLDocumentException */ public static double getDoubleAttributeWithDefault(Element element, String attributeName, double defaultValue) throws BadXMLDocumentException { String doubleString = element.getAttribute(attributeName); if (doubleString == null || doubleString.length() == 0) { return defaultValue; } try { return Double.parseDouble(doubleString); } catch (NumberFormatException nfe) { handleBadDocument(element, messages.getString("DoubleExpectedForAttr", attributeName, doubleString)); //$NON-NLS-1$ return defaultValue; } } /** * Add a Boolean attribute using the boolean representation of 'true' and 'false' as defined by this class. * @param element Element the element whose attribute to convert to a boolean value * @param attributeName String the name of the attribute whose value represents a boolean. * @param value Boolean the boolean value */ public static void setBooleanAttribute(Element element, String attributeName, boolean value) { if (value) element.setAttribute(attributeName, XMLPersistenceConstants.TRUE_STRING); else element.setAttribute(attributeName, XMLPersistenceConstants.FALSE_STRING); } /** * Get a Boolean representation of an attribute value. * Throw an appropriate throwable if the string doesn't have a good boolean representation. * @param element Element the element whose attribute to convert to a boolean value * @param attributeName String the name of the attribute whose value represents a boolean. * @return boolean the boolean representation. * @throws BadXMLDocumentException */ public static boolean getBooleanAttribute(Element element, String attributeName) throws BadXMLDocumentException { final String booleanString = element.getAttribute(attributeName); final String errorMessage; //if we can't parse the String if (booleanString != null) { if (booleanString.equalsIgnoreCase(XMLPersistenceConstants.TRUE_STRING)) { return true; } else if (booleanString.equalsIgnoreCase(XMLPersistenceConstants.FALSE_STRING)) { return false; } errorMessage = messages.getString("CouldNotParseBool", booleanString); //$NON-NLS-1$ } else { errorMessage = messages.getString("NoBoolVal"); //$NON-NLS-1$ } handleBadDocument(element, errorMessage); return false; } /** * Get a Boolean representation of an attribute value. If the value doesn't exist (or can't be * parsed) then return the default value. * @param element Element the element whose attribute to convert to a boolean value * @param attributeName String the name of the attribute whose value represents a boolean. * @param defaultValue Boolean the default value if the attribute isn't found * @return boolean the boolean representation. */ public static boolean getBooleanAttributeWithDefault(Element element, String attributeName, boolean defaultValue) { // If the attribute doesn't exist then return the default value. if (!element.hasAttribute(attributeName)) return defaultValue; String booleanString = element.getAttribute(attributeName); if (booleanString.equalsIgnoreCase(XMLPersistenceConstants.TRUE_STRING)) { return true; } else if (booleanString.equalsIgnoreCase(XMLPersistenceConstants.FALSE_STRING)) { return false; } // Couldn't parse the string, return the default value. return defaultValue; } /** * Adds a new element containing a CDATA section to the parent element * @param parent the parent element to add the new element to * @param tag the tag name of the new element * @param text the text of the CDATA section (if text is null or empty no CDATA section will be added). */ public static void addTextElement(Element parent, String tag, String text) { addTextElementNS(parent, null, tag, text); } /** * Adds a new element containing a CDATA section to the parent element * @param parent the parent element to add the new element to * @param namespaceURI the namespace of the added element's tag name, or null if there isn't any.. * @param tag the tag name of the new element * @param text the text of the CDATA section (if text is null or empty no CDATA section will be added). */ public static void addTextElementNS(Element parent, String namespaceURI, String tag, String text) { // Add an element with the given tag name. Document document = parent.getOwnerDocument(); Element textElement = namespaceURI != null ? document.createElementNS(namespaceURI, tag) : document.createElement(tag); parent.appendChild(textElement); if (text != null && text.length() > 0) { CDATASection cdata = createCDATASection(document, text); textElement.appendChild(cdata); } } /** * Adds a new element containing a CDATA section with the specified date to the parent element. * @param parent the parent element to add the new element to * @param tag the tag name of the new element * @param date the date to use for the CDATA section (if date is null no CDATA section will be added). */ public static void addDateElement(Element parent, String tag, Date date) { addDateElementNS(parent, null, tag, date); } /** * Adds a new element containing a CDATA section with the specified date to the parent element. * @param parent the parent element to add the new element to * @param namespaceURI the namespace of the added element's tag name, or null if there isn't any.. * @param tag the tag name of the new element * @param date the date to use for the CDATA section (if date is null no CDATA section will be added). */ public static void addDateElementNS(Element parent, String namespaceURI, String tag, Date date) { String dateText = null; if (date != null) { dateText = XMLPersistenceConstants.formatDateAsISO8601(date); } addTextElementNS(parent, namespaceURI, tag, dateText); } /** * Adds a new element containing a CDATA section with the specified boolean value to the parent element. * @param parent the parent element to add the new element to * @param tag the tag name of the new element * @param value the boolean value to store in the CDATA section of the new element */ public static void addBooleanElement(Element parent, String tag, boolean value) { addBooleanElementNS(parent, null, tag, value); } /** * Adds a new element containing a CDATA section with the specified boolean value to the parent element. * @param parent the parent element to add the new element to * @param namespaceURI the namespace of the added element's tag name, or null if there isn't any.. * @param tag the tag name of the new element * @param value the boolean value to store in the CDATA section of the new element */ public static void addBooleanElementNS(Element parent, String namespaceURI, String tag, boolean value) { String valueText = (value) ? XMLPersistenceConstants.TRUE_STRING : XMLPersistenceConstants.FALSE_STRING; addTextElementNS(parent, namespaceURI, tag, valueText); } /** * Adds a new element containing a CDATA section with the specified value to the parent element. * @param parent the parent element to add the new element to * @param tag the tag name of the new element * @param value the value to store in the CDATA section of the new element */ public static void addIntegerElement(Element parent, String tag, int value) { addIntegerElementNS(parent, null, tag, value); } /** * Adds a new element containing a CDATA section with the specified value to the parent element. * @param parent the parent element to add the new element to * @param namespaceURI the namespace of the added element's tag name, or null if there isn't any.. * @param tag the tag name of the new element * @param value the value to store in the CDATA section of the new element */ public static void addIntegerElementNS(Element parent, String namespaceURI, String tag, int value) { addTextElementNS(parent, namespaceURI, tag, Integer.toString(value)); } /** * Adds a new element containing a CDATA section with the specified value to the parent element. * @param parent the parent element to add the new element to * @param namespaceURI the namespace of the added element's tag name, or null if there isn't any.. * @param tag the tag name of the new element * @param value the value to store in the CDATA section of the new element */ public static void addLongElementNS(Element parent, String namespaceURI, String tag, long value) { addTextElementNS(parent, namespaceURI, tag, Long.toString(value)); } /** * @param element the element whose String value to get * @return the String value of the first child of the specific element * @throws BadXMLDocumentException */ public static String getElementStringValue(Node element) throws BadXMLDocumentException { checkIsElement(element); if (element.hasChildNodes()) { StringBuilder elementText = new StringBuilder(); Node elementValueNode = element.getFirstChild(); getAdjacentCharacterData(elementValueNode, elementText); return elementText.toString(); } return null; } /** * @param element the element whose boolean value to get * @return the boolean value of the first child of the specified element. The return value is * true iff the child's text value equals TRUE_STRING (ignoring case). Otherwise this returns false. * * @throws BadXMLDocumentException */ public static boolean getElementBooleanValue(Node element) throws BadXMLDocumentException { String stringValue = getElementStringValue(element); if (stringValue == null) { return false; } if (stringValue.equalsIgnoreCase(XMLPersistenceConstants.TRUE_STRING)) { return true; } return false; } /** * @param element the element whose Date value to get * @return the Date value of the first child of the specified element. Null if there is no valid * Date string stored by the first child. * */ public static Date getElementDateValue(Node element) throws BadXMLDocumentException { String stringValue = getElementStringValue(element); if (stringValue == null) { return null; } try { return XMLPersistenceConstants.unformatDateFromISO8601(stringValue); } catch (ParseException ex) { // This means the string value does not represent a valid date. } return null; } /** * Get the Integer representation of an attribute value. * Throw an appropriate throwable if the string doesn't have a good integer representation. * @param element the element whose Integer value to get. * @return the Integer value of the first child of the specified element. Null if there is no valid * Integer stored by the first child. * @throws BadXMLDocumentException */ public static Integer getElementIntegerValue(Element element) throws BadXMLDocumentException { String intString = getElementStringValue(element); if (intString == null) { return null; } try { return new Integer(intString); } catch (NumberFormatException nfe) { // The string value does not represent a valid integer. } return null; } /** * Get the Long representation of an attribute value. * Throw an appropriate throwable if the string doesn't have a good long representation. * * @param element the element whose value to get. * @return the value of the first child of the specified element. * Null if there is no valid Long value stored by the first child. * @throws BadXMLDocumentException */ public static Long getElementLongValue(Element element) throws BadXMLDocumentException { String longString = getElementStringValue(element); if (longString == null) { return null; } try { return new Long(longString); } catch (NumberFormatException nfe) { // The string value does not represent a valid long value. } return null; } /** * Handle an error in disassembling an XML document. * @param errorNode Node the node being processed when the error occurred. * @param errorMessage String the error message associated with the error. * @throws BadXMLDocumentException */ public static void handleBadDocument(Node errorNode, String errorMessage) throws BadXMLDocumentException { throw new BadXMLDocumentException(errorNode, errorMessage); } /** * Returns the element name of the given element. * @param element * @return String */ public static String getElementName(Element element) { // When loading from disk the local name will be setup correctly, but if the local name // is fetched directly after calling Document.createElement() then the value will be null. // See the JavaDoc for more info. To workaround this, use the complete node name. String elemName = element.getLocalName (); if (elemName == null) { elemName = element.getNodeName(); } return elemName; } /** * Returns the first child element, or null if none exists. * @param element * @return Element */ public static Element getFirstChildElement(Element element) { for (Node node = element.getFirstChild (); node != null; node = node.getNextSibling ()) { if (node instanceof Element) { return (Element)node; } } return null; } /** * Returns the first child element with the specified name, or null if none exists. * @param element * @param elementName * @return Element */ public static Element getChildElement (Element element, String elementName) { for (Node node = element.getFirstChild (); node != null; node = node.getNextSibling ()) { if (node instanceof Element) { Element childElem = (Element) node; String elemName = getElementName(childElem); if (elementName.equals (elemName)) return childElem; } } return null; } /** * @param element * @return all child elements of the specified node. */ public static List<Element> getChildElements (Node element) { List<Element> childElems = new ArrayList<Element>(); for (Node node = element.getFirstChild (); node != null; node = node.getNextSibling ()) { if (node instanceof Element) { childElems.add((Element)node); } } return childElems; } /** * Returns all child elements of the specified node with the specified name. * @param element * @param elementName * @return the list of child elements */ public static List<Element> getChildElements (Node element, String elementName) { List<Element> childElems = new ArrayList<Element>(); for (Node node = element.getFirstChild (); node != null; node = node.getNextSibling ()) { if (node instanceof Element) { Element childElem = (Element)node; String elemName = getElementName(childElem); if (elementName.equals (elemName)) childElems.add (childElem); } } return childElems; } /** * Returns the child text of this element, or null if there is none. * @param element * @return String */ public static String getChildText (Element element) { return getChildText(element, null); } /** * Returns the child text of this element, or the default value if there is none. * @param element * @param defaultValue * @return Either the child text of the element or the default value if there is not child text. */ public static String getChildText (Element element, String defaultValue) { Node childNode = element.getFirstChild (); if (childNode == null || !(childNode instanceof Text)) return defaultValue; return childNode.getNodeValue (); } /** * A convenience method for return a string from an element. First, this * method checks to see if the the specified name exists as an attribute * in the element. If it does, simply returns the attribute value. * Then this method checks to see if the specified element has a child * element that matches the given name. If it does, attempts to read * the text from the child node. Otherwise, returns <code>null</code>. * @param element * @param name * @return String */ public static String getAttributeOrChildText(Element element, String name) { if (element.hasAttribute(name)) { return element.getAttribute(name); } Element childElem = getChildElement(element, name); return childElem == null ? null : getChildText(childElem); } /** * Attach namespace and optionally schema info to the given document. * @param document the document to which to attach the info. * @param documentNSInfo the namespace info for the document. * @param schemaLocation the schema location. If null, no schema info will be attached. * @param schemaLocationNS the namespace URI for the schema location. * If null, and the schema location is non-null, the schema location will not have a namespace. */ public static void attachNamespaceAndSchema(Document document, NamespaceInfo documentNSInfo, String schemaLocation, String schemaLocationNS) { Element rootElement = document.getDocumentElement(); attachNamespaceAndSchema(rootElement, documentNSInfo, schemaLocation, schemaLocationNS); } /** * Attach namespace and optionally schema info to the given element. * @param rootElement the element to which to attach the info. * @param documentNSInfo the namespace info for the document. * @param schemaLocation the schema location. If null, no schema info will be attached. * @param schemaLocationNS the namespace URI for the schema location. * If null, and the schema location is non-null, the schema location will not have a namespace. */ public static void attachNamespaceAndSchema(Element rootElement, NamespaceInfo documentNSInfo, String schemaLocation, String schemaLocationNS) { Document document = rootElement.getOwnerDocument(); String documentNS = documentNSInfo.getNamespaceURI(); String documentNSPrefix = documentNSInfo.getNamespacePrefix(); String qualifiedName = XMLPersistenceConstants.XML_NS_PREFIX + ":" + documentNSPrefix; //$NON-NLS-1$ Attr nsAttr = document.createAttributeNS(XMLPersistenceConstants.XML_NS, qualifiedName); nsAttr.setPrefix(XMLPersistenceConstants.XML_NS_PREFIX); nsAttr.setValue(documentNS); rootElement.setAttributeNode(nsAttr); // Attach schema information if available if (schemaLocation != null) { // Specify the schema instance namespace: // "xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance" qualifiedName = XMLPersistenceConstants.XML_NS_PREFIX + ":" + XMLPersistenceConstants.XSI_NS_PREFIX; //$NON-NLS-1$ Attr xsiAttr = document.createAttributeNS(XMLPersistenceConstants.XML_NS, qualifiedName); xsiAttr.setPrefix(XMLPersistenceConstants.XML_NS_PREFIX); xsiAttr.setValue(XMLPersistenceConstants.XSI_NS); rootElement.setAttributeNode(xsiAttr); Attr schemaLocationAttr; if (schemaLocationNS == null) { // Specify the schema location: "xsi:noNameSpaceSchemaLocation=..." schemaLocationAttr = document.createAttributeNS(XMLPersistenceConstants.XSI_NS, "noNamespaceSchemaLocation"); //$NON-NLS-1$ schemaLocationAttr.setValue(schemaLocation); } else { // Specify the namespace attribute: // "xmlns=http://www.businessobjects.com/cal/metadata" rootElement.setAttribute(XMLPersistenceConstants.XML_NS_PREFIX, schemaLocationNS); // Specify the schema location: "xsi:schemaLocation=..." schemaLocationAttr = document.createAttributeNS(XMLPersistenceConstants.XSI_NS, "schemaLocation"); //$NON-NLS-1$ schemaLocationAttr.setValue(schemaLocationNS + " " + schemaLocation); //$NON-NLS-1$ } schemaLocationAttr.setPrefix(XMLPersistenceConstants.XSI_NS_PREFIX); rootElement.setAttributeNode(schemaLocationAttr); } } }