Java tutorial
/** * Copyright (C) 2010 Orbeon, Inc. * * This program 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 program 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. * * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html */ package org.orbeon.oxf.xml; import orbeon.apache.xerces.impl.Constants; import orbeon.apache.xerces.impl.XMLEntityManager; import orbeon.apache.xerces.impl.XMLErrorReporter; import orbeon.apache.xerces.xni.parser.XMLInputSource; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.dom4j.Element; import org.dom4j.QName; import org.orbeon.oxf.common.OXFException; import org.orbeon.oxf.common.ValidationException; import org.orbeon.oxf.pipeline.api.ExternalContext; import org.orbeon.oxf.pipeline.api.PipelineContext; import org.orbeon.oxf.pipeline.api.TransformerXMLReceiver; import org.orbeon.oxf.processor.*; import org.orbeon.oxf.processor.generator.DOMGenerator; import org.orbeon.oxf.processor.generator.URLGenerator; import org.orbeon.oxf.processor.transformer.TransformerURIResolver; import org.orbeon.oxf.resources.URLFactory; import org.orbeon.oxf.util.*; import org.orbeon.oxf.xml.dom4j.Dom4jUtils; import org.orbeon.oxf.xml.dom4j.LocationData; import org.orbeon.oxf.xml.dom4j.LocationDocumentResult; import org.orbeon.oxf.xml.xerces.XercesSAXParserFactoryImpl; import org.orbeon.saxon.om.Name10Checker; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.*; import org.xml.sax.helpers.AttributesImpl; import javax.xml.parsers.*; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.*; import java.net.URL; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.security.MessageDigest; import java.util.*; public class XMLUtils { private static Logger logger = Logger.getLogger(XMLUtils.class); public static final Attributes EMPTY_ATTRIBUTES = new AttributesImpl(); public static final EntityResolver ENTITY_RESOLVER = new EntityResolver(); public static final ErrorHandler ERROR_HANDLER = new ErrorHandler(); private static final ContentHandler NULL_CONTENT_HANDLER = new XMLReceiverAdapter(); private static final DocumentBuilderFactory documentBuilderFactory; private static Map<Thread, DocumentBuilder> documentBuilders = null; private static Map<String, SAXParserFactory> parserFactories = new HashMap<String, SAXParserFactory>(); public static final String XML_CONTENT_TYPE1 = "text/xml"; public static final String XML_CONTENT_TYPE2 = "application/xml"; public static final String XML_CONTENT_TYPE3_SUFFIX = "+xml"; public static final String XML_CONTENT_TYPE = XML_CONTENT_TYPE2; public static final String TEXT_CONTENT_TYPE_PREFIX = "text/"; public static class ParserConfiguration { public final boolean validating; public final boolean handleXInclude; public final boolean externalEntities; public final URIProcessorOutputImpl.URIReferences uriReferences; public ParserConfiguration(boolean validating, boolean handleXInclude, boolean externalEntities) { this(validating, handleXInclude, externalEntities, null); } public ParserConfiguration(boolean validating, boolean handleXInclude, boolean externalEntities, URIProcessorOutputImpl.URIReferences uriReferences) { this.validating = validating; this.handleXInclude = handleXInclude; this.externalEntities = externalEntities; this.uriReferences = uriReferences; } public ParserConfiguration(ParserConfiguration parserConfiguration, URIProcessorOutputImpl.URIReferences uriReferences) { this.validating = parserConfiguration.validating; this.handleXInclude = parserConfiguration.handleXInclude; this.externalEntities = parserConfiguration.externalEntities; this.uriReferences = uriReferences; } public String getKey() { return (validating ? "1" : "0") + (handleXInclude ? "1" : "0") + (externalEntities ? "1" : "0"); } public static final ParserConfiguration PLAIN = new ParserConfiguration(false, false, false); public static final ParserConfiguration XINCLUDE_ONLY = new ParserConfiguration(false, true, false); } static { try { // Create factory documentBuilderFactory = (DocumentBuilderFactory) Class .forName("orbeon.apache.xerces.jaxp.DocumentBuilderFactoryImpl").newInstance(); // Configure factory documentBuilderFactory.setNamespaceAware(true); } catch (Exception e) { throw new OXFException(e); } } private XMLUtils() { } /** * Create a new DocumentBuilder. * * WARNING: Check how this is used in this file first before calling! */ private static DocumentBuilder newDocumentBuilder() { synchronized (documentBuilderFactory) { try { return documentBuilderFactory.newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new OXFException(e); } } } /** * Create a new SAX parser factory. * * WARNING: Use this only in special cases. In general, use newSAXParser(). */ public static SAXParserFactory createSAXParserFactory(XMLUtils.ParserConfiguration parserConfiguration) { try { return new XercesSAXParserFactoryImpl(parserConfiguration); } catch (Exception e) { throw new OXFException(e); } } /** * Get a SAXParserFactory to build combinations of validating and XInclude-aware SAXParser. * * @param parserConfiguration parser configuration * @return the SAXParserFactory */ public static synchronized SAXParserFactory getSAXParserFactory( XMLUtils.ParserConfiguration parserConfiguration) { final String key = parserConfiguration.getKey(); final SAXParserFactory existingFactory = parserFactories.get(key); if (existingFactory != null) return existingFactory; final SAXParserFactory newFactory = createSAXParserFactory(parserConfiguration); parserFactories.put(key, newFactory); return newFactory; } /** * Create a new SAXParser, which can be a combination of validating and/or XInclude-aware. * * @param parserConfiguration parser configuration * @return the SAXParser */ public static synchronized SAXParser newSAXParser(XMLUtils.ParserConfiguration parserConfiguration) { try { return getSAXParserFactory(parserConfiguration).newSAXParser(); } catch (Exception e) { throw new OXFException(e); } } public static XMLReader newXMLReader(XMLUtils.ParserConfiguration parserConfiguration) { final SAXParser saxParser = XMLUtils.newSAXParser(parserConfiguration); try { final XMLReader xmlReader = saxParser.getXMLReader(); xmlReader.setEntityResolver(XMLUtils.ENTITY_RESOLVER); xmlReader.setErrorHandler(XMLUtils.ERROR_HANDLER); return xmlReader; } catch (Exception e) { throw new OXFException(e); } } public static String prefixFromQName(String qName) { final int colonIndex = qName.indexOf(':'); return (colonIndex == -1) ? "" : qName.substring(0, colonIndex); } public static String localNameFromQName(String qName) { final int colonIndex = qName.indexOf(':'); return (colonIndex == -1) ? qName : qName.substring(colonIndex + 1); } public static String buildQName(String prefix, String localname) { return (prefix == null || prefix.equals("")) ? localname : prefix + ":" + localname; } /** * Encode a URI and local name to an exploded QName (also known as a "Clark name") String. */ public static String buildExplodedQName(String uri, String localname) { if ("".equals(uri)) return localname; else { final StringBuilder sb = new StringBuilder(uri.length() + localname.length() + 2); sb.append('{'); sb.append(uri); sb.append('}'); sb.append(localname); return sb.toString(); } } public static String buildExplodedQName(QName qName) { return buildExplodedQName(qName.getNamespaceURI(), qName.getName()); } /** * Convert dom4j attributes to SAX attributes. * * @param element dom4j Element * @return SAX Attributes */ public static AttributesImpl getSAXAttributes(Element element) { final AttributesImpl result = new AttributesImpl(); for (Iterator i = element.attributeIterator(); i.hasNext();) { final org.dom4j.Attribute attribute = (org.dom4j.Attribute) i.next(); result.addAttribute(attribute.getNamespaceURI(), attribute.getName(), attribute.getQualifiedName(), XMLReceiverHelper.CDATA, attribute.getValue()); } return result; } /** * Return whether the given mediatype is considered as XML. * * TODO: This does test on the mediatype only, but we need one to check the content type as well for the case * "text/html; charset=foobar" * * @param mediatype mediatype or null * @return true if not null and XML mediatype, false otherwise */ public static boolean isXMLMediatype(String mediatype) { return mediatype != null && (mediatype.equals(XML_CONTENT_TYPE1) || mediatype.equals(XML_CONTENT_TYPE2) || mediatype.endsWith(XML_CONTENT_TYPE3_SUFFIX)); } /** * Return whether the given content type is considered as text. * * @param contentType content type or null * @return true if not null and a text content type, false otherwise. */ public static boolean isTextContentType(String contentType) { return contentType != null && contentType.startsWith(TEXT_CONTENT_TYPE_PREFIX); } /** * Return whether the given content type is considered as text or JSON. * * NOTE: There was debate about whether JSON is text or not and whether it should have a text/* mediatype: * * http://www.alvestrand.no/pipermail/ietf-types/2006-February/001655.html * * @param contentType content type or null * @return true if not null and a text or JSON content type, false otherwise */ public static boolean isTextOrJSONContentType(String contentType) { return contentType != null && (isTextContentType(contentType) || contentType.startsWith("application/json")); } /** * Given an input stream, return a reader. This performs encoding detection as per the XML spec. Caller must close * the resulting Reader when done. * * @param inputStream InputStream to process * @return Reader initialized with the proper encoding * @throws IOException */ public static Reader getReaderFromXMLInputStream(InputStream inputStream) throws IOException { // Create a Xerces XMLInputSource final XMLInputSource inputSource = new XMLInputSource(null, null, null, inputStream, null); // Obtain encoding from Xerces final XMLEntityManager entityManager = new XMLEntityManager(); entityManager.setProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY, new XMLErrorReporter());// prevent NPE by providing this entityManager.setupCurrentEntity("[xml]", inputSource, false, true);// the result is the encoding, but we don't use it directly return entityManager.getCurrentEntity().reader; } public static class EntityResolver implements org.xml.sax.EntityResolver { public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { final InputSource is = new InputSource(); is.setSystemId(systemId); is.setPublicId(publicId); final URL url = URLFactory.createURL(systemId); // Would be nice to support XML Catalogs or similar here. See: // http://xerces.apache.org/xerces2-j/faq-xcatalogs.html if (url.getProtocol().equals("http")) { logger.warn("XML entity resolver for public id: " + publicId + " is accessing external entity via HTTP: " + url.toExternalForm()); } is.setByteStream(url.openStream()); return is; } } public static class ErrorHandler implements org.xml.sax.ErrorHandler { public void error(SAXParseException exception) throws SAXException { // NOTE: We used to throw here, but we probably shouldn't. logger.info("Error: " + exception); } public void fatalError(SAXParseException exception) throws SAXException { throw new ValidationException("Fatal error: " + exception.getMessage(), new LocationData(exception)); } public void warning(SAXParseException exception) throws SAXException { logger.info("Warning: " + exception); } } public static Document createDocument() { return getThreadDocumentBuilder().newDocument(); } public static Document stringToDOM(String xml) { try { return getThreadDocumentBuilder().parse(new InputSource(new StringReader(xml))); } catch (SAXException e) { throw new OXFException(e); } catch (IOException e) { throw new OXFException(e); } } /** * Parse a string into SAX events. If the string is empty or only contains white space, output an empty document. * * @param xml XML string * @param systemId system id of the document, or null * @param xmlReceiver receiver to output to * @param parserConfiguration parser configuration * @param handleLexical whether the XML parser must output SAX LexicalHandler events, including comments */ public static void stringToSAX(String xml, String systemId, XMLReceiver xmlReceiver, XMLUtils.ParserConfiguration parserConfiguration, boolean handleLexical) { if (xml.trim().equals("")) { try { xmlReceiver.startDocument(); xmlReceiver.endDocument(); } catch (SAXException e) { throw new OXFException(e); } } else { readerToSAX(new StringReader(xml), systemId, xmlReceiver, parserConfiguration, handleLexical); } } /** * Read a URL into SAX events. * * @param systemId system id of the document * @param xmlReceiver receiver to output to * @param parserConfiguration parser configuration * @param handleLexical whether the XML parser must output SAX LexicalHandler events, including comments */ public static void urlToSAX(String systemId, XMLReceiver xmlReceiver, XMLUtils.ParserConfiguration parserConfiguration, boolean handleLexical) { try { final URL url = URLFactory.createURL(systemId); final InputStream is = url.openStream(); final InputSource inputSource = new InputSource(is); inputSource.setSystemId(systemId); try { inputSourceToSAX(inputSource, xmlReceiver, parserConfiguration, handleLexical); } finally { is.close(); } } catch (IOException e) { throw new OXFException(e); } } public static void inputStreamToSAX(InputStream inputStream, String systemId, XMLReceiver xmlReceiver, XMLUtils.ParserConfiguration parserConfiguration, boolean handleLexical) { final InputSource inputSource = new InputSource(inputStream); inputSource.setSystemId(systemId); inputSourceToSAX(inputSource, xmlReceiver, parserConfiguration, handleLexical); } public static void readerToSAX(Reader reader, String systemId, XMLReceiver xmlReceiver, XMLUtils.ParserConfiguration parserConfiguration, boolean handleLexical) { final InputSource inputSource = new InputSource(reader); inputSource.setSystemId(systemId); inputSourceToSAX(inputSource, xmlReceiver, parserConfiguration, handleLexical); } private static void inputSourceToSAX(InputSource inputSource, XMLReceiver xmlReceiver, XMLUtils.ParserConfiguration parserConfiguration, boolean handleLexical) { // Insert XInclude processor if needed final TransformerURIResolver resolver; if (parserConfiguration.handleXInclude) { parserConfiguration = new XMLUtils.ParserConfiguration(parserConfiguration.validating, false, parserConfiguration.externalEntities, parserConfiguration.uriReferences); resolver = new TransformerURIResolver(XMLUtils.ParserConfiguration.PLAIN); xmlReceiver = new XIncludeReceiver(null, xmlReceiver, parserConfiguration.uriReferences, resolver); } else { resolver = null; } try { final XMLReader xmlReader = newSAXParser(parserConfiguration).getXMLReader(); xmlReader.setContentHandler(xmlReceiver); if (handleLexical) xmlReader.setProperty(XMLConstants.SAX_LEXICAL_HANDLER, xmlReceiver); xmlReader.setEntityResolver(ENTITY_RESOLVER); xmlReader.setErrorHandler(ERROR_HANDLER); xmlReader.parse(inputSource); } catch (SAXParseException e) { throw new ValidationException(e.getMessage(), new LocationData(e)); } catch (Exception e) { throw new OXFException(e); } finally { if (resolver != null) resolver.destroy(); } } /** * Return whether the given string contains well-formed XML. * * @param xmlString string to check * @return true iif the given string contains well-formed XML */ public static boolean isWellFormedXML(String xmlString) { // Empty string is never well-formed XML if (xmlString.trim().length() == 0) return false; try { final XMLReader xmlReader = newSAXParser(XMLUtils.ParserConfiguration.PLAIN).getXMLReader(); xmlReader.setContentHandler(NULL_CONTENT_HANDLER); xmlReader.setEntityResolver(ENTITY_RESOLVER); xmlReader.setErrorHandler(new org.xml.sax.ErrorHandler() { public void error(SAXParseException exception) throws SAXException { throw exception; } public void fatalError(SAXParseException exception) throws SAXException { throw exception; } public void warning(SAXParseException exception) throws SAXException { } }); xmlReader.parse(new InputSource(new StringReader(xmlString))); return true; } catch (Exception e) { // Ideally we would like the parser to not throw as this is time-consuming, but not sure how to achieve that return false; } } /** * Associated one DocumentBuilder per thread. This is so we avoid synchronizing (parse() for * example may take a lot of time on a DocumentBuilder) or creating DocumentBuilder instances * all the time. Since typically in an app server we work with a thread pool, not too many * instances of DocumentBuilder should be created. */ private static DocumentBuilder getThreadDocumentBuilder() { Thread thread = Thread.currentThread(); DocumentBuilder documentBuilder = (documentBuilders == null) ? null : documentBuilders.get(thread); // Try a first test outside the synchronized block if (documentBuilder == null) { synchronized (documentBuilderFactory) { // Redo the test within the synchronized block documentBuilder = (documentBuilders == null) ? null : documentBuilders.get(thread); if (documentBuilder == null) { if (documentBuilders == null) documentBuilders = new HashMap<Thread, DocumentBuilder>(); documentBuilder = newDocumentBuilder(); documentBuilders.put(thread, documentBuilder); } } } return documentBuilder; } public static String domToString(Node node) { try { Transformer transformer = TransformerUtils.getXMLIdentityTransformer(); DOMSource source = new DOMSource(node); StringBuilderWriter writer = new StringBuilderWriter(); transformer.transform(source, new StreamResult(writer)); return writer.toString(); } catch (TransformerException e) { throw new OXFException(e); } } public static void error(String message) { throw new OXFException(message); } /** * Compute a digest for a SAX source. */ public static byte[] getDigest(Source source) { final DigestContentHandler digester = new DigestContentHandler(); TransformerUtils.sourceToSAX(source, digester); return digester.getResult(); } /** * This digester is based on some existing public document (not sure which). There are some * changes though. It is not clear anymore why we used that document as a base, as this is * purely internal. * * The bottom line is that the digest should change whenever the infoset of the source XML * document changes. */ public static class DigestContentHandler implements XMLReceiver { private static final int ELEMENT_CODE = Node.ELEMENT_NODE; private static final int ATTRIBUTE_CODE = Node.ATTRIBUTE_NODE; private static final int TEXT_CODE = Node.TEXT_NODE; private static final int PROCESSING_INSTRUCTION_CODE = Node.PROCESSING_INSTRUCTION_NODE; private static final int NAMESPACE_CODE = 0XAA01; // some code that is none of the above private static final int COMMENT_CODE = 0XAA02; // some code that is none of the above /** * 4/6/2005 d : Previously we were using String.getBytes( "UnicodeBigUnmarked" ). ( Believe * the code was copied from RFC 2803 ). This first tries to get a java.nio.Charset with * the name if this fails it uses a sun.io.CharToByteConverter. * Now in the case of "UnicodeBigUnmarked" there is no such Charset so a * CharToByteConverter, utf-16be, is used. Unfortunately this negative lookup is expensive. * ( Costing us a full second in the 50thread/512MB test. ) * The solution, of course, is just to use get the appropriate Charset and hold on to it. */ private static final Charset utf16BECharset = Charset.forName("UTF-16BE"); /** * Encoder has state and therefore cannot be shared across threads. */ private final CharsetEncoder charEncoder = utf16BECharset.newEncoder(); private java.nio.CharBuffer charBuff = java.nio.CharBuffer.allocate(64); private java.nio.ByteBuffer byteBuff = java.nio.ByteBuffer.allocate(128); private final MessageDigest digest = SecureUtils.defaultMessageDigest(); private void ensureCharBuffRemaining(final int size) { if (charBuff.remaining() < size) { final int cpcty = (charBuff.capacity() + size) * 2; final java.nio.CharBuffer newChBuf = java.nio.CharBuffer.allocate(cpcty); newChBuf.put(charBuff); charBuff = newChBuf; } } private void updateWithCharBuf() { final int reqSize = (int) charEncoder.maxBytesPerChar() * charBuff.position(); if (byteBuff.capacity() < reqSize) { byteBuff = java.nio.ByteBuffer.allocate(2 * reqSize); } // Make ready for read charBuff.flip(); final CoderResult cr = charEncoder.encode(charBuff, byteBuff, true); try { if (cr.isError()) cr.throwException(); // Make ready for read byteBuff.flip(); final byte[] byts = byteBuff.array(); final int len = byteBuff.remaining(); final int strt = byteBuff.arrayOffset(); digest.update(byts, strt, len); } catch (final CharacterCodingException e) { throw new OXFException(e); } catch (java.nio.BufferOverflowException e) { throw new OXFException(e); } catch (java.nio.BufferUnderflowException e) { throw new OXFException(e); } finally { // Make ready for write charBuff.clear(); byteBuff.clear(); } } private void updateWith(final String s) { addToCharBuff(s); updateWithCharBuf(); } private void updateWith(final char[] chArr, final int ofst, final int len) { ensureCharBuffRemaining(len); charBuff.put(chArr, ofst, len); updateWithCharBuf(); } private void addToCharBuff(final char c) { ensureCharBuffRemaining(1); charBuff.put(c); } private void addToCharBuff(final String s) { final int size = s.length(); ensureCharBuffRemaining(size); charBuff.put(s); } public byte[] getResult() { return digest.digest(); } public void setDocumentLocator(Locator locator) { } public void startDocument() throws SAXException { charBuff.clear(); byteBuff.clear(); charEncoder.reset(); } public void endDocument() throws SAXException { } public void startPrefixMapping(String prefix, String uri) throws SAXException { digest.update((byte) ((NAMESPACE_CODE >> 24) & 0xff)); digest.update((byte) ((NAMESPACE_CODE >> 16) & 0xff)); digest.update((byte) ((NAMESPACE_CODE >> 8) & 0xff)); digest.update((byte) (NAMESPACE_CODE & 0xff)); updateWith(prefix); digest.update((byte) 0); digest.update((byte) 0); updateWith(uri); digest.update((byte) 0); digest.update((byte) 0); } public void endPrefixMapping(String prefix) throws SAXException { } public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { digest.update((byte) ((ELEMENT_CODE >> 24) & 0xff)); digest.update((byte) ((ELEMENT_CODE >> 16) & 0xff)); digest.update((byte) ((ELEMENT_CODE >> 8) & 0xff)); digest.update((byte) (ELEMENT_CODE & 0xff)); addToCharBuff('{'); addToCharBuff(namespaceURI); addToCharBuff('}'); addToCharBuff(localName); updateWithCharBuf(); digest.update((byte) 0); digest.update((byte) 0); int attCount = atts.getLength(); digest.update((byte) ((attCount >> 24) & 0xff)); digest.update((byte) ((attCount >> 16) & 0xff)); digest.update((byte) ((attCount >> 8) & 0xff)); digest.update((byte) (attCount & 0xff)); for (int i = 0; i < attCount; i++) { digest.update((byte) ((ATTRIBUTE_CODE >> 24) & 0xff)); digest.update((byte) ((ATTRIBUTE_CODE >> 16) & 0xff)); digest.update((byte) ((ATTRIBUTE_CODE >> 8) & 0xff)); digest.update((byte) (ATTRIBUTE_CODE & 0xff)); final String attURI = atts.getURI(i); final String attNam = atts.getLocalName(i); addToCharBuff('{'); addToCharBuff(attURI); addToCharBuff('}'); addToCharBuff(attNam); updateWithCharBuf(); digest.update((byte) 0); digest.update((byte) 0); final String val = atts.getValue(i); updateWith(val); } } public void endElement(String namespaceURI, String localName, String qName) throws SAXException { } public void characters(char ch[], int start, int length) throws SAXException { digest.update((byte) ((TEXT_CODE >> 24) & 0xff)); digest.update((byte) ((TEXT_CODE >> 16) & 0xff)); digest.update((byte) ((TEXT_CODE >> 8) & 0xff)); digest.update((byte) (TEXT_CODE & 0xff)); updateWith(ch, start, length); digest.update((byte) 0); digest.update((byte) 0); } public void ignorableWhitespace(char ch[], int start, int length) throws SAXException { } public void processingInstruction(String target, String data) throws SAXException { digest.update((byte) ((PROCESSING_INSTRUCTION_CODE >> 24) & 0xff)); digest.update((byte) ((PROCESSING_INSTRUCTION_CODE >> 16) & 0xff)); digest.update((byte) ((PROCESSING_INSTRUCTION_CODE >> 8) & 0xff)); digest.update((byte) (PROCESSING_INSTRUCTION_CODE & 0xff)); updateWith(target); digest.update((byte) 0); digest.update((byte) 0); updateWith(data); digest.update((byte) 0); digest.update((byte) 0); } public void skippedEntity(String name) throws SAXException { } public void startDTD(String name, String publicId, String systemId) throws SAXException { } public void endDTD() throws SAXException { } public void startEntity(String name) throws SAXException { } public void endEntity(String name) throws SAXException { } public void startCDATA() throws SAXException { } public void endCDATA() throws SAXException { } public void comment(char[] ch, int start, int length) throws SAXException { // We do consider comments significant for the purpose of digesting. But should this be an option? digest.update((byte) ((COMMENT_CODE >> 24) & 0xff)); digest.update((byte) ((COMMENT_CODE >> 16) & 0xff)); digest.update((byte) ((COMMENT_CODE >> 8) & 0xff)); digest.update((byte) (COMMENT_CODE & 0xff)); updateWith(ch, start, length); digest.update((byte) 0); digest.update((byte) 0); } } /** * Convert a double into a String without scientific notation. * * This is useful for XPath 1.0, which does not understand the scientific notation. */ public static String removeScientificNotation(double value) { String result = Double.toString(value); int eIndex = result.indexOf('E'); if (eIndex == -1) { // No scientific notation, return value as is return stripZeros(result); } else { // Scientific notation, convert value // Parse string representation String mantissa = result.substring(0, eIndex); boolean negative = mantissa.charAt(0) == '-'; String sign = negative ? "-" : ""; String mantissa1 = mantissa.substring(negative ? 1 : 0, negative ? 2 : 1); String mantissa2 = mantissa.substring(negative ? 3 : 2); int exponent = Integer.parseInt(result.substring(eIndex + 1)); // Calculate result if (exponent > 0) { // Positive exponent, shift decimal point to the right int mantissa2Length = mantissa2.length(); if (exponent > mantissa2Length) { result = sign + mantissa1 + mantissa2 + nZeros(exponent - mantissa2Length); } else if (exponent == mantissa2Length) { result = sign + mantissa1 + mantissa2; } else { result = sign + mantissa1 + mantissa2.substring(0, exponent) + '.' + mantissa2.substring(exponent); } } else if (exponent == 0) { // Not sure if this can happen result = mantissa; } else { // Negative exponent, shift decimal point to the left result = sign + '0' + '.' + nZeros(-exponent - 1) + mantissa1 + mantissa2; } return stripZeros(result); } } /** * Remove unnecessary zeros after the decimal point, e.g. "12.000" becomes "12". */ private static String stripZeros(String s) { int index = s.lastIndexOf('.'); if (index == -1) return s; for (int i = index + 1; i < s.length(); i++) { char c = s.charAt(i); if (c != '0') return s; } return s.substring(0, index); } private static String nZeros(int n) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < n; i++) sb.append('0'); return sb.toString(); } public static void parseDocumentFragment(Reader reader, XMLReceiver xmlReceiver) throws SAXException { try { final XMLReader xmlReader = newSAXParser(XMLUtils.ParserConfiguration.PLAIN).getXMLReader(); xmlReader.setContentHandler(new XMLFragmentReceiver(xmlReceiver)); final ArrayList<Reader> readers = new ArrayList<Reader>(3); readers.add(new StringReader("<root>")); readers.add(reader); readers.add(new StringReader("</root>")); xmlReader.parse(new InputSource(new SequenceReader(readers.iterator()))); } catch (IOException e) { throw new OXFException(e); } } public static void parseDocumentFragment(String fragment, XMLReceiver xmlReceiver) throws SAXException { if (fragment.contains("<") || fragment.contains("&")) { try { final XMLReader xmlReader = newSAXParser(XMLUtils.ParserConfiguration.PLAIN).getXMLReader(); xmlReader.setContentHandler(new XMLFragmentReceiver(xmlReceiver)); xmlReader.parse(new InputSource(new StringReader("<root>" + fragment + "</root>"))); } catch (IOException e) { throw new OXFException(e); } } else { // Optimization when fragment looks like text xmlReceiver.characters(fragment.toCharArray(), 0, fragment.length()); } } private static class XMLFragmentReceiver extends ForwardingXMLReceiver { private int elementCount = 0; public XMLFragmentReceiver(XMLReceiver xmlReceiver) { super(xmlReceiver); } public void startElement(String uri, String localname, String qName, Attributes attributes) throws SAXException { elementCount++; if (elementCount > 1) super.startElement(uri, localname, qName, attributes); } public void endElement(String uri, String localname, String qName) throws SAXException { elementCount--; if (elementCount > 0) super.endElement(uri, localname, qName); } public void startDocument() throws SAXException { } public void endDocument() throws SAXException { } } /** * Convert an Object to a String and generate SAX characters events. */ public static void objectToCharacters(Object o, ContentHandler contentHandler) { try { char[] charValue = (o == null) ? null : o.toString().toCharArray(); if (charValue != null) contentHandler.characters(charValue, 0, charValue.length); } catch (Exception e) { throw new OXFException(e); } } /** * Read characters from a Reader and generate SAX characters events. * * The caller has to close the Reader if needed. */ public static void readerToCharacters(Reader reader, ContentHandler contentHandler) { try { // Work with buffered Reader reader = new BufferedReader(reader); // Read and write in chunks char[] buf = new char[1024]; int count; while ((count = reader.read(buf)) != -1) contentHandler.characters(buf, 0, count); } catch (Exception e) { throw new OXFException(e); } } /** * Read bytes from an InputStream and generate SAX characters events in Base64 encoding. The * InputStream is closed when done. * * The caller has to close the stream if needed. */ public static void inputStreamToBase64Characters(InputStream is, ContentHandler contentHandler) { try { final OutputStream os = new ContentHandlerOutputStream(contentHandler, false); NetUtils.copyStream(new BufferedInputStream(is), os); os.close(); // necessary with ContentHandlerOutputStream to make sure all extra characters are written } catch (Exception e) { throw new OXFException(e); } } public interface Attribute { public String getURI(); public String getLocalName(); public String getQName(); public String getValue(); } public static String escapeXMLMinimal(String str) { str = StringUtils.replace(str, "&", "&"); str = StringUtils.replace(str, "<", "<"); return str; } public static String unescapeXMLMinimal(String str) { str = StringUtils.replace(str, "&", "&"); str = StringUtils.replace(str, "<", "<"); return str; } public static org.dom4j.Document cleanXML(org.dom4j.Document doc, String stylesheetURL) { try { final org.dom4j.Element element = doc.getRootElement(); final String systemId = Dom4jUtils.makeSystemId(element); // The date to clean final DOMGenerator dataToClean = new DOMGenerator(doc, "clean xml", DOMGenerator.ZeroValidity, systemId); // The stylesheet URLGenerator stylesheetGenerator = new URLGenerator(stylesheetURL); // The transformation // Define the name of the processor (this is a QName) final QName processorName = new QName("xslt", XMLConstants.OXF_PROCESSORS_NAMESPACE); // Get a factory for this processor final ProcessorFactory processorFactory = ProcessorFactoryRegistry.lookup(processorName); if (processorFactory == null) throw new OXFException("Cannot find processor factory with name '" + processorName.getNamespacePrefix() + ":" + processorName.getName() + "'"); // Create processor final Processor xsltProcessor = processorFactory.createInstance(); // Where the result goes DOMSerializer transformationOutput = new DOMSerializer(); // Connect PipelineUtils.connect(stylesheetGenerator, "data", xsltProcessor, "config"); PipelineUtils.connect(dataToClean, "data", xsltProcessor, "data"); PipelineUtils.connect(xsltProcessor, "data", transformationOutput, "data"); // Run the pipeline // Candidate for Scala withPipelineContext final PipelineContext pipelineContext = new PipelineContext(); boolean success = false; try { final org.dom4j.Document result = transformationOutput.runGetDocument(pipelineContext); success = true; return result; } finally { pipelineContext.destroy(success); } } catch (Exception e) { throw new OXFException(e); } } public static String toString(final Locator loc) { return loc.getSystemId() + ", line " + loc.getLineNumber() + ", column " + loc.getColumnNumber(); } /** * @param attributes source attributes * @return new AttributesImpl containing all attributes that were in src attributes and that were * in the default name space. */ public static AttributesImpl getAttributesFromDefaultNamespace(final Attributes attributes) { final AttributesImpl ret = new AttributesImpl(); final int size = attributes.getLength(); for (int i = 0; i < size; i++) { final String ns = attributes.getURI(i); if (!"".equals(ns)) continue; final String lnam = attributes.getLocalName(i); final String qnam = attributes.getQName(i); final String typ = attributes.getType(i); final String val = attributes.getValue(i); ret.addAttribute(ns, lnam, qnam, typ, val); } return ret; } /** * Append classes to existing attributes. This creates a new AttributesImpl object. * * @param attributes existing attributes * @param newClasses new classes to append * @return new attributes */ public static AttributesImpl appendToClassAttribute(Attributes attributes, String newClasses) { final String oldClassAttribute = attributes.getValue("class"); final String newClassAttribute = oldClassAttribute == null ? newClasses : oldClassAttribute + ' ' + newClasses; return addOrReplaceAttribute(attributes, "", "", "class", newClassAttribute); } /** * Append an attribute value to existing mutable attributes. * * @param attributes existing attributes * @param attributeName attribute name * @param attributeValue value to set or append */ public static void addOrAppendToAttribute(AttributesImpl attributes, String attributeName, String attributeValue) { final int oldAttributeIndex = attributes.getIndex(attributeName); if (oldAttributeIndex == -1) { // No existing class attribute // Add attributes.addAttribute("", attributeName, attributeName, XMLReceiverHelper.CDATA, attributeValue); } else { // Existing attribute final String oldAttributeValue = attributes.getValue(oldAttributeIndex); final String newAttributeValue = oldAttributeValue + ' ' + attributeValue; // Replace value attributes.setValue(oldAttributeIndex, newAttributeValue); } } public static AttributesImpl addOrReplaceAttribute(Attributes attributes, String uri, String prefix, String localname, String value) { final AttributesImpl newAttributes = new AttributesImpl(); boolean replaced = false; for (int i = 0; i < attributes.getLength(); i++) { final String attributeURI = attributes.getURI(i); final String attributeValue = attributes.getValue(i); final String attributeType = attributes.getType(i); final String attributeQName = attributes.getQName(i); final String attributeLocalname = attributes.getLocalName(i); if (uri.equals(attributeURI) && localname.equals(attributeLocalname)) { // Found existing attribute replaced = true; newAttributes.addAttribute(uri, localname, XMLUtils.buildQName(prefix, localname), XMLReceiverHelper.CDATA, value); } else { // Not a matched attribute newAttributes.addAttribute(attributeURI, attributeLocalname, attributeQName, attributeType, attributeValue); } } if (!replaced) { // Attribute did not exist already so add it newAttributes.addAttribute(uri, localname, XMLUtils.buildQName(prefix, localname), XMLReceiverHelper.CDATA, value); } return newAttributes; } public static AttributesImpl removeAttribute(Attributes attributes, String uri, String localname) { final AttributesImpl newAttributes = new AttributesImpl(); for (int i = 0; i < attributes.getLength(); i++) { final String attributeURI = attributes.getURI(i); final String attributeValue = attributes.getValue(i); final String attributeType = attributes.getType(i); final String attributeQName = attributes.getQName(i); final String attributeLocalname = attributes.getLocalName(i); if (!uri.equals(attributeURI) || !localname.equals(attributeLocalname)) { // Not a matched attribute newAttributes.addAttribute(attributeURI, attributeLocalname, attributeQName, attributeType, attributeValue); } } return newAttributes; } public static void streamNullDocument(ContentHandler contentHandler) throws SAXException { contentHandler.startDocument(); contentHandler.startPrefixMapping(XMLConstants.XSI_PREFIX, XMLConstants.XSI_URI); final AttributesImpl attributes = new AttributesImpl(); attributes.addAttribute(XMLConstants.XSI_URI, "nil", "xsi:nil", "CDATA", "true"); contentHandler.startElement("", "null", "null", attributes); contentHandler.endElement("", "null", "null"); contentHandler.endPrefixMapping(XMLConstants.XSI_PREFIX); contentHandler.endDocument(); } public static String saxElementToDebugString(String uri, String qName, Attributes attributes) { // Open start tag final StringBuilder sb = new StringBuilder("<"); sb.append(qName); final Set<String> declaredPrefixes = new HashSet<String>(); mapPrefixIfNeeded(declaredPrefixes, uri, qName, sb); // Attributes if any for (int i = 0; i < attributes.getLength(); i++) { mapPrefixIfNeeded(declaredPrefixes, attributes.getURI(i), attributes.getQName(i), sb); sb.append(' '); sb.append(attributes.getQName(i)); sb.append("=\""); sb.append(attributes.getValue(i)); sb.append('\"'); } // Close start tag sb.append('>'); // Content sb.append("[...]"); // Close element with end tag sb.append("</"); sb.append(qName); sb.append('>'); return sb.toString(); } private static void mapPrefixIfNeeded(Set<String> declaredPrefixes, String uri, String qName, StringBuilder sb) { final String prefix = XMLUtils.prefixFromQName(qName); if (prefix.length() > 0 && !declaredPrefixes.contains(prefix)) { sb.append(" xmlns:"); sb.append(prefix); sb.append("=\""); sb.append(uri); sb.append("\""); declaredPrefixes.add(prefix); } } public interface DebugXML { void toXML(XMLReceiverHelper helper); } public static org.dom4j.Document createDebugRequestDocument(final DebugXML debugXML) { return createDocument(new DebugXML() { public void toXML(XMLReceiverHelper helper) { wrapWithRequestElement(helper, debugXML); } }); } public static org.dom4j.Document createDocument(DebugXML debugXML) { final TransformerXMLReceiver identity = TransformerUtils.getIdentityTransformerHandler(); final LocationDocumentResult result = new LocationDocumentResult(); identity.setResult(result); final XMLReceiverHelper helper = new XMLReceiverHelper(new ForwardingXMLReceiver(identity) { @Override public void startDocument() { } @Override public void endDocument() { } }); try { identity.startDocument(); debugXML.toXML(helper); identity.endDocument(); } catch (SAXException e) { throw new OXFException(e); } return result.getDocument(); } public static void wrapWithRequestElement(XMLReceiverHelper helper, DebugXML debugXML) { helper.startDocument(); final ExternalContext externalContext = NetUtils.getExternalContext(); final ExternalContext.Request request = (externalContext != null) ? externalContext.getRequest() : null; helper.startElement("request", new String[] { "request-uri", (request != null) ? request.getRequestURI() : null, "query-string", (request != null) ? request.getQueryString() : null, "method", (request != null) ? request.getMethod() : null }); debugXML.toXML(helper); helper.endElement(); helper.endDocument(); } /** * Make an NCName out of a non-blank string. Any characters that do not belong in an NCName are converted to '_'. * * @param name source * @return NCName */ public static String makeNCName(String name) { if (StringUtils.isBlank(name)) throw new IllegalArgumentException("Name must not be blank or empty"); final Name10Checker name10Checker = Name10Checker.getInstance(); if (name10Checker.isValidNCName(name)) { return name; } else { final StringBuilder sb = new StringBuilder(); final char start = name.charAt(0); sb.append(name10Checker.isNCNameStartChar(start) ? start : '_'); for (int i = 1; i < name.length(); i++) { final char ch = name.charAt(i); sb.append(name10Checker.isNCNameChar(ch) ? ch : '_'); } return sb.toString(); } } }