uk.ac.starlink.votable.MultiTableStreamer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of stil Show documentation
Show all versions of stil Show documentation
Starlink Tables Infrastructure Library
package uk.ac.starlink.votable;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.URL;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import uk.ac.starlink.fits.FitsTableBuilder;
import uk.ac.starlink.table.QueueTableSequence;
import uk.ac.starlink.table.RowStore;
import uk.ac.starlink.table.StarTable;
import uk.ac.starlink.table.StoragePolicy;
import uk.ac.starlink.table.TableFormatException;
import uk.ac.starlink.table.TableSequence;
import uk.ac.starlink.util.DataSource;
import uk.ac.starlink.util.StarEntityResolver;
/**
* Loads multiple tables from a VOTable document.
* They are inserted into a supplied QueueTableSequence as they are
* encountered.
*
* @author Mark Taylor
* @since 29 Sep 2010
*/
class MultiTableStreamer extends SkeletonDOMBuilder implements TableHandler {
private final StoragePolicy storage_;
private final QueueTableSequence tqueue_;
private final Namespacing namespacing_;
private volatile boolean isVotable_;
private RowStore rowStore_;
private static final Logger logger_ =
Logger.getLogger( "uk.ac.starlink.votable" );
/**
* Constructor.
*
* @param storage storage policy
* @param tqueue destination table queue for table results
* @param strict whether to effect a strict reading of the VOTable standard
*/
public MultiTableStreamer( StoragePolicy storage, QueueTableSequence tqueue,
boolean strict ) {
super( strict );
storage_ = storage;
tqueue_ = tqueue;
namespacing_ = Namespacing.getInstance();
setTableHandler( this );
}
public void startTable( StarTable meta ) throws SAXException {
flushRowStore();
assert rowStore_ == null;
try {
rowStore_ = storage_.makeRowStore();
rowStore_.acceptMetadata( meta );
}
catch ( IOException e ) {
tqueue_.addError( e );
rowStore_ = null;
}
}
public void rowData( Object[] row ) throws SAXException {
if ( rowStore_ != null ) {
try {
rowStore_.acceptRow( row );
}
catch ( IOException e ) {
tqueue_.addError( e );
rowStore_ = null;
}
}
}
public void endTable() throws SAXException {
if ( rowStore_ != null ) {
StarTable table = null;
try {
rowStore_.endRows();
}
catch ( IOException e ) {
tqueue_.addError( e );
rowStore_ = null;
}
}
}
protected void processBinaryHref( URL url, Attributes atts,
boolean isBinary2 )
throws SAXException {
TableElement tableEl = getTableElement();
if ( tableEl != null ) {
try {
String encoding = getAttribute( atts, "encoding" );
Decoder[] decoders = getDecoders( tableEl.getFields() );
TabularData tdata =
new TableBodies
.HrefBinaryTabularData( decoders, url, encoding,
isBinary2 );
tableEl.setData( tdata );
tqueue_.addTable( new VOStarTable( tableEl ) );
}
catch ( IOException e ) {
tqueue_.addError( e );
}
}
}
protected void processFitsHref( URL url, String extnum, Attributes atts )
throws SAXException {
TableElement tableEl = getTableElement();
if ( tableEl != null ) {
try {
DataSource datsrc = DataSource.makeDataSource( url );
datsrc.setPosition( extnum );
StarTable ft = new FitsTableBuilder()
.makeStarTable( datsrc, false, storage_ );
TabularData tdata =
new TableBodies.StarTableTabularData( ft );
tableEl.setData( tdata );
tqueue_.addTable( new VOStarTable( tableEl ) );
}
catch ( IOException e ) {
tqueue_.addError( e );
}
}
}
@Override
public void startElement( String namespaceURI, String localName,
String qName, Attributes atts )
throws SAXException {
if ( ! isVotable_ &&
"VOTABLE".equals( getVOTagName( namespaceURI, localName,
qName ) ) ) {
isVotable_ = true;
synchronized ( this ) {
notifyAll();
}
}
super.startElement( namespaceURI, localName, qName, atts );
}
@Override
public void endDocument() {
flushRowStore();
}
/**
* Checks if this object's RowStore is currently holding a table,
* and if so hands it off to the the table queue and clears the RowStore.
* This method should be called when the table is expected to be complete.
* However, it doesn't want to be done too early, for instance
* immediately after the end of the TABLE element,
* since there may be parts of the SAX stream somewhat later
* (sibling RESOURCE elements for instance) that should be taken
* account of when creating the StarTable.
*/
private void flushRowStore() {
if ( rowStore_ != null ) {
StarTable table = rowStore_.getStarTable();
if ( table != null ) {
tqueue_.addTable( table );
}
rowStore_ = null;
}
}
/**
* Utility method which uses an instance of this class to turn a
* SAX source into a table sequence. The returned sequence will
* be populated on a different thread. It will only be returned
* once it looks like the supplied stream does actually represent
* a VOTable; if it does not, a TableFormatException will be thrown.
*
* @param saxsrc input SAX event stream
* @param storage storage policy
* @param strict whether to effect a strict reading of the VOTable standard
*/
public static TableSequence streamStarTables( final InputSource saxsrc,
StoragePolicy storage,
boolean strict )
throws SAXException, IOException {
/* Construct a table handler which can pull out tables from a
* document and add them to a table queue. */
final QueueTableSequence tqueue = new QueueTableSequence();
final MultiTableStreamer streamer =
new MultiTableStreamer( storage, tqueue, strict );
/* Get a SAX parser. */
final XMLReader parser;
try {
SAXParserFactory spfact = SAXParserFactory.newInstance();
spfact.setValidating( false );
streamer.namespacing_.configureSAXParserFactory( spfact );
parser = spfact.newSAXParser().getXMLReader();
}
catch ( ParserConfigurationException e ) {
throw (SAXException) new SAXException( e.getMessage(), e )
.initCause( e );
}
/* Install the content handler. */
parser.setContentHandler( streamer );
/* Install a custom entity resolver. */
parser.setEntityResolver( StarEntityResolver.getInstance() );
/* Install an error handler. */
parser.setErrorHandler( new ErrorHandler() {
public void error( SAXParseException e ) {
logger_.warning( e.toString() );
}
public void warning( SAXParseException e ) {
logger_.warning( e.toString() );
}
public void fatalError( SAXParseException e ) throws SAXException {
throw e;
}
} );
/* Perform the parse in a new thread. */
final boolean[] doneHolder = new boolean[ 1 ];
Thread worker = new Thread( "VOTable streamer" ) {
public void run() {
try {
parser.parse( saxsrc );
}
catch ( Throwable e ) {
tqueue.addError( e );
}
finally {
tqueue.endSequence();
synchronized ( streamer ) {
doneHolder[ 0 ] = true;
streamer.notifyAll();
}
}
}
};
worker.setDaemon( true );
worker.start();
/* Wait until we can see that the SAX stream does contain a VOTable
* document; if it does, return the stream which continues to be
* populated by the worker thread, otherwise, throw an exception
* indicating that this is not suitable input. */
synchronized ( streamer ) {
while ( ! streamer.isVotable_ && ! doneHolder[ 0 ] ) {
try {
streamer.wait();
}
catch ( InterruptedException e ) {
throw (IOException) new InterruptedIOException()
.initCause( e );
}
}
}
if ( ! streamer.isVotable_ ) {
throw new TableFormatException( "No VOTABLE element" );
}
else {
return tqueue;
}
}
}