Java tutorial
//============================================================================= //=== Copyright (C) 2001-2005 Food and Agriculture Organization of the //=== United Nations (FAO-UN), United Nations World Food Programme (WFP) //=== and United Nations Environment Programme (UNEP) //=== //=== This library is free software; you can redistribute it and/or //=== modify it under the terms of the GNU Lesser General Public //=== License as published by the Free Software Foundation; either //=== version 2.1 of the License, or (at your option) any later version. //=== //=== This library is distributed in the hope that it will be useful, //=== but WITHOUT ANY WARRANTY; without even the implied warranty of //=== MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //=== Lesser General Public License for more details. //=== //=== You should have received a copy of the GNU Lesser General Public //=== License along with this library; if not, write to the Free Software //=== Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA //=== //=== Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, //=== Rome - Italy. email: GeoNetwork@fao.org //============================================================================== package org.fao.geonet.utils; import net.sf.json.JSON; import net.sf.json.xml.XMLSerializer; import net.sf.saxon.Configuration; import net.sf.saxon.FeatureKeys; import org.apache.commons.io.IOUtils; import org.apache.fop.apps.Fop; import org.apache.fop.apps.FopFactory; import org.apache.fop.apps.MimeConstants; import org.apache.xml.resolver.tools.CatalogResolver; import org.eclipse.core.runtime.URIUtil; import org.fao.geonet.exceptions.XSDValidationErrorEx; import org.jdom.Attribute; import org.jdom.Content; import org.jdom.DocType; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.Namespace; import org.jdom.Text; import org.jdom.input.SAXBuilder; import org.jdom.output.Format; import org.jdom.output.SAXOutputter; import org.jdom.output.XMLOutputter; import org.jdom.transform.JDOMResult; import org.jdom.transform.JDOMSource; import org.jdom.xpath.XPath; import org.mozilla.universalchardet.UniversalDetector; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.io.StringReader; import java.lang.reflect.Method; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.UUID; import javax.xml.XMLConstants; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.URIResolver; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.ValidatorHandler; import static org.fao.geonet.Constants.ENCODING; //============================================================================= /** * General class of useful static methods. */ public final class Xml { public static final Namespace xsiNS = Namespace.getNamespace("xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); //-------------------------------------------------------------------------- /** * * @param validate * @return */ private static SAXBuilder getSAXBuilder(boolean validate) { SAXBuilder builder = getSAXBuilderWithoutXMLResolver(validate); Resolver resolver = ResolverWrapper.getInstance(); builder.setEntityResolver(resolver.getXmlResolver()); return builder; } private static SAXBuilder getSAXBuilderWithoutXMLResolver(boolean validate) { SAXBuilder builder = new SAXBuilder(validate); builder.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); return builder; } //-------------------------------------------------------------------------- /** * */ public static void resetResolver() { Resolver resolver = ResolverWrapper.getInstance(); resolver.reset(); } //-------------------------------------------------------------------------- //--- //--- Load API //--- //-------------------------------------------------------------------------- /** * * @param file * @return * @throws IOException * @throws JDOMException */ public static Element loadFile(String file) throws IOException, JDOMException { return loadFile(new File(file)); } //-------------------------------------------------------------------------- /** * Loads an xml file from a URL and returns its root node. * * @param url * @return * @throws IOException * @throws JDOMException */ public static Element loadFile(URL url) throws IOException, JDOMException { SAXBuilder builder = getSAXBuilderWithoutXMLResolver(false);//new SAXBuilder(); Document jdoc = builder.build(url); return (Element) jdoc.getRootElement().detach(); } //-------------------------------------------------------------------------- /** * Loads an xml file from a URL after posting content to the URL. * * @param url * @param xmlQuery * @return * @throws IOException * @throws JDOMException */ public static Element loadFile(URL url, Element xmlQuery) throws IOException, JDOMException { Element result = null; try { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/xml"); connection.setRequestProperty("Content-Length", "" + Integer.toString(getString(xmlQuery).getBytes(ENCODING).length)); connection.setRequestProperty("Content-Language", "en-US"); connection.setDoOutput(true); PrintStream out = new PrintStream(connection.getOutputStream(), true, ENCODING); out.print(getString(xmlQuery)); out.close(); SAXBuilder builder = getSAXBuilderWithoutXMLResolver(false);//new SAXBuilder(); Document jdoc = builder.build(connection.getInputStream()); result = (Element) jdoc.getRootElement().detach(); } catch (Exception e) { Log.error(Log.ENGINE, "Error loading URL " + url.getPath() + " .Threw exception " + e); e.printStackTrace(); } return result; } //-------------------------------------------------------------------------- /** * Loads an xml file and returns its root node. * * @param file * @return * @throws IOException * @throws JDOMException */ public static Element loadFile(File file) throws IOException, JDOMException { SAXBuilder builder = getSAXBuilderWithoutXMLResolver(false); //new SAXBuilder(); String convert = System.getProperty("jeeves.filecharsetdetectandconvert"); // detect charset and convert if required if (convert != null && convert.equals("enabled")) { byte[] content = convertFileToUTF8ByteArray(file); return loadStream(new ByteArrayInputStream(content)); // no charset detection and conversion allowed } else { Document jdoc = builder.build(file); return (Element) jdoc.getRootElement().detach(); } } //-------------------------------------------------------------------------- /** * Reads file into byte array, detects charset and converts from this * charset to UTF8 * * @param file file to decode and convert to UTF8 * @return * @throws IOException * @throws CharacterCodingException */ public synchronized static byte[] convertFileToUTF8ByteArray(File file) throws IOException, CharacterCodingException { FileInputStream in = null; DataInputStream inStream = null; try { in = new FileInputStream(file); inStream = new DataInputStream(in); byte[] buf = new byte[(int) file.length()]; int nrRead = inStream.read(buf); UniversalDetector detector = new UniversalDetector(null); detector.handleData(buf, 0, nrRead); detector.dataEnd(); String encoding = detector.getDetectedCharset(); detector.reset(); if (encoding != null) { if (!encoding.equals(ENCODING)) { Log.error(Log.JEEVES, "Detected character set " + encoding + ", converting to UTF-8"); return convertByteArrayToUTF8ByteArray(buf, encoding); } } return buf; } finally { if (in != null) { IOUtils.closeQuietly(in); } if (inStream != null) { IOUtils.closeQuietly(inStream); } } } //-------------------------------------------------------------------------- /** * Decode byte array as specified charset, then convert to UTF-8 * by encoding as UTF8 * * @param buf byte array to decode and convert to UTF8 * @param charsetName charset to decode byte array into * @return * @throws CharacterCodingException */ public synchronized static byte[] convertByteArrayToUTF8ByteArray(byte[] buf, String charsetName) throws CharacterCodingException { Charset cset; cset = Charset.forName(charsetName); // detected character set name CharsetDecoder csetDecoder = cset.newDecoder(); Charset utf8 = Charset.forName(ENCODING); CharsetEncoder utf8Encoder = utf8.newEncoder(); ByteBuffer inputBuffer = ByteBuffer.wrap(buf); // decode as detected character set CharBuffer data = csetDecoder.decode(inputBuffer); // encode as UTF-8 ByteBuffer outputBuffer = utf8Encoder.encode(data); // remove any nulls from the end of the encoded data why? - this is a // bug in the encoder???? could also be that the file has characters // from more than one charset? byte[] out = outputBuffer.array(); int length = out.length; while (out[length - 1] == 0) length--; byte[] result = new byte[length]; System.arraycopy(out, 0, result, 0, length); // now return the converted bytes return result; } //-------------------------------------------------------------------------- /** * Loads xml from a string and returns its root node * (validates the xml if required). * * @param data * @param validate * @return * @throws IOException * @throws JDOMException */ public static Element loadString(String data, boolean validate) throws IOException, JDOMException { //SAXBuilder builder = new SAXBuilder(validate); SAXBuilder builder = getSAXBuilderWithoutXMLResolver(validate); // oasis catalogs are used Document jdoc = builder.build(new StringReader(data)); return (Element) jdoc.getRootElement().detach(); } //-------------------------------------------------------------------------- /** * Loads xml from an input stream and returns its root node. * * @param input * @return * @throws IOException * @throws JDOMException */ public static Element loadStream(InputStream input) throws IOException, JDOMException { SAXBuilder builder = getSAXBuilderWithoutXMLResolver(false); //new SAXBuilder(); builder.setFeature("http://apache.org/xml/features/validation/schema", false); builder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); Document jdoc = builder.build(input); return (Element) jdoc.getRootElement().detach(); } //-------------------------------------------------------------------------- //--- //--- Transform API //--- //-------------------------------------------------------------------------- /** * Transforms an xml tree into another using a stylesheet on disk. * * @param xml * @param styleSheetPath * @return * @throws Exception */ public static Element transform(Element xml, String styleSheetPath) throws Exception { JDOMResult resXml = new JDOMResult(); transform(xml, styleSheetPath, resXml, null); return (Element) resXml.getDocument().getRootElement().detach(); } //-------------------------------------------------------------------------- /** * Transforms an xml tree into another using a stylesheet on disk and pass parameters. * * @param xml * @param styleSheetPath * @param params * @return * @throws Exception */ public static Element transform(Element xml, String styleSheetPath, Map<String, String> params) throws Exception { JDOMResult resXml = new JDOMResult(); transform(xml, styleSheetPath, resXml, params); return (Element) resXml.getDocument().getRootElement().detach(); } //-------------------------------------------------------------------------- /** * Transforms an xml tree putting the result to a stream (uses a stylesheet on disk). * * @param xml * @param styleSheetPath * @param out * @throws Exception */ public static void transform(Element xml, String styleSheetPath, OutputStream out) throws Exception { StreamResult resStream = new StreamResult(out); transform(xml, styleSheetPath, resStream, null); } //-------------------------------------------------------------------------- /** * Transforms an xml tree putting the result to a stream - no parameters. * * @param xml * @param styleSheetPath * @param result * @throws Exception */ public static void transform(Element xml, String styleSheetPath, Result result) throws Exception { transform(xml, styleSheetPath, result, null); } //-------------------------------------------------------------------------- private static class JeevesURIResolver implements URIResolver { /** * * @param href * @param base * @return * @throws TransformerException */ public Source resolve(String href, String base) throws TransformerException { Resolver resolver = ResolverWrapper.getInstance(); CatalogResolver catResolver = resolver.getCatalogResolver(); if (Log.isDebugEnabled(Log.XML_RESOLVER)) { Log.debug(Log.XML_RESOLVER, "Trying to resolve " + href + ":" + base); } Source s = catResolver.resolve(href, base); boolean isFile = false; try { final File file = new File(s.getSystemId()); isFile = file.isFile(); } catch (Exception e) { isFile = false; } // If resolver has a blank XSL file use it to replace // resolved file that doesn't exist... String blankXSLFile = resolver.getBlankXSLFile(); if (blankXSLFile != null && s.getSystemId().endsWith(".xsl") && !isFile) { try { if (Log.isDebugEnabled(Log.XML_RESOLVER)) { Log.debug(Log.XML_RESOLVER, " Check if exist " + s.getSystemId()); } File f = URIUtil.toFile(new URI(s.getSystemId())); if (Log.isDebugEnabled(Log.XML_RESOLVER)) Log.debug(Log.XML_RESOLVER, "Check on " + f.getPath() + " exists returned: " + f.exists()); // If the resolved resource does not exist, set it to blank file path to not trigger FileNotFound Exception if (f == null || !(f.exists())) { if (Log.isDebugEnabled(Log.XML_RESOLVER)) { Log.debug(Log.XML_RESOLVER, " Resolved resource " + s.getSystemId() + " does not exist. blankXSLFile returned instead."); } s.setSystemId(blankXSLFile); } } catch (URISyntaxException e) { Log.warning(Log.XML_RESOLVER, "URI syntax problem: " + e.getMessage()); e.printStackTrace(); } } if (Log.isDebugEnabled(Log.XML_RESOLVER) && s != null) { Log.debug(Log.XML_RESOLVER, "Resolved as " + s.getSystemId()); } return s; } } //-------------------------------------------------------------------------- /** * Transforms an xml tree putting the result to a stream with optional parameters. * * @param xml * @param styleSheetPath * @param result * @param params * @throws Exception */ public static void transform(Element xml, String styleSheetPath, Result result, Map<String, String> params) throws Exception { File styleSheet = new File(styleSheetPath); Source srcXml = new JDOMSource(new Document((Element) xml.detach())); Source srcSheet = new StreamSource(styleSheet); // Dear old saxon likes to yell loudly about each and every XSLT 1.0 // stylesheet so switch it off but trap any exceptions because this // code is run on transformers other than saxon TransformerFactory transFact = TransformerFactoryFactory.getTransformerFactory(); transFact.setURIResolver(new JeevesURIResolver()); try { transFact.setAttribute(FeatureKeys.VERSION_WARNING, false); transFact.setAttribute(FeatureKeys.LINE_NUMBERING, true); transFact.setAttribute(FeatureKeys.PRE_EVALUATE_DOC_FUNCTION, false); transFact.setAttribute(FeatureKeys.RECOVERY_POLICY, Configuration.RECOVER_SILENTLY); // Add the following to get timing info on xslt transformations //transFact.setAttribute(FeatureKeys.TIMING,true); } catch (IllegalArgumentException e) { Log.warning(Log.ENGINE, "WARNING: transformerfactory doesnt like saxon attributes!"); //e.printStackTrace(); } finally { Transformer t = transFact.newTransformer(srcSheet); if (params != null) { for (Map.Entry<String, String> param : params.entrySet()) { t.setParameter(param.getKey(), param.getValue()); } } t.transform(srcXml, result); } } //-------------------------------------------------------------------------- /** * Clears the cache used in the stylesheet transformer factory. This will only work for the GeoNetwork Caching * stylesheet transformer factory. This is a no-op for other transformer factories. */ public static void clearTransformerFactoryStylesheetCache() { TransformerFactory transFact = TransformerFactory.newInstance(); try { Class<?> class1 = transFact.getClass(); Method cacheMethod = class1.getDeclaredMethod("clearCache"); cacheMethod.invoke(transFact, new Object[0]); } catch (Exception e) { Log.error(Log.ENGINE, "Failed to find/invoke clearCache method - continuing (" + e.getMessage() + ")"); } } // -------------------------------------------------------------------------- /** * Transform an xml tree to PDF using XSL-FOP * putting the result to a stream (uses a stylesheet * on disk) */ public static String transformFOP(String uploadDir, Element xml, String styleSheetPath) throws Exception { String file = uploadDir + UUID.randomUUID().toString() + ".pdf"; // Step 1: Construct a FopFactory // (reuse if you plan to render multiple documents!) FopFactory fopFactory = FopFactory.newInstance(); // Step 2: Set up output stream. // Note: Using BufferedOutputStream for performance reasons (helpful // with FileOutputStreams). OutputStream out = new BufferedOutputStream(new FileOutputStream(new File(file))); try { // Step 3: Construct fop with desired output format Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, out); // Step 4: Setup JAXP using identity transformer TransformerFactory factory = TransformerFactoryFactory.getTransformerFactory(); factory.setURIResolver(new JeevesURIResolver()); Source xslt = new StreamSource(new File(styleSheetPath)); try { factory.setAttribute(FeatureKeys.VERSION_WARNING, false); factory.setAttribute(FeatureKeys.LINE_NUMBERING, true); factory.setAttribute(FeatureKeys.RECOVERY_POLICY, Configuration.RECOVER_SILENTLY); } catch (IllegalArgumentException e) { Log.warning(Log.ENGINE, "WARNING: transformerfactory doesnt like saxon attributes!"); //e.printStackTrace(); } finally { Transformer transformer = factory.newTransformer(xslt); // Step 5: Setup input and output for XSLT transformation // Setup input stream Source src = new JDOMSource(new Document((Element) xml.detach())); // Resulting SAX events (the generated FO) must be piped through to // FOP Result res = new SAXResult(fop.getDefaultHandler()); // Step 6: Start XSLT transformation and FOP processing transformer.transform(src, res); } } finally { // Clean-up out.close(); } return file; } //-------------------------------------------------------------------------- //--- //--- General stuff //--- //-------------------------------------------------------------------------- /** * Writes an xml element to a stream. * * @param doc * @param out * @throws IOException */ public static void writeResponse(Document doc, OutputStream out) throws IOException { XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat()); outputter.output(doc, out); } //--------------------------------------------------------------------------- /** * Converts an xml element to a string. * * @param data * @return */ public static String getString(Element data) { XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat()); return outputter.outputString(data); } //--------------------------------------------------------------------------- /** * Converts an xml element to JSON * * @param xml the XML element * @return the JSON response * * @throws IOException */ public static String getJSON(Element xml) throws IOException { return Xml.getJSON(Xml.getString(xml)); } /** * Converts an xml string to JSON * * @param xml the XML element * @return the JSON response * * @throws IOException */ public static String getJSON(String xml) throws IOException { XMLSerializer xmlSerializer = new XMLSerializer(); // Disable type hints. When enable, a type attribute in the root // element will throw NPE. // http://sourceforge.net/mailarchive/message.php?msg_id=27646519 xmlSerializer.setTypeHintsEnabled(false); xmlSerializer.setTypeHintsCompatibility(false); JSON json = xmlSerializer.read(xml); return json.toString(2); } /** * * @param data * @return */ public static String getString(DocType data) { XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat()); return outputter.outputString(data); } //--------------------------------------------------------------------------- /** * * @param data * @return */ public static String getString(Document data) { XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat()); return outputter.outputString(data); } //--------------------------------------------------------------------------- /** * Creates and prepares an XPath element - simple xpath (like "a/b/c"). * * @param xml * @param xpath * @param theNSs * @return * @throws JDOMException */ private static XPath prepareXPath(Element xml, String xpath, List<Namespace> theNSs) throws JDOMException { XPath xp = XPath.newInstance(xpath); for (Namespace ns : theNSs) { xp.addNamespace(ns); } return xp; } //--------------------------------------------------------------------------- /** * Retrieves a single XML element given a simple xpath (like "a/b/c"). * * @param xml * @param xpath * @param theNSs * @return * @throws JDOMException */ public static Object selectSingle(Element xml, String xpath, List<Namespace> theNSs) throws JDOMException { XPath xp = prepareXPath(xml, xpath, theNSs); return xp.selectSingleNode(xml); } //--------------------------------------------------------------------------- /** * Retrieves a single XML element as a JDOM element given a simple xpath. * * @param xml * @param xpath * @return * @throws JDOMException */ public static Element selectElement(Element xml, String xpath) throws JDOMException { return selectElement(xml, xpath, new ArrayList<Namespace>()); } //--------------------------------------------------------------------------- /** * Retrieves a single XML element as a JDOM element given a simple xpath. * * @param xml * @param xpath * @param theNSs * @return * @throws JDOMException */ public static Element selectElement(Element xml, String xpath, List<Namespace> theNSs) throws JDOMException { Object result = selectSingle(xml, xpath, theNSs); if (result == null) { return null; } else if (result instanceof Element) { Element elem = (Element) result; return (Element) (elem); } else { //-- Found something but not an element return null; } } //--------------------------------------------------------------------------- /** * Evaluates an XPath expression on an element and returns Elements. * * @param xml * @param xpath * @param theNSs * @return * @throws JDOMException */ public static List<?> selectNodes(Element xml, String xpath, List<Namespace> theNSs) throws JDOMException { XPath xp = prepareXPath(xml, xpath, theNSs); return xp.selectNodes(xml); } /** * Evaluates an XPath expression on an document and returns Elements. * * @param xml * @param xpath * @param theNSs * @return * @throws JDOMException */ public static List<?> selectDocumentNodes(Element xml, String xpath, List<Namespace> theNSs) throws JDOMException { XPath xp = XPath.newInstance(xpath); for (Namespace ns : theNSs) { xp.addNamespace(ns); } xml = (Element) xml.clone(); Document document = new Document(xml); return xp.selectNodes(document); } //--------------------------------------------------------------------------- /** * Evaluates an XPath expression on an element and returns Elements. * @param xml * @param xpath * @return * @throws JDOMException */ public static List<?> selectNodes(Element xml, String xpath) throws JDOMException { return selectNodes(xml, xpath, new ArrayList<Namespace>()); } //--------------------------------------------------------------------------- /** * Evaluates an XPath expression on an element and returns string result. * * @param xml * @param xpath * @return * @throws JDOMException */ public static String selectString(Element xml, String xpath) throws JDOMException { return selectString(xml, xpath, new ArrayList<Namespace>()); } //--------------------------------------------------------------------------- /** * Evaluates an XPath expression on an element and returns string result. * * @param xml * @param xpath * @param theNSs * @return * @throws JDOMException */ public static String selectString(Element xml, String xpath, List<Namespace> theNSs) throws JDOMException { XPath xp = prepareXPath(xml, xpath, theNSs); return xp.valueOf(xml); } //--------------------------------------------------------------------------- /** * Evaluates an XPath expression on an element and returns true/false. * * @param xml * @param xpath * @return * @throws JDOMException */ public static boolean selectBoolean(Element xml, String xpath) throws JDOMException { String result = selectString(xml, xpath, new ArrayList<Namespace>()); return result.length() > 0; } //--------------------------------------------------------------------------- /** * Evaluates an XPath expression on an element and returns true/false. * * @param xml * @param xpath * @param theNSs * @return * @throws JDOMException */ public static boolean selectBoolean(Element xml, String xpath, List<Namespace> theNSs) throws JDOMException { return selectString(xml, xpath, theNSs).length() > 0; } //--------------------------------------------------------------------------- /** * Evaluates an XPath expression on an element and returns number result. * * @param xml * @param xpath * @return * @throws JDOMException */ public static Number selectNumber(Element xml, String xpath) throws JDOMException { return selectNumber(xml, xpath, new ArrayList<Namespace>()); } //--------------------------------------------------------------------------- /** * Evaluates an XPath expression on an element and returns number result. * * @param xml * @param xpath * @param theNSs * @return * @throws JDOMException */ public static Number selectNumber(Element xml, String xpath, List<Namespace> theNSs) throws JDOMException { XPath xp = prepareXPath(xml, xpath, theNSs); return xp.numberValueOf(xml); } //--------------------------------------------------------------------------- /** * Error handler that collects up validation errors. * */ public static class ErrorHandler extends DefaultHandler { private int errorCount = 0; private Element xpaths; private Namespace ns = Namespace.NO_NAMESPACE; private SAXOutputter so; public void setSo(SAXOutputter so) { this.so = so; } public boolean errors() { return errorCount > 0; } public Element getXPaths() { return xpaths; } public void addMessage(SAXParseException exception, String typeOfError) { if (errorCount == 0) xpaths = new Element("xsderrors", ns); errorCount++; Element elem = (Element) so.getLocator().getNode(); Element x = new Element("xpath", ns); try { String xpath = org.fao.geonet.utils.XPath.getXPath(elem); //-- remove the first element to ensure XPath fits XML passed with //-- root element if (xpath.startsWith("/")) { int ind = xpath.indexOf('/', 1); if (ind != -1) { xpath = xpath.substring(ind + 1); } else { xpath = "."; // error to be placed on the root element } } x.setText(xpath); } catch (JDOMException e) { e.printStackTrace(); x.setText("nopath"); } String message = exception.getMessage() + " (Element: " + elem.getQualifiedName(); String parentName; if (!elem.isRootElement()) { Element parent = (Element) elem.getParent(); if (parent != null) parentName = parent.getQualifiedName(); else parentName = "Unknown"; } else { parentName = "/"; } message += " with parent element: " + parentName + ")"; Element m = new Element("message", ns).setText(message); Element errorType = new Element("typeOfError", ns).setText(typeOfError); Element errorNumber = new Element("errorNumber", ns).setText(String.valueOf(errorCount)); Element e = new Element("error", ns); e.addContent(errorType); e.addContent(errorNumber); e.addContent(m); e.addContent(x); xpaths.addContent(e); } public void error(SAXParseException parseException) throws SAXException { addMessage(parseException, "ERROR"); } public void fatalError(SAXParseException parseException) throws SAXException { addMessage(parseException, "FATAL ERROR"); } public void warning(SAXParseException parseException) throws SAXException { addMessage(parseException, "WARNING"); } /** * Set namespace to use for report elements * @param ns */ public void setNs(Namespace ns) { this.ns = ns; } public Namespace getNs() { return ns; } } //--------------------------------------------------------------------------- /** * Validates an XML document using the hints in the DocType (DTD validation) or schemaLocation attribute hint. * * @param doc * @throws Exception */ public synchronized static void validate(Document doc) throws Exception { if (doc.getDocType() != null) { // assume DTD validation SAXBuilder builder = getSAXBuilder(true); builder.build(new StringReader(getString(doc))); } Element xml = doc.getRootElement(); if (xml != null) { // try XSD validation String schemaLoc = xml.getAttributeValue("schemaLocation", xsiNS); if (schemaLoc == null || schemaLoc.equals("")) { throw new IllegalArgumentException( "XML document missing/blank schemaLocation hints or DocType dtd - cannot validate"); } validate(xml); } else { throw new IllegalArgumentException("XML document is missing root element - cannot validate"); } } //--------------------------------------------------------------------------- /** * Validates an XML document using the hints in the schemaLocation attribute. * * @param xml * @throws Exception */ public synchronized static void validate(Element xml) throws Exception { Schema schema = factory().newSchema(); ErrorHandler eh = new ErrorHandler(); validateRealGuts(schema, xml, eh); if (eh.errors()) { Element xsdXPaths = eh.getXPaths(); throw new XSDValidationErrorEx("XSD Validation error(s):\n" + getString(xsdXPaths), xsdXPaths); } } //--------------------------------------------------------------------------- /** * Validates an xml document with respect to an xml schema described by .xsd file path. * * @param schemaPath * @param xml * @throws Exception */ public static void validate(String schemaPath, Element xml) throws Exception { Element xsdXPaths = validateInfo(schemaPath, xml); if (xsdXPaths != null && xsdXPaths.getContent().size() > 0) throw new XSDValidationErrorEx("XSD Validation error(s):\n" + getString(xsdXPaths), xsdXPaths); } //--------------------------------------------------------------------------- /** * Validates an xml document with respect to schemaLocation hints. * * @param xml * @return * @throws Exception */ public static Element validateInfo(Element xml) throws Exception { ErrorHandler eh = new ErrorHandler(); Schema schema = factory().newSchema(); validateRealGuts(schema, xml, eh); if (eh.errors()) { return eh.getXPaths(); } else { return null; } } //--------------------------------------------------------------------------- /** * Validates an xml document with respect to schemaLocation hints using supplied error handler. * * @param xml * @param eh * @return * @throws Exception */ public static Element validateInfo(Element xml, ErrorHandler eh) throws Exception { Schema schema = factory().newSchema(); validateRealGuts(schema, xml, eh); if (eh.errors()) { return eh.getXPaths(); } else { return null; } } //--------------------------------------------------------------------------- /** * Validates an xml document with respect to an xml schema described by .xsd file path. * * @param schemaPath * @param xml * @return * @throws Exception */ public static Element validateInfo(String schemaPath, Element xml) throws Exception { ErrorHandler eh = new ErrorHandler(); validateGuts(schemaPath, xml, eh); if (eh.errors()) { return eh.getXPaths(); } else { return null; } } //--------------------------------------------------------------------------- /** * Validates an xml document with respect to an xml schema described by .xsd file path using supplied error handler. * * @param schemaPath * @param xml * @param eh * @return * @throws Exception */ public static Element validateInfo(String schemaPath, Element xml, ErrorHandler eh) throws Exception { validateGuts(schemaPath, xml, eh); if (eh.errors()) { return eh.getXPaths(); } else { return null; } } //--------------------------------------------------------------------------- /** * Called by validation methods that supply an xml schema described by .xsd file path. * * @param schemaPath * @param xml * @param eh * @throws Exception */ private static void validateGuts(String schemaPath, Element xml, ErrorHandler eh) throws Exception { StreamSource schemaFile = new StreamSource(new File(schemaPath)); Schema schema = factory().newSchema(schemaFile); validateRealGuts(schema, xml, eh); } //--------------------------------------------------------------------------- /** * Called by all validation methods to do the real guts of the validation job. * @param schema * @param xml * @param eh * @throws Exception */ private static void validateRealGuts(Schema schema, Element xml, ErrorHandler eh) throws Exception { Resolver resolver = ResolverWrapper.getInstance(); ValidatorHandler vh = schema.newValidatorHandler(); vh.setResourceResolver(resolver.getXmlResolver()); vh.setErrorHandler(eh); SAXOutputter so = new SAXOutputter(vh); eh.setSo(so); so.output(xml); } private static SchemaFactory factory() { return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); } /** * Create and XPath expression for identified Element. * * @param element element within the xml to find an XPath for. * * return xpath or null if there was an error. */ public static String getXPathExpr(Content element) { StringBuilder builder = new StringBuilder(); if (!doCreateXpathExpr(element, builder)) { return null; } else { return builder.toString(); } } /** * Create and XPath expression for identified Element. * * @param attribute element within the xml to find an XPath for. * * return xpath or null if there was an error. */ public static String getXPathExpr(Attribute attribute) { StringBuilder builder = new StringBuilder(); if (!doCreateXpathExpr(attribute, builder)) { return null; } else { return builder.toString(); } } private static boolean doCreateXpathExpr(Object content, StringBuilder builder) { if (builder.length() > 0) { builder.insert(0, "/"); } Element parentElement; if (content instanceof Element) { Element element = (Element) content; final List<Attribute> attributes = element.getAttributes(); doCreateAttributesXpathExpr(builder, attributes); final String textTrim = element.getTextTrim(); if (!textTrim.isEmpty()) { boolean addToCondition = builder.length() > 0 && builder.charAt(0) == '['; if (!addToCondition) { builder.insert(0, "']"); } else { builder.deleteCharAt(0); builder.insert(0, "' and "); } builder.insert(0, textTrim).insert(0, "[normalize-space(text()) = '"); } builder.insert(0, element.getName()); if (element.getNamespacePrefix() != null && !element.getNamespacePrefix().trim().isEmpty()) { builder.insert(0, ':').insert(0, element.getNamespacePrefix()); } parentElement = element.getParentElement(); } else if (content instanceof Text) { final Text text = (Text) content; builder.insert(0, "text()"); parentElement = text.getParentElement(); } else if (content instanceof Attribute) { Attribute attribute = (Attribute) content; builder.insert(0, attribute.getName()); if (attribute.getNamespacePrefix() != null && !attribute.getNamespacePrefix().trim().isEmpty()) { builder.insert(0, ':').insert(0, attribute.getNamespacePrefix()); } builder.insert(0, '@'); parentElement = attribute.getParent(); } else { parentElement = null; } if (parentElement != null && parentElement.getParentElement() != null) { return doCreateXpathExpr(parentElement, builder); } return true; } private static void doCreateAttributesXpathExpr(StringBuilder builder, List<Attribute> attributes) { if (!attributes.isEmpty()) { StringBuilder attBuilder = new StringBuilder("["); for (Attribute attribute : attributes) { if (attBuilder.length() > 1) { attBuilder.append(" and "); } attBuilder.append('@'); if (attribute.getNamespacePrefix() != null && !attribute.getNamespacePrefix().trim().isEmpty()) { attBuilder.append(attribute.getNamespacePrefix()).append(':'); } attBuilder.append(attribute.getName()).append(" = '").append(attribute.getValue()).append('\''); } attBuilder.append("]"); builder.insert(0, attBuilder); } } }