// JDBCSAXParser.java
package dbxml.sax;
import java.io.IOException;
import java.sql.*;
import org.xml.sax.*;
import org.xml.sax.helpers.AttributeListImpl;
/**
* SAX parser that uses a JDBC data source a input instead of an XML file
* of byte stream.
*
* This is a proof-of-concept implemention and does not address all
* the issues. Many improvements to this parser are possible.
* This parser treats a table in a database as a virtual XML
* document.
*
* @author Ramnivas Laddad
*/
public class JDBCSAXParser extends ParserBase {
/**
* When generating any SAX startElement() event, we need to send
* an attribute list. Because the attribute list is always empty
* we reuse this "stock" class member.
*/
private static final AttributeList _stockEmptyAttributeList
= new AttributeListImpl();
//-----------------------------------------------------------------------
// Methods from Parser interface
//-----------------------------------------------------------------------
/**
* Implement the method from base interface.
* If the argument input source is of type other than JDBCInputSource,
* it throws an SAXException as this parser cannot deal with it
*
* @param source an input source (must be of type JDBCInputSource)
* @exception SAXException if an error occurs or the argument is not
* of type JDBCInputSource
* @exception IOException if an error occurs
*/
public void parse (InputSource source) throws SAXException, IOException {
if (! (source instanceof JDBCInputSource)) {
throw new SAXException("JDBCSAXParser can work only with source "
+ "of JDBCInputSource type");
}
parse((JDBCInputSource)source);
}
/**
* Implement the method from base interface.
* Always thows an SAXException as the information passed is not sufficient
* to carry out parsing.
*
* @param systemId unused
* @exception SAXException thrown always
* @exception IOException never thrown
*/
public void parse (String systemId) throws SAXException, IOException {
throw new SAXException("JDBCSAXParser needs more information to "
+ "connect to database");
}
//-----------------------------------------------------------------------
// Additional methods
//-----------------------------------------------------------------------
/**
* Parse the given JDBC source to generate SAX events.
* Obtains a result set by executing a query returned by
* getSelectorSQLStatement and then parses that result set.
*
* @param source a input source describing database table to be parsed
* @exception SAXException if an error occurs
* @exception IOException if an error occurs
*/
public void parse(JDBCInputSource source)
throws SAXException, IOException {
try {
Connection connection = source.getConnection();
if (connection == null) {
throw new SAXException("Could not establish connection with "
+ "database");
}
String sqlQuery = getSelectorSQLStatement(source.getTableName());
PreparedStatement pstmt = connection.prepareStatement(sqlQuery);
ResultSet rs = pstmt.executeQuery();
parse(rs, source.getTableName());
rs.close();
connection.close();
} catch (SQLException ex) {
throw new SAXException(ex);
}
}
/**
* Parse the given JDBC result set object to generate SAX events.
*
* @param rs result set object to be parsed
* @param tableName the name of table name being parsed
* @exception SAXException if an parsing error occurs
* @exception SQLException if an SQL error occurs
* @exception IOException if an i/o error occurs
*/
public void parse(ResultSet rs, String tableName)
throws SAXException, SQLException, IOException {
if (_documentHandler == null) {
return; // nobody is intersted in me, no need to sweat!
}
ResultSetMetaData rsmd = rs.getMetaData();
int numCols = rsmd.getColumnCount();
String tableMarker = getTableMarker(tableName);
String rowMarker = getRowMarker();
_documentHandler.startDocument();
_documentHandler.startElement(tableMarker, _stockEmptyAttributeList);
while(rs.next()) {
_documentHandler.startElement(rowMarker,
_stockEmptyAttributeList);
for (int i = 1; i <= numCols; i++) {
generateSAXEventForColumn(rsmd, rs, i);
}
_documentHandler.endElement(rowMarker);
}
_documentHandler.endElement(tableMarker);
_documentHandler.endDocument();
}
/**
* A convenience method that creates a JDBCInputSource object from
* its argument and parses it
*
* @param connectionURL a JDBC URL for the database
* @param userName user name to connect to the database
* @param passwd password to connect to the database
* @param tableName the name of table name being parsed
* @exception SAXException if an parsing error occurs
* @exception IOException if an i/o error occurs
*/
public void parse(String connectionURL, String userName, String passwd,
String tableName) throws SAXException, IOException {
parse(new JDBCInputSource(connectionURL, userName, passwd, tableName));
}
//-----------------------------------------------------------------------
// Protected methods that derived classes could override to
// customize the parsing
//-----------------------------------------------------------------------
/**
* Generate SAX event when visting a column.
* Fires startElement event followed by a
* characters event followed by endElement
* event. No events are fired for a null data.
*
* This method may be overriden to customize event generation for
* columns. For example, if one wishes to use special attribute,
* instead of no events, for indicating null column, then this is the
* method to override. Also for handling binary data or using desired
* format for special types such as date and currency, one may override
* this method.
*
* @param rsmd meta data for the result set
* @param rs the result set
* @param columnIndex index of column being visited (1 for first column)
* @exception SAXException if an parsing error occurs
* @exception SQLException if an SQL error occurs
*/
protected void generateSAXEventForColumn(ResultSetMetaData rsmd,
ResultSet rs,
int columnIndex)
throws SAXException, SQLException {
String columnValue = rs.getString(columnIndex);
if (columnValue == null) {
return;
}
String columnMarker
= getColumnMarker(rsmd.getColumnLabel(columnIndex));
char[] columnValueChars = columnValue.toCharArray();
_documentHandler.startElement(columnMarker,
_stockEmptyAttributeList);
_documentHandler.characters(columnValueChars,
0, columnValueChars.length);
_documentHandler.endElement(columnMarker);
}
/**
* Get the marker for indicating the start and end of the document.
* By default, it is same as the name of the table. Override this
* to use custom marker.
*
* @param tableName the name of table name being parsed
* @return the marker desired
*/
protected String getTableMarker(String tableName) {
return tableName;
}
/**
* Get the marker for indicating the start and end of a row.
* By default it is "row". Override this to use custome marker.
*
* @return the marker desired
*/
protected String getRowMarker() {
return "row";
}
/**
* Get the marker for indicating the start and end of a column.
* By default it is same as the name of column.
* Override this to use custome marker.
*
* @param columnName a value of type 'String'
* @return a value of type 'String'
*/
protected String getColumnMarker(String columnName) {
return columnName;
}
/**
* Get the select query that will be used to obtain the result
* set for parsing. By default it is "select * from ".
* Override this to allow database-level filtering.
*
* @param tableName the name of table name being parsed
* @return query string
*/
protected String getSelectorSQLStatement(String tableName) {
return "select * from " + tableName;
}
}