// 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; } }