The sample CSV ODA driver extension
The BIRT data engine supports the Eclipse DTP ODA framework. The DTP ODA framework supports creating an extension that can plug any external data source into BIRT Report Engine.
The DTP ODA API specifies the interfaces for a run-time driver. The BIRT data engine uses the data source and data set definitions in a report design to access the ODA run-time driver to execute a query and retrieve data.
The DTP ODA interfaces are similar to JDBC interfaces, having extensions that support retrieving data from non-RDBMS sources. An extended ODA driver can implement these interfaces to wrap the API for another data source, such as a CSV file, to retrieve a result set containing data rows.
The CSV ODA driver extension described in this section is a simplified example that illustrates how to create an ODA plug-in using the Eclipse PDE. The package for the CSV ODA extension example, org.eclipse.birt.report.data.oda.csv, uses the following classes to implement the ODA plug-in interfaces:
*CSVFileDriver
Implements the IDriver interface. Instantiates the connection object for the CSV ODA driver and sets up the log configuration and application context.
*Connection
Implements the IConnection interface. Opens and closes the connection to the CSV file and instantiates the IQuery object.
*Query
Implements the IQuery interface. Handles the processing that performs the following operations:
*Sets up the java.io.File object, containing the file and path names
*Fetches the data rows from the data file, using the internal class, CSVBufferReader
*Trims the column data, removing extraneous characters such as commas and quotes
*Prepares the result set metadata, containing the table and column names
*ResultSet
Implements the IResultSet interface. Handles the processing that transforms the String value for a column to the specified data type.
*ResultSetMetaData
Implements the IResultSetMetaData interface. Describes the metadata for each column in the result set.
*DataSetMetaData
Implements the IDataSetMetaData interface. Describes the features and capabilities of the data set.
*Messages
Defines the exception messages for the CSV ODA driver.
*CommonConstant
Defines the constants used in the package, such as the driver name, ODA version, query keywords, and delimiters.
CSVFileDriver class
The CSVFileDriver class instantiates the connection object for the CSV ODA driver by calling the getConnection( ) method, as shown in Listing 25‑2.
Listing 25‑2 The getConnection( ) method
public IConnection getConnection( String dataSourceType )
throws OdaException
{
return new Connection( );
}
Connection class
The Connection class opens and closes the connection to the CSV file and calls the newQuery( ) method to instantiate the Query object. Listing 25‑3 shows the newQuery( ) method.
Listing 25‑3 The newQuery( ) method
public IQuery newQuery( String dataSetType ) throws OdaException
{
if( !isOpen( ) )
throw new OdaException( Messages.getString(
"common_CONNECTION_HAS_NOT_OPENED" ) );
return new Query( this.homeDir, this);
}
Query class
The Query class constructor sets up a java.io.File object, containing the file and path names. The constructor supports an application submitting the home directory parameter, homeDir, as a file name as well as a path. Query( ) configures the data source property based on the value of the HOME property specified in the report design, as shown in Listing 25‑4.
Listing 25‑4 The Query class constructor
Query ( String homeDir, IConnection host ) throws OdaException
{
if ( homeDir == null || host == null )
throw new OdaException(Messages.getString
( "Common.ARGUMENT_CANNOT_BE_NULL" ));
File file = new File(homeDir);
if (file.isDirectory( )
{
this.homeDirectory = homeDir;
}
else if (file.isFile( )
{
this.homeDirectory = file.getParent( );
}
this.connection = host;
}
The Query class prepares and executes a query, then retrieves the data. Query implements the following additional methods:
*prepare( ) performs the following operations:
*Generates query and column information by calling splitQueryText( )
*Validates the connection by calling validateOpenConnection( )
*Formats the query String, eliminating redundant spaces and converting all keywords to uppercase, by calling formatQueryText( )
*Validates the query by calling validateQueryText( )
*Prepares the metadata required for the execution of the query and retrieval of the query results by calling prepareMetaData( )
Listing 25‑5 shows the prepare( ) method.
Listing 25‑5 The prepare( ) method
public void prepare( String queryText ) throws OdaException
{
if ( queryText != null )
{
String query = splitQueryText(queryText)[0] ;
String colInfo = splitQueryText( queryText )[1];
validateOpenConnection( );
String formattedQuery = formatQueryText( query );
validateQueryText( formattedQuery );
prepareMetaData( formattedQuery, colInfo );
}
else
throw new OdaException( Messages.getString(
"common_NULL_QUERY_TEXT" ) );
}
*prepareMetaData( ) acquires the following metadata:
*Table name
*Actual column names read from data file
*Query column names
*Query data types
prepareMetaData( ) then instantiates and configures the ResultSetMetaData object. Listing 25‑6 shows the prepareMetaData( ) method.
Listing 25‑6 The prepareMetaData( ) method
private void prepareMetaData( String query,
String savedSelectedColInfo ) throws OdaException
{
String[ ] queryFragments = parsePreparedQueryText( query );
String tableName = queryFragments[2];
String[ ] allColumnNames =
discoverActualColumnMetaData( tableName,NAME_LITERAL );
String[ ] allColumnTypes =
createTempColumnTypes( allColumnNames.length );
String[ ] queryColumnNames = null;
String[ ] queryColumnTypes = null;
String[ ] queryColumnLables = null;
 
queryColumnNames = allColumnNames;
queryColumnTypes = allColumnTypes;
queryColumnLabels = allColumnNames;
 
this.resultSetMetaData = new ResultSetMetaData(
queryColumnNames, queryColumnTypes, queryColumnLabels );
this.currentTableName = tableName;
}
*executeQuery( ) performs the following operations:
*Fetches the data from the file to a Vector object
*Transfers the data from the Vector to a two-dimensional String array
*Returns the data rows and metadata in a single ResultSet object
Listing 25‑7 shows the executeQuery( ) method.
Listing 25‑7 The executeQuery( ) method
public IResultSet executeQuery( ) throws OdaException
{
Vector v = fetchQueriedDataFromFileToVector( );
String[ ][ ] rowSet =
copyDataFromVectorToTwoDimensionArray( v );
return new ResultSet( rowSet, this.resultSetMetaData );
}
*The internal class, CSVBufferedReader, fetches the data rows from the data file. Listing 25‑8 shows the readLine( ) method.
Listing 25‑8 The readLine( ) method
public String readLine( ) throws IOException
{
if ( isLastCharBuff( ) && needRefillCharBuff( ) )
{
return null;
}
 
if ( needRefillCharBuff( ) )
{
charBuffer = newACharBuff( );
int close = reader.read( charBuffer );
if ( close == -1 )
{
return null;
}
if ( close != CHARBUFFSIZE )
{
this.eofInPosition = close;
}
this.startingPosition = 0;
}
 
String candidate = "";
int stopIn = CHARBUFFSIZE;
if ( isLastCharBuff( ) )
{
stopIn = this.eofInPosition;
}
 
for ( int i = this.startingPosition; i < stopIn; i++ )
{
if ( this.charBuffer[i] == '\n' )
{
return readALine( candidate, stopIn, i );
}
}
 
if ( isLastCharBuff( ) )
{
return readLastLine( candidate );
}
return readExtraContentOfALine( candidate );
}
ResultSet class
The ResultSet class performs the following operations:
*Provides the cursor processing that fetches forward into the buffered result set rows
*Transforms the String value for a column to the specified data type
ResultSet implements the following methods:
*ResultSet( ), the constructor, sets up a two-dimensional array that contains the table data and metadata, as shown in Listing 25‑9.
Listing 25‑9 The ResultSet( ) constructor
ResultSet( String[ ][ ] sData, IResultSetMetaData rsmd )
{
this.sourceData = sData;
this.resultSetMetaData = rsmd;
}
*getRow( ) returns the cursor, indicating the position of the row in the result set, as shown in Listing 25‑10.
Listing 25‑10 The getRow( ) method
public int getRow( ) throws OdaException
{
validateCursorState( );
return this.cursor;
}
*next( ) increments the cursor to point to the next row, as shown in Listing 25‑11.
Listing 25‑11 The next( ) method
public boolean next( ) throws OdaException
{
if ( (this.maxRows <= 0 ? false : cursor >= this.maxRows - 1)
|| cursor >= this.sourceData.length - 1 )
{
cursor = CURSOR_INITIAL_VALUE;
return false;
}
cursor++;
return true;
}
*getString( ) returns the value for a column in the row at the column position specified in the result set, as shown in Listing 25‑12.
Listing 25‑12 The getString( ) method
public String getString( int index ) throws OdaException
{
validateCursorState( );
String result = sourceData[cursor][index - 1];
if ( result.length( ) == 0 )
{
result = null;
}
this.wasNull = result == null ? true : false;
return result;
}
ResultSetMetaData class
The ResultSetMetaData class describes the metadata for a column in the result set, including the following information:
*Column count in the result set
*Display length
*Label
*Name
*Data type
*Precision
*Scale
*Permits null
getColumnName( ) returns the column name for a column at the row, column position specified in the result set, as shown in Listing 25‑13.
Listing 25‑13 The getColumnName( ) method
public String getColumnName( int index ) throws OdaException
{
if ( index > getColumnCount( ) || index < 1 )
throw new OdaException( Messages.getString(
"resultSetMetaData_INVALID_COLUMN_INDEX" ) + index );
 
return this.columnNames[index - 1].trim( );
}
DataSetMetaData class
The DataSetMetaData class describes the features and capabilities of the data set, including the following:
*Indicating whether the data set supports multiple result sets
*Providing information about the sort mode for columns
*Returning a reference to the data source connection
getConnection( ) returns a reference to a data source connection, as shown in Listing 25‑14.
Listing 25‑14 The getConnection( ) method
public IConnection getConnection( ) throws OdaException
{
return m_connection;
}
Messages class
The Messages class defines the exception messages for the CSV ODA driver. The text strings are stored in messages.properties to ease national language translation.
getString( ) returns a message from the resource bundle using the key value, as shown in Listing 25‑15.
Listing 25‑15 The getString( ) method
public static String getString(String key) {
try {
return RESOURCE_BUNDLE.getString(key);
}
catch (MissingResourceException e) {
return '!' + key + '!';
}
}
CommonConstants class
The CommonConstants class defines the constants used in the package, such as the driver name, ODA version, query keywords, and delimiters. Listing 25‑16 shows these definitions.
Listing 25‑16 The CommonConstants class
final class CommonConstants
{
public static final String DELIMITER_COMMA = ",";
public static final String DELIMITER_SPACE = " ";
public static final String DELIMITER_DOUBLEQUOTE = "\"";
public static final String KEYWORD_SELECT = "SELECT";
public static final String KEYWORD_FROM = "FROM";
public static final String KEYWORD_AS = "AS";
public static final String KEYWORD_ASTERISK = "*";
public static final String DRIVER_NAME = "ODA CSV FILE DRIVER";
 
public static final int MaxConnections = 0;
public static final int MaxStatements = 0;
public static final String CONN_HOME_DIR_PROP = "HOME";
public static final String CONN_DEFAULT_CHARSET = "UTF-8";
public static final String PRODUCT_VERSION = "3.0";
}