/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-2008, Open Source Geospatial Foundation (OSGeo)
*
* 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;
* version 2.1 of the License.
*
* 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.
*/
package org.geotools.xml;
import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.xml.gml.FCBuffer.StopException;
import org.geotools.xml.handlers.ComplexElementHandler;
import org.geotools.xml.handlers.DocumentHandler;
import org.geotools.xml.handlers.ElementHandlerFactory;
import org.geotools.xml.handlers.IgnoreHandler;
import org.xml.sax.Attributes;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
* This is a schema content handler. Code here has been modified from code
* written by Ian Schneider.
*
* <p>
* This class contains one stack used to store part of the parse tree. The
* ElementHandlers found on the stack have direct next handlers placed on the
* stack. So here's the warning, be careful to read how you may be affecting
* (or forgetting to affect) the stack.
* </p>
*
* <p>
* If a FlowHandler implementation is available in the hints, the handler will
* periodically check it to see if it should stop parsing. See the FlowHandler
* interface.
* </p>
*
* <p>
* This is an XML Schema driven parser and {@link #resolveEntity(String, String)} will
* ignore all dtd references. If an {@link EntityResolver} is provided it will be used.
* </p>
*
* @author dzwiers, Refractions Research, Inc. http://www.refractions.net
*
* @source $URL$
* @version $Id$
*
* @see XMLElementHandler
*/
public class XMLSAXHandler extends DefaultHandler {
/**
* the logger -- should be used for debugging (assuming there are bugs LOL)
*/
protected final static Logger logger = org.geotools.util.logging.Logging.getLogger(
"net.refractions.xml.sax");
protected static Level level = Level.FINE;
// the stack of handlers
private Stack handlers = new Stack();
/** Collects string chunks in {@link #characters(char[], int, int)}
* callback to be handled at the beggining of {@link #endElement(String, String, String)}
*/
private StringBuffer characters = new StringBuffer();
/** entity resolver */
EntityResolver entityResolver;
public void setEntityResolver(EntityResolver entityResolver) {
this.entityResolver = entityResolver;
}
public EntityResolver getEntityResolver() {
return entityResolver;
}
/**
* Delegate to {@link #entityResolver} if available.
* <p>
* <p>
* Note this is an XMLSchema based parser, all attempts to resolved DTDs are rejected.
* </p>
*
* @param publicId The public identifier, or null if none is available.
* @param systemId The system identifier provided in the XML document.
* @return The new input source, or null to require the default behavior.
* @exception java.io.IOException If there is an error setting up the new input source.
* @exception org.xml.sax.SAXException Any SAX exception, possibly wrapping another exception.
*/
public InputSource resolveEntity( String publicId, String systemId ) throws SAXException, IOException {
// avoid dtd files
if (systemId != null && systemId.endsWith("dtd")) {
return new InputSource(new StringReader(""));
}
if (entityResolver != null) {
return entityResolver.resolveEntity(publicId, systemId);
} else {
return super.resolveEntity(publicId, systemId);
}
}
// hints
private Map hints;
private ElementHandlerFactory ehf = new ElementHandlerFactory(logger);
// used to store prefix -> targetNamespace mapping until which time as the
// schema uri is availiable (on the next startElement Call).
private Map schemaProxy = new HashMap();
// the base handler for the document
private DocumentHandler document = null;
// the Locator stores the current position in the parse
// for end-user debug information
private Locator locator;
// the uri of the instance ducment, used to resolve relative URIs
private URI instanceDocument;
/**
* <p>
* This contructor is intended to create an XMLSAXHandler to be used when
* parsing an XML instance document. The instance document's uri is also
* be provided, as this will allow the parser to resolve relative uri's.
* </p>
*
* @param intendedDocument
* @param hints DOCUMENT ME!
*/
public XMLSAXHandler(URI intendedDocument, Map hints) {
instanceDocument = intendedDocument;
init(hints);
logger.setLevel(level);
}
/**
* <p>
* This contructor is intended to create an XMLSAXHandler to be used when
* parsing an XML instance document. The instance document's uri is also
* be provided, as this will allow the parser to resolve relative uri's.
* </p>
*
* @param hints Hints as per {@link {@link XMLHandlerHints}
*/
public XMLSAXHandler(Map<String,Object> hints) {
init(hints);
logger.setLevel(level);
}
protected void init(Map<String,Object> hints){
if( hints == null ){
hints = new HashMap<String,Object>();
}
this.hints = hints;
setEntityResolver(XMLHandlerHints.toEntityResolver(hints));
}
/**
* Implementation of endDocument.
*
* @see org.xml.sax.ContentHandler#endDocument()
*/
public void endDocument(){
document = ((DocumentHandler) handlers.pop());
}
/**
* Implementation of startDocument.
*
* @see org.xml.sax.ContentHandler#startDocument()
*/
public void startDocument(){
try {
document = new DocumentHandler(ehf);
handlers.push(document);
} catch (RuntimeException e) {
logger.warning(e.toString());
throw e;
}
}
/**
* Implementation of characters.
*
* @param ch
* @param start
* @param length
*
* @throws SAXException
*
* @see org.xml.sax.ContentHandler#characters(char[], int, int)
*/
public void characters(char[] ch, int start, int length)
throws SAXException {
characters.append(ch, start, length);
}
/**
* Handles the string chunks collected in {@link #characters}.
*/
private void handleCharacters() throws SAXException{
if(characters.length() == 0){
return;
}
try {
checkStatus();
String text = characters.toString();
characters.setLength(0);
if ((text != null) && !"".equals(text)) {
((XMLElementHandler) handlers.peek()).characters(text);
}
} catch (SAXException e) {
logger.warning(e.toString());
throw e;
}
}
private void checkStatus() throws StopException {
if (this.hints != null && hints.get(XMLHandlerHints.FLOW_HANDLER_HINT) != null) {
FlowHandler handler = (FlowHandler) hints.get(XMLHandlerHints.FLOW_HANDLER_HINT);
if (handler.shouldStop(hints)) {
throw new StopException();
}
}
if (Thread.currentThread().isInterrupted()) {
throw new StopException();
}
}
/**
* Implementation of endElement.
*
* @param namespaceURI
* @param localName
* @param qName
*
* @throws SAXException
*
* @see org.xml.sax.ContentHandler#endElement(java.lang.String,
* java.lang.String, java.lang.String)
*/
public void endElement(String namespaceURI, String localName, String qName)
throws SAXException {
handleCharacters();
logger.fine("END: " + qName);
XMLElementHandler handler = null;
try {
handler = (XMLElementHandler) handlers.peek();
URI uri = new URI(namespaceURI);
handler.endElement(uri, localName, hints);
} catch (Exception e) {
processException(e);
logger.warning(e.getMessage());
logger.warning("Line " + locator.getLineNumber() + " Col "
+ locator.getColumnNumber());
SAXException exception = new SAXException(e.getMessage()+" at Line " + locator.getLineNumber() + " Col "
+ locator.getColumnNumber()+" tag is: \n"+qName, e);
exception.initCause(e);
throw exception;
}
finally {
handlers.pop(); // we must do this or leak memory
if (handler != null && !handlers.isEmpty()){
XMLElementHandler parent = ((XMLElementHandler)handlers.peek());
if (parent instanceof ComplexElementHandler){
ComplexElementHandler complexParent = (ComplexElementHandler)parent;
String typename = complexParent.getType().getClass().getName();
// TODO: HACK The required Type is not in this Module
if(typename.equals("org.geotools.xml.wfs.WFSBasicComplexTypes$FeatureCollectionType")){
complexParent.removeElement(handler);
}
}
}
}
}
private void processException( Exception e ) {
if( e instanceof RuntimeException )
throw (RuntimeException) e;
StringBuffer msg=new StringBuffer(e.getLocalizedMessage());
StackTraceElement[] trace = e.getStackTrace();
for( int i = 0; i < trace.length; i++ ) {
StackTraceElement element = trace[i];
msg.append(" ");
msg.append(element.toString());
msg.append("\n");
}
logger.log(Level.SEVERE, msg.toString());
}
/**
* Implementation of startElement.
*
* @param namespaceURI
* @param localName
* @param qName
* @param atts
*
* @throws SAXException
*
* @see org.xml.sax.ContentHandler#startElement(java.lang.String,
* java.lang.String, java.lang.String, org.xml.sax.Attributes)
*/
public void startElement(String namespaceURI, String localName,
String qName, Attributes atts) throws SAXException {
characters.setLength(0);
checkStatus();
if (schemaProxy.size() != 0) {
logger.fine("ADDING NAMESPACES: " + schemaProxy.size());
String t = atts.getValue("http://www.w3.org/2001/XMLSchema-instance",
"schemaLocation");
if ((t == null) || "".equals(t)) {
t = atts.getValue("", "schemaLocation");
}
if (!((t == null) || "".equals(t))) {
t = t.trim();
String[] targ2uri = t.split("\\s+");
if (targ2uri != null) {
if(targ2uri.length !=0 && targ2uri.length%2!=0)
throw new SAXException("Bad Schema location attribute: you must have an even number of terms");
for (int i = 0; i < (targ2uri.length / 2); i++) {
String uri = targ2uri[(i * 2) + 1];
String targ = targ2uri[i * 2];
String prefix = (String) schemaProxy.get(targ);
URI targUri = null;
boolean set = false;
if (hints!=null && hints.containsKey(XMLHandlerHints.NAMESPACE_MAPPING)){
Map schemas=(Map) hints.get(XMLHandlerHints.NAMESPACE_MAPPING);
if( schemas.containsKey(targ) ){
ehf.startPrefixMapping(prefix, targ, (URI) schemas.get(targ));
set=true;
break;
}
}
if(!set){
try {
targUri = (instanceDocument == null) ? new URI(uri)
: instanceDocument
.resolve(uri);
} catch (URISyntaxException e1) {
logger.warning(e1.toString());
}
ehf.startPrefixMapping(prefix, targ, targUri);
}
schemaProxy.remove(targ);
}
}
}
if (schemaProxy.size() != 0) {
Iterator it = schemaProxy.keySet().iterator();
while (it.hasNext()) {
String targ = (String) it.next();
String prefix = (String) schemaProxy.get(targ);
ehf.startPrefixMapping(prefix, targ);
it.remove();
}
}
}
logger.finest("Moving on to finding the element handler");
try {
XMLElementHandler parent = ((XMLElementHandler) handlers.peek());
logger.finest("Parent Node = " + parent.getClass().getName()
+ " '" + parent.getName() + "'");
// logger.finest("Parent Node = "+parent.getClass().getName()+"
// '"+parent.getName()+"' "+
// (parent.getType()==null?"null":
// ((((ComplexType)parent.getType()).getChild()==null)?"null":
// ((((ComplexType)parent.getType()).getChild().getGrouping() ==
// ElementGrouping.SEQUENCE)?
// ((((Sequence)((ComplexType)parent.getType()).getChild()).getChildren()==null)?0:
// ((Sequence)((ComplexType)parent.getType()).getChild()).getChildren().length)+"":"null"))));
logger.finest("This Node = " + localName + " :: " + namespaceURI);
URI uri = new URI(namespaceURI);
XMLElementHandler eh = parent.getHandler(uri,
localName, hints);
if (eh == null) {
eh = new IgnoreHandler();
}
logger.finest("This Node = " + eh.getClass().getName());
handlers.push(eh);
eh.startElement(new URI(namespaceURI), localName, atts);
} catch (Exception e) {
processException(e);
logger.warning(e.toString());
logger.warning("Line " + locator.getLineNumber() + " Col "
+ locator.getColumnNumber());
SAXException exception = new SAXException(e.getMessage()+" at Line " + locator.getLineNumber() + " Col "
+ locator.getColumnNumber()+" tag is: \n"+qName, e);
exception.initCause(e);
throw exception;
}
}
/**
* <p>
* Used to set the logger level for all XMLSAXHandlers
* </p>
*
* @param l
*/
public static void setLogLevel(Level l) {
level = l;
logger.setLevel(l);
XMLElementHandler.setLogLevel(l);
}
/**
* getDocument purpose.
*
* <p>
* Completes the post-processing phase, and returns the value from the
* parse ...
* </p>
*
* @return Object parsed
*
* @throws SAXException
*
* @see DocumentHandler#getValue()
*/
public Object getDocument() throws SAXException {
return document.getValue();
}
/**
* Implementation of error.
*
* @param exception
*
* @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
*/
public void error(SAXParseException exception){
logger.severe("ERROR " + exception.getMessage());
logger.severe("col " + locator.getColumnNumber() + ", line "
+ locator.getLineNumber());
}
/**
* Implementation of fatalError.
*
* @param exception
*
* @throws SAXException
*
* @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
*/
public void fatalError(SAXParseException exception)
throws SAXException {
logger.severe("FATAL " + exception.getMessage());
if(locator != null) {
logger.severe("col " + locator.getColumnNumber() + ", line "
+ locator.getLineNumber());
}
throw exception;
}
/**
* Implementation of warning.
*
* @param exception
*
* @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
*/
public void warning(SAXParseException exception){
logger.warning("WARN " + exception.getMessage());
logger.severe("col " + locator.getColumnNumber() + ", line "
+ locator.getLineNumber());
}
/**
* Stores the locator for future error reporting
*
* @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator)
*/
public void setDocumentLocator(Locator locator) {
super.setDocumentLocator(locator);
this.locator = locator;
}
/**
* @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String)
*/
public void endPrefixMapping(String prefix){
// hard coded schemas should not be removed. For example. GML and WFS
if( prefix.equals("gml") || prefix.equals("wfs"))
return;
ehf.endPrefixMapping(prefix);
}
/**
* @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String,
* java.lang.String)
*/
public void startPrefixMapping(String prefix, String uri){
if ("http://www.w3.org/2001/XMLSchema-instance".equals(uri)) {
return;
}
schemaProxy.put(uri, prefix);
}
}