/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-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 org.xml.sax.SAXException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.geotools.xml.impl.ElementNameStreamingParserHandler;
import org.geotools.xml.impl.StreamingParserHandler;
import org.geotools.xml.impl.TypeStreamingParserHandler;
/**
* XML parser capable of streaming.
* <p>
* Performs the same task as {@link org.geotools.xml.Parser}, with the addition
* that objects are streamed back to the client. Streaming can occur in a
* number of different modes.
* </p>
* <p>
* As an example consider the following gml document:
* <pre>
* <test:TestFeatureCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
* xmlns:gml="http://www.opengis.net/gml"
* xmlns:test="http://www.geotools.org/test"
* xsi:schemaLocation="http://www.geotools.org/test test.xsd">
*
* <gml:featureMember>
* <test:TestFeature fid="0">
* ...
* </test:TestFeature>
* </gml:featureMember>
*
* <gml:featureMember>
* <test:TestFeature fid="1">
* ...
* </test:TestFeature>
* </gml:featureMember>
*
* <gml:featureMember>
* <test:TestFeature fid="2">
* ....
* </test:TestFeature>
* </gml:featureMember>
*
* </test:TestFeatureCollection>
* </pre>
* And suppose we want to stream back each feature as it is parsed.
* </p>
* <p>
* <h3>1. Element Name</h3>
* Objects are streamed back when an element of a particular name has been
* parsed.
* <pre>
* Configuration configuration = new GMLConfiguration();
* QName elementName = new QName( "http://www.geotools.org/test", "TestFeature" );
*
* StreamingParser parser = new StreamingParser( configuration, elementName );
*
* Feature f = null;
* while ( ( f = parser.parse() ) != null ) {
* ...
* }
* </pre>
* </p>
* <p>
* <h3>2. Type</h3>
* Objects are streamed back when an element has been parsed into an object
* of a particular type.
* <pre>
* Configuration configuration = new GMLConfiguration();
* StreamingParser parser = new StreamingParser( configuration, Feature.class );
*
* Feature f = null;
* while ( ( f = parser.parse() ) != null ) {
* ...
* }
* </pre>
* </p>
* <p>
* <h3>3. Xpath Expression</h3>
* Objects are streamed back when an element has been parsed which matches
* a particular xpath expression.
* <pre>
* Configuration configuration = new GMLConfiguration();
* String xpath = "//TestFeature";
* StreamingParser parser = new StreamingParser( configuration, xpath );
*
* Feature f = null;
* while ( ( f = parser.parse() ) != null ) {
* ...
* }
* </pre>
* </p>
*
* @author Justin Deoliveira, The Open Planning Project
*
* @source $URL$
*/
public class StreamingParser {
/**
* The sax driver / handler.
*/
private StreamingParserHandler handler;
/**
* The sax parser.
*/
private SAXParser parser;
/**
* The xml input.
*/
private InputStream input;
/**
* The parsing thread.
*/
private Thread thread;
/**
* Creates a new instance of the type based streaming parser.
*
* @param configuration Object representing the configuration of the parser.
* @param input The input stream representing the instance document to be parsed.
* @param type The type of parsed objects to stream back.
*
* @throws ParserConfigurationException
* @throws SAXException
*/
public StreamingParser(Configuration configuration, InputStream input, Class type)
throws ParserConfigurationException, SAXException {
this(configuration, input, new TypeStreamingParserHandler(configuration, type));
}
/**
* Creates a new instance of the element name based streaming parser.
*
* @param configuration Object representing the configuration of the parser.
* @param input The input stream representing the instance document to be parsed.
* @param elementName The name of elements to stream back.
*
* @throws ParserConfigurationException
* @throws SAXException
*/
public StreamingParser(Configuration configuration, InputStream input, QName elementName)
throws ParserConfigurationException, SAXException {
this(configuration, input, new ElementNameStreamingParserHandler(configuration, elementName));
}
/**
* Creates a new instance of the xpath based streaming parser.
*
* @param configuration Object representing the configuration of the parser.
* @param input The input stream representing the instance document to be parsed.
* @param xpath An xpath expression which dictates how the parser streams
* objects back to the client.
*
* @throws ParserConfigurationException
* @throws SAXException
*/
public StreamingParser(Configuration configuration, InputStream input, String xpath)
throws ParserConfigurationException, SAXException {
this(configuration, input, createJXpathStreamingParserHandler(configuration,xpath));
}
/**
* Method for dynamic creation of the xpath streaming parser handler.
* <p>
* We do this to allow the jxpath component to be removed... and avoid its
* dependencies.
* </p>
* @param configuration
* @param xpath
* @return
*/
static StreamingParserHandler createJXpathStreamingParserHandler(Configuration configuration, String xpath)
throws ParserConfigurationException {
Class clazz;
try {
clazz = Class.forName( "org.geotools.xml.impl.jxpath.JXPathStreamingParserHandler");
} catch (ClassNotFoundException e) {
throw (ParserConfigurationException) new ParserConfigurationException().initCause(e);
}
Constructor c;
try {
c = clazz.getConstructor(new Class[]{Configuration.class,String.class});
return (StreamingParserHandler) c.newInstance(new Object[]{configuration,xpath});
}
catch( Exception e ) {
//shoudl not happen
throw new RuntimeException( e );
}
//return new JXPathStreamingParserHandler(configuration, xpath)
}
/**
* Internal constructor.
*/
protected StreamingParser(Configuration configuration, InputStream input,
StreamingParserHandler handler) throws ParserConfigurationException, SAXException {
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
parser = spf.newSAXParser();
this.handler = handler;
this.input = input;
}
/**
* Streams the parser to the next element in the instance document which
* matches the xpath query specified in the contstructor. This method
* returns null when there are no more objects to stream.
*
* @return The next object in the stream, or null if no such object is
* available.
*/
public Object parse() {
if (thread == null) {
Runnable runnable = new Runnable() {
public void run() {
try {
parser.parse(input, handler);
} catch (Exception e) {
//close the buffer
handler.getBuffer().close();
throw new RuntimeException(e);
}
}
;
};
thread = new Thread(runnable);
thread.start();
}
return handler.getBuffer().get();
}
}