/*
* Copyright 2001-2005 Internet2
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package gov.nih.nci.cagrid.opensaml;
import gov.nih.nci.cagrid.common.XMLUtilities;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Stack;
import java.util.Map.Entry;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* Utility classes for XML constants and optimizations
*
* @author Scott Cantor, Howard Gilbert
* @created January 2, 2002
*/
public class XML
{
/** OpenSAML configuration */
protected SAMLConfig config = SAMLConfig.instance();
/** XML core namespace */
public final static String XML_NS = "http://www.w3.org/XML/1998/namespace";
/** XML namespace for xmlns attributes */
public final static String XMLNS_NS = "http://www.w3.org/2000/xmlns/";
/** XML Schema Instance namespace */
public final static String XSI_NS = "http://www.w3.org/2001/XMLSchema-instance";
/** XML Schema Instance namespace */
public final static String XSD_NS = "http://www.w3.org/2001/XMLSchema";
/** OpenSAML XML namespace */
public final static String OPENSAML_NS = "http://www.opensaml.org";
/** SAML XML namespace */
public final static String SAML_NS = "urn:oasis:names:tc:SAML:1.0:assertion";
/** SAML protocol XML namespace */
public final static String SAMLP_NS = "urn:oasis:names:tc:SAML:1.0:protocol";
/** SAML 1.x Metadata Profile protocol indicators and namespace */
public final static String SAML10_PROTOCOL_ENUM = SAMLP_NS;
public final static String SAML11_PROTOCOL_ENUM = "urn:oasis:names:tc:SAML:1.1:protocol";
public final static String SAML_ARTIFACT_SOURCEID = "urn:oasis:names:tc:SAML:profiles:v1metadata";
/** XML Signature namespace */
public final static String XMLSIG_NS = "http://www.w3.org/2000/09/xmldsig#";
/** SOAP 1.1 Envelope XML namespace */
public final static String SOAP11ENV_NS = "http://schemas.xmlsoap.org/soap/envelope/";
/** XML core schema identifier */
public final static String XML_SCHEMA_ID = "xml.xsd";
/** SAML XML Schema Identifier */
public final static String SAML_SCHEMA_ID = "cs-sstc-schema-assertion-01.xsd";
/** SAML protocol XML Schema Identifier */
public final static String SAMLP_SCHEMA_ID = "cs-sstc-schema-protocol-01.xsd";
/** SAML 1.1 XML Schema Identifier */
public final static String SAML11_SCHEMA_ID = "cs-sstc-schema-assertion-1.1.xsd";
/** SAML 1.1 protocol XML Schema Identifier */
public final static String SAMLP11_SCHEMA_ID = "cs-sstc-schema-protocol-1.1.xsd";
/** XML Signature Schema Identifier */
public final static String XMLSIG_SCHEMA_ID = "xmldsig-core-schema.xsd";
/** SOAP 1.1 Envelope Schema Identifier */
public final static String SOAP11ENV_SCHEMA_ID = "soap-envelope.xsd";
private static Logger log = Logger.getLogger(XML.class.getName());
/** A global object to manage a pool of custom DOM parsers */
public static ParserPool parserPool = new ParserPool();
/**
* A "safe" null/empty check for strings.
*
* @param s The string to check
* @return true iff the string is null or length zero
*/
public static boolean isEmpty(String s) {
return (s==null || s.length() == 0);
}
/**
* A "safe" assignment function for strings that blocks the empty string
*
* @param s The string to check
* @return s iff the string is non-empty or else null
*/
public static String assign(String s) {
return (s != null && s.length() > 0) ? s.trim() : null;
}
/**
* Compares two strings for equality, allowing for nulls
*
* @param s1 The first operand
* @param s2 The second operand
*
* @return true iff both are null or both are non-null and the same strng value
*/
public static boolean safeCompare(String s1, String s2) {
if (s1 == null || s2 == null)
return s1 == s2;
else
return s1.equals(s2);
}
/**
* Shortcut for checking a DOM element node's namespace and local name
*
* @param e An element to compare against
* @param ns An XML namespace to compare
* @param localName A local name to compare
* @return true iff the element's local name and namespace match the
* parameters
*/
public static boolean isElementNamed(Element e, String ns, String localName) {
return (e != null && safeCompare(ns, e.getNamespaceURI()) && safeCompare(localName, e.getLocalName()));
}
/**
* Gets the first child Element of the node, skipping any Text nodes such as whitespace.
*
* @param n The parent in which to search for children
* @return The first child Element of n, or null if none
*/
public static Element getFirstChildElement(Node n) {
Node child = n.getFirstChild();
while (child != null && child.getNodeType() != Node.ELEMENT_NODE)
child = child.getNextSibling();
if (child != null)
return (Element)child;
else
return null;
}
/**
* Gets the last child Element of the node, skipping any Text nodes such as whitespace.
*
* @param n The parent in which to search for children
* @return The last child Element of n, or null if none
*/
public static Element getLastChildElement(Node n) {
Node child = n.getLastChild();
while (child != null && child.getNodeType() != Node.ELEMENT_NODE)
child = child.getPreviousSibling();
if (child != null)
return (Element)child;
else
return null;
}
/**
* Gets the first child Element of the node of the given name,
* skipping any Text nodes such as whitespace.
*
* @param n The parent in which to search for children
* @param ns The namespace URI of the element to locate
* @param localName The local name of the element to locate
* @return The first child Element of n with the specified name, or null if none
*/
public static Element getFirstChildElement(Node n, String ns, String localName) {
Element e = getFirstChildElement(n);
while (e != null && !isElementNamed(e, ns, localName))
e = getNextSiblingElement(e);
return e;
}
/**
* Gets the last child Element of the node of the given name,
* skipping any Text nodes such as whitespace.
*
* @param n The parent in which to search for children
* @param ns The namespace URI of the element to locate
* @param localName The local name of the element to locate
* @return The last child Element of n with the specified name, or null if none
*/
public static Element getLastChildElement(Node n, String ns, String localName) {
Element e = getLastChildElement(n);
while (e != null && !isElementNamed(e, ns, localName))
e = getPreviousSiblingElement(e);
return e;
}
/**
* Gets the next sibling Element of the node, skipping any Text nodes such as whitespace.
*
* @param n The sibling to start with
* @return The next sibling Element of n, or null if none
*/
public static Element getNextSiblingElement(Node n) {
Node sib = n.getNextSibling();
while (sib != null && sib.getNodeType() != Node.ELEMENT_NODE)
sib = sib.getNextSibling();
if (sib != null)
return (Element)sib;
else
return null;
}
/**
* Gets the previous sibling Element of the node, skipping any Text nodes such as whitespace.
*
* @param n The sibling to start with
* @return The previous sibling Element of n, or null if none
*/
public static Element getPreviousSiblingElement(Node n) {
Node sib = n.getPreviousSibling();
while (sib != null && sib.getNodeType() != Node.ELEMENT_NODE)
sib = sib.getPreviousSibling();
if (sib != null)
return (Element)sib;
else
return null;
}
/**
* Gets the next sibling Element of the node of the given name,
* skipping any Text nodes such as whitespace.
*
* @param n The sibling to start with
* @param ns The namespace URI of the element to locate
* @param localName The local name of the element to locate
* @return The next sibling Element of n with the specified name, or null if none
*/
public static Element getNextSiblingElement(Node n, String ns, String localName) {
Element e = getNextSiblingElement(n);
while (e != null && !isElementNamed(e, ns, localName))
e = getNextSiblingElement(e);
return e;
}
/**
* Gets the previous sibling Element of the node of the given name,
* skipping any Text nodes such as whitespace.
*
* @param n The sibling to start with
* @param ns The namespace URI of the element to locate
* @param localName The local name of the element to locate
* @return The previous sibling Element of n with the specified name, or null if none
*/
public static Element getPreviousSiblingElement(Node n, String ns, String localName) {
Element e = getPreviousSiblingElement(n);
while (e != null && !isElementNamed(e, ns, localName))
e = getPreviousSiblingElement(e);
return e;
}
/**
* Builds a QName from a QName-valued attribute by evaluating it
*
* @param e The element containing the attribute
* @param namespace The namespace of the attribute
* @param name The local name of the attribute
* @return A QName containing the attribute value as a
* namespace/local name pair.
*/
public static QName getQNameAttribute(Element e, String namespace, String name)
{
String qval = XML.assign(e.getAttributeNS(namespace, name));
if (qval == null)
return null;
return new QName(getNamespaceForQName(qval, e), qval.substring(qval.indexOf(':') + 1));
}
/**
* Builds a QName from a QName-valued text node by evaluating it
*
* @param t The text node containing the QName value
* @return A QName containing the text node value as a namespace/local
* name pair.
*/
public static QName getQNameTextNode(Text t)
{
String qval = XML.assign(t.getNodeValue());
Node n = t.getParentNode();
if (qval == null || n == null || n.getNodeType() != Node.ELEMENT_NODE)
return null;
return new QName(getNamespaceForQName(qval, (Element)n), qval.substring(qval.indexOf(':') + 1));
}
/**
* Gets the XML namespace URI that is mapped to the prefix of a QName, in
* the context of the DOM element e
*
* @param qname The QName value to map a prefix from
* @param e The DOM element in which to calculate the prefix binding
* @return The XML namespace URI mapped to qname's prefix in the
* context of e
*/
public static String getNamespaceForQName(String qname, Element e)
{
// Determine the QName prefix.
String prefix = null;
if (qname != null && qname.indexOf(':') >= 0)
prefix = qname.substring(0, qname.indexOf(':'));
return getNamespaceForPrefix(prefix, e);
}
/**
* Gets the XML namespace URI that is mapped to the specified prefix, in
* the context of the DOM element e
*
* @param prefix The namespace prefix to map
* @param e The DOM element in which to calculate the prefix binding
* @return The XML namespace URI mapped to prefix in the context of
* e
*/
public static String getNamespaceForPrefix(String prefix, Element e)
{
return e.lookupNamespaceURI(prefix);
/*
Node n = e;
String ns = null;
if (prefix != null)
{
if (prefix.equals("xml"))
return XML.XML_NS;
else if (prefix.equals("xmlns"))
return XML.XMLNS_NS;
}
while ((ns == null || ns.length()==0) && n != null && n.getNodeType() == Node.ELEMENT_NODE)
{
ns = ((Element)n).getAttributeNS(XML.XMLNS_NS,(prefix!=null) ? prefix : "xmlns");
n = n.getParentNode();
}
return ns;
*/
}
/**
* Nested class that provides XML parsers as a pooled resource
*
* @author Scott Cantor, Howard Gilbert
* @created January 15, 2002
*/
public static class ParserPool implements ErrorHandler, EntityResolver
{
/** OpenSAML configuration */
protected SAMLConfig config = SAMLConfig.instance();
// Stacks of DocumentBuilder parsers keyed by the Schema they support
private Map /*<Schema,Stack>*/ pools = new HashMap();
// The stack of non-schema-validating parsers
private Stack unparsedpool = new Stack();
// Resolution of extension schemas keyed by XML namespace
private Map /*<String,EntityResolver>*/ extensions = new HashMap();
/*
* The 1.0 and 1.1 SAML XSD files use the same namespace but
* they are not compatible. The 1.0 schema is "broken" and
* should not be used, but it is included and made available
* if someone needs to send or expects to receive 1.0 formatted
* traffic.
*
* The default Default schema is 1.1. This can be overridden
* by the hosting application if additional namespace information
* will be injected into the SAML elements. For example,
* Shibboleth must override the default and supply its own
* Schema object constructed from the same files plus at least
* the shibboleth.xsd file containing the definition of
* namespace elements that override types of the AttributeValue
* element.
*
* The default is assigned at static class initialization. It
* can be changed at any time, before or after calls have been
* made and the pools are partially filled. The default applies
* only to calls that do not specify their own Schema object.
* When the default changes, the old default Schema simply
* becomes a pool of parsers that can be used if you provide
* that Schema as an explicit argument.
*/
private Schema defaultSchema=null; // The default schema (one of the following)
private Schema schemaSAML10 = null; // The SAML 1.0 Standard schema
private Schema schemaSAML11 = null; // The SAML 1.1 Standard schema (default)
/**
* Original method to install a custom schema. Use setDefaultSchemas instead
* to maintain support for SAML 1.0 and 1.1.
*
* @param schema
* @deprecated
*/
public synchronized void setDefaultSchema(Schema schema) {
this.defaultSchema = schema;
}
/**
* Directly installs a custom schema. You must supply both a SAML 1.0
* and a SAML 1.1 schema object.
*
* @param schema10 The schemas to use when handling SAML 1.0
* @param schema11 The schemas to use when handling SAML 1.1
*/
public synchronized void setDefaultSchemas(Schema schema10, Schema schema11) {
this.schemaSAML10 = schema10;
this.schemaSAML11 = schema11;
if (SAMLConfig.instance().getBooleanProperty("gov.nih.nci.cagrid.opensaml.compatibility-mode")) {
defaultSchema=schemaSAML10;
} else {
defaultSchema=schemaSAML11; // This is the expected default
}
}
public synchronized Schema getDefaultSchema() {
return defaultSchema;
}
public synchronized Schema getSchemaSAML10() {
return schemaSAML10;
}
public synchronized Schema getSchemaSAML11() {
return schemaSAML11;
}
/*
* The JAXP factory is set up once and is then used to
* create parsers in the parser pool. Access to this field
* must be synchronized, and is in ParserPool.get()
*/
private DocumentBuilderFactory dbf = null;
/**
* Constructor for the ParserPool object
*
* <p>To demonstrate the technology, the current version of this
* code creates both 1.0 and 1.1 Schema objects. However, it then
* selects only one of the two to use. Future code could refine
* this and maintain two pools of parsers.
*/
public ParserPool()
{
// Build a parser factory and the default schema set.
dbf = XMLUtilities.getDocumentBuilderFactory();
dbf.setNamespaceAware(true);
try {
dbf.setFeature("http://apache.org/xml/features/validation/schema/normalized-value",false);
} catch (ParserConfigurationException e) {
log.warn("Unable to turn off data normalization in parser, supersignatures may fail with Xerces-J: " + e);
}
registerSchemas(null);
/*
* The DocumentBuilderFactory is almost ready. The last step
* will be to assign a particular Schema to it before
* obtaining each parser. The parser will then go in the
* pool associated with that Schema object. This is done
* at runtime in the get method.
*/
}
/**
* Registers one or more extension schemas in the default schema set. This relieves
* SAML applications from managing their own JAXP schema objects and enables dual
* compatibility with SAML 1.0 and 1.1<p>
* Note that you <b>must</b> insure that any dependencies are specified ahead of the
* schemas that require them, because they must be loaded by the SchemaFactory
* before they are required.
*
* @param exts A map of EntityResolver interfaces keyed by "systemId" to
* enable the SAML runtime to obtain the schema instances anytime required
*/
public synchronized void registerSchemas(Map /*<String,EntityResolver>*/ exts) {
// First merge the new set into the maintained set.
if (exts != null)
extensions.putAll(exts);
/*
* Create a JAXP 1.3 Schema object from an array of open files.
* There is no EntityResolver or ResourceResolver, so the list
* must be complete (no dependencies on XSD files not in the
* list. Also, to compile correctly, an XSD file must appear
* in the list before another XSD that depends on (imports) it.
*/
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
ArrayList sources = new ArrayList();
sources.add(new StreamSource(XML.class.getResourceAsStream("/schemas/" + XML_SCHEMA_ID),XML_SCHEMA_ID));
sources.add(new StreamSource(XML.class.getResourceAsStream("/schemas/" + XML.XMLSIG_SCHEMA_ID),XML.XMLSIG_SCHEMA_ID));
sources.add(new StreamSource(XML.class.getResourceAsStream("/schemas/" + XML.SOAP11ENV_SCHEMA_ID),XML.SOAP11ENV_SCHEMA_ID));
sources.add(new StreamSource(XML.class.getResourceAsStream("/schemas/" + SAML_SCHEMA_ID),SAML_SCHEMA_ID));
sources.add(new StreamSource(XML.class.getResourceAsStream("/schemas/" + SAMLP_SCHEMA_ID),SAMLP_SCHEMA_ID));
for (Iterator i=extensions.entrySet().iterator(); i.hasNext();) {
Entry entry = (Entry)i.next();
try {
sources.add(new SAXSource(((EntityResolver)entry.getValue()).resolveEntity(null,(String)entry.getKey())));
}
catch (SAXException e) {
log.error("Unable to obtain extension schema (" + entry.getKey() + "): " + e);
}
catch (IOException e) {
log.error("Unable to obtain extension schema (" + entry.getKey() + "): " + e);
}
}
try {
schemaSAML10 = factory.newSchema((Source[])sources.toArray(new Source[0]));
} catch (SAXException e) {
log.error("Unable to parse SAML 1.0 Schemas: " + e);
}
// Note: I would like to close the InputStream objects, but the API
// is silent on this. To be safe, I must assume they no longer belong
// to me but have been transferred to JAXP.
/*
* Now do it again. We need new InputStream objects because the
* previous streams have been read to the end of file.
*/
sources.clear();
sources.add(new StreamSource(XML.class.getResourceAsStream("/schemas/" + XML_SCHEMA_ID),XML_SCHEMA_ID));
sources.add(new StreamSource(XML.class.getResourceAsStream("/schemas/" + XML.XMLSIG_SCHEMA_ID),XML.XMLSIG_SCHEMA_ID));
sources.add(new StreamSource(XML.class.getResourceAsStream("/schemas/" + XML.SOAP11ENV_SCHEMA_ID),XML.SOAP11ENV_SCHEMA_ID));
sources.add(new StreamSource(XML.class.getResourceAsStream("/schemas/" + SAML11_SCHEMA_ID),SAML11_SCHEMA_ID));
sources.add(new StreamSource(XML.class.getResourceAsStream("/schemas/" + SAMLP11_SCHEMA_ID),SAMLP11_SCHEMA_ID));
for (Iterator i=extensions.entrySet().iterator(); i.hasNext();) {
Entry entry = (Entry)i.next();
try {
sources.add(new SAXSource(((EntityResolver)entry.getValue()).resolveEntity(null,(String)entry.getKey())));
}
catch (SAXException e) {
log.error("Unable to obtain extension schema (" + entry.getKey() + "): " + e);
}
catch (IOException e) {
log.error("Unable to obtain extension schema (" + entry.getKey() + "): " + e);
}
}
try {
schemaSAML11 = factory.newSchema((Source[])sources.toArray(new Source[0]));
} catch (SAXException e) {
log.error("Unable to parse SAML 1.1 Schemas: " + e);
}
// A property can be used to select icky 1.0 syntax
if (SAMLConfig.instance().getBooleanProperty("gov.nih.nci.cagrid.opensaml.compatibility-mode")) {
defaultSchema=schemaSAML10;
} else {
defaultSchema=schemaSAML11; // This is the expected default
}
}
/**
* Get a DOM parser suitable for our task
*
* @param schema JAXP 1.3 Schema object (or null for no XSD)
* @return A DOM parser ready to use
* @exception SAMLException Raised if a system error prevents a parser
* from being created
*/
public synchronized DocumentBuilder get(Schema schema)
throws SAMLException
{
DocumentBuilder p = null;
Stack pool;
if (schema!=null) {
pool = (Stack) pools.get(schema);
if (pool==null) {
pool = new Stack();
pools.put(schema,pool);
}
} else {
pool = unparsedpool; // Parser with no xsd validation
}
if (pool.empty())
{
// Build a parser to order.
try {
dbf.setSchema(schema); // null for no validation, or a Schema object
p = dbf.newDocumentBuilder();
p.setErrorHandler(this);
p.setEntityResolver(this); // short-circuits URI resolution
} catch (ParserConfigurationException e) {
log.error("Unable to obtain usable XML parser from environment");
throw new SAMLException("Unable to obtain usable XML parser from environment",e);
}
}
else
p = (DocumentBuilder)pool.pop();
return p;
}
/**
* Get a DocumentBuilder for the default Schema
*
* <p>Note: This uses the default (probably SAML 1.1) Schema.
* To get an non-schema-validating parser, call "get(null)". </p>
*
* @return Document Builder
* @throws SAMLException can't create a DocumentBuilder
*/
public DocumentBuilder get() throws SAMLException{
return get(getDefaultSchema());
}
/**
* Parses a document using a pooled parser with the proper settings
*
* @param in A stream containing the content to
* be parsed
* @param schema Schema object or null
* @return The DOM document resulting from the
* parse
* @exception SAMLException Raised if a parser is unavailable
* @exception SAXException Raised if a parsing error occurs
* @exception java.io.IOException Raised if an I/O error occurs
*/
public Document parse(InputSource in, Schema schema)
throws SAMLException, SAXException, java.io.IOException
{
DocumentBuilder p = get(schema);
try
{
Document doc =p.parse(in);
return doc;
}
finally
{
put(p);
}
}
/**
* Short form of parse to support legacy callers
*
* <p>This version is not preferred. If the caller converts
* the InputStream to an InputSource, then it can append a
* file name as the systemId. Here we only get the InputStream
* and create an InputSource with no identifier to be used in
* logging or generating error messages.
*
* @param in InputStream of XML to be parsed
* @return DOM Document
* @exception SAMLException Raised if a parser is unavailable
* @exception SAXException Raised if a parsing error occurs
* @exception java.io.IOException Raised if an I/O error occurs
*/
public Document parse(InputStream in)
throws SAMLException, SAXException, java.io.IOException {
return parse(new InputSource(in),getDefaultSchema());
}
/**
* Parses a document using a pooled parser with the proper settings
*
* @param systemId The URI to parse
* @return The DOM document resulting from the
* parse
* @exception SAMLException Raised if a parser is unavailable
* @exception SAXException Raised if a parsing error occurs
* @exception java.io.IOException Raised if an I/O error occurs
*/
public Document parse(String systemId, Schema schema)
throws SAMLException, SAXException, java.io.IOException
{
DocumentBuilder p = get(schema);
try
{
Document doc = p.parse(new InputSource(systemId));
return doc;
}
finally
{
put(p);
}
}
/**
* Legacy version of parse where the default Schema is implied
*
* @param systemId URI to be parsed, becomes systemId of InputSource
* @return DOM Document
* @exception SAMLException Raised if a parser is unavailable
* @exception SAXException Raised if a parsing error occurs
* @exception java.io.IOException Raised if an I/O error occurs
*/
public Document parse(String systemId)
throws SAMLException, SAXException, java.io.IOException {
return parse(systemId,getDefaultSchema());
}
/**
* Builds a new DOM document
*
* <p>In JAXP, you get a new empty DOM document from a
* DocumentBuilder. There is no evidence that the Schema
* is attached to the DOM, so it doesn't matter what pool
* to use.
*
* @return An empty DOM document
*/
public Document newDocument()
{
DocumentBuilder p=null;
try {
p = get();
} catch (SAMLException e) {
// Configuration error, no XML support. Return null??
// Throw RuntimeException??
return null;
}
Document doc = p.newDocument();
put(p);
return doc;
}
/**
* Return a parser to the pool
*
* @param p Description of Parameter
*/
public synchronized void put(DocumentBuilder p)
{
Schema schema = p.getSchema();
if (schema==null){
unparsedpool.push(p);
} else {
Stack pool = (Stack) pools.get(schema);
pool.push(p);
}
}
/**
* Called by parser if a fatal error is detected, does nothing
*
* @param exception Describes the error
* @exception SAXException Can be raised to indicate an explicit error
*/
public void fatalError(SAXParseException e)
throws SAXException
{
throw e;
}
/**
* Called by parser if an error is detected, currently just throws e
*
* @param e Description of Parameter
* @exception SAXParseException Can be raised to indicate an explicit
* error
*/
public void error(SAXParseException e)
throws SAXParseException
{
throw e;
}
/**
* Called by parser if a warning is issued, currently logs the
* condition
*
* @param e Describes the warning
* @exception SAXParseException Can be raised to indicate an explicit
* error
*/
public void warning(SAXParseException e)
throws SAXParseException
{
log.warn("Parser warning: line = " + e.getLineNumber() + " : uri = " + e.getSystemId());
log.warn("Parser warning (root cause): " + e.getMessage());
}
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
/*
* During parsing, this should not be called with a systemId corresponding to any known
* externally resolvable entity. It prevents "accidental" resolution of external entities
* via URI resolution. Network based retrieval of resources is NOT allowable and should
* really be something the parser can block globally. We also can't return null, because
* that signals URI resolution. So what we return is a dummy source to shortcut and
* fail any such attempts.
*/
return new InputSource(); // Hopefully this will fail the parser and not be treated as null.
}
}
}