src.com.ibm.as400.vaccess.SQLResultSetData Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jt400-jdk8 Show documentation
Show all versions of jt400-jdk8 Show documentation
The Open Source version of the IBM Toolbox for Java
The newest version!
///////////////////////////////////////////////////////////////////////////////
//
// JTOpen (IBM Toolbox for Java - OSS version)
//
// Filename: SQLResultSetData.java
//
// The source code contained herein is licensed under the IBM Public License
// Version 1.0, which has been approved by the Open Source Initiative.
// Copyright (C) 1997-2000 International Business Machines Corporation and
// others. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////////
package com.ibm.as400.vaccess;
import com.ibm.as400.access.Trace;
import java.io.IOException;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Types;
import java.util.Vector;
/**
The SQLResultSetData class represents the result
set generated by a SQL query using JDBC.
This class handles caching and storing the data fields so they can be
retrieved and displayed by different views.
The data is retrieved from the system when load() is called.
It is up to the user to call close() when the
result set is no longer needed.
Most errors are reported by firing ErrorEvents, rather
than throwing exceptions. Users should listen for ErrorEvents
in order to diagnose and recover from error conditions.
SQLResultSetData objects generate the following events:
- ErrorEvent
- WorkingEvent
**/
// Note that this class throws events from within synchronized blocks,
// which could cause hangs if the handlers of these events do
// operations from a different thread an attempt to access another
// synchronized piece of code.
// At this time this seems to be an acceptable risk, since the
// events thrown are not likely to need enough processing to
// require another thread, and getting having the events thrown
// from outside a sychronized block would be nearly impossible.
// The other option is to have the firing of the events be done
// from another thread, but the overhead of creating another thread
// not only takes resources, but also delays the delivery of the event.
class SQLResultSetData
implements Serializable
{
private static final String copyright = "Copyright (C) 1997-2000 International Business Machines Corporation and others.";
// What this result set represents
private SQLConnection connection_ = null;
private String query_ = null;
// The result set information
transient private Object[][] data_ = null; // the result set data
transient private ResultSet resultSet_ = null; // result set for this data
transient private Statement stmt_ = null;
transient private boolean resourceOpen_ = false;// jdbc resources open
// Column information
transient private int numColumns_ = 0;
transient private ResultSetMetaData resultSetMetaData_ = null; // describes result set columns
// Row information
// Number of rows data_ is bumped by when more space needed. 100 is arbitrary.
private static int ROW_INCREMENT_ = 100;
// Number of rows in the table. Always 0 until allRecordsRead_ = true.
transient private int numRows_ = 0;
// The index of the last record read. Up to this row is valid in data_.
transient private int lastRecordRead_ = -1;
// If all records in resultSet_ have been transferred to data_.
transient private boolean allRecordsRead_ = true;
// Warnings
transient private SQLWarning warnings_;
// Flag for if an error event was sent.
transient private boolean error_;
// Event support.
transient private ErrorEventSupport errorListeners_
= new ErrorEventSupport (this);
transient private WorkingEventSupport workingListeners_
= new WorkingEventSupport (this);
private boolean fireWorkingEvents_ = true;
/**
Constructs a SQLResultSetData object.
**/
public SQLResultSetData ()
{
super();
}
/**
Adds a listener to be notified when an error occurs.
The listener's errorOccurred method will be called.
@param listener The listener.
**/
public void addErrorListener (ErrorListener listener)
{
errorListeners_.addErrorListener (listener);
}
/**
Adds a listener to be notified when work starts and stops
on potentially long-running operations.
@param listener The listener.
**/
public void addWorkingListener (WorkingListener listener)
{
workingListeners_.addWorkingListener (listener);
}
/**
Closes the JDBC resources.
**/
synchronized public void close ()
{
if (resourceOpen_)
{
try
{
resultSet_.close();
}
catch (SQLException e)
{
errorListeners_.fireError(e);
}
}
if (stmt_ != null)
{
try
{
stmt_.close();
}
catch (SQLException e)
{
errorListeners_.fireError(e);
}
}
resourceOpen_ = false;
}
/**
Clears all SQL warnings.
**/
public void clearWarnings ()
{
warnings_ = null;
if (resultSet_ != null)
{
try
{
resultSet_.clearWarnings();
}
catch (SQLException e)
{
errorListeners_.fireError(e);
}
}
}
/**
Returns whether all records have been processed (stored in a local
cache).
@return true if all records have been processed; false otherwise.
**/
synchronized public boolean getAllRecordsProcessed ()
{
return allRecordsRead_;
}
/**
Returns the display size for the column at the specified index.
Indices start at 0.
@param index Index of the column.
@return The display size for the column.
**/
synchronized public int getColumnDisplaySize(int index)
{
if (resultSet_ == null)
{
throw new IndexOutOfBoundsException("index");
}
int result = 0;
try
{
// For most types, we just consult with JDBC. However, for times, dates, and @C1A
// timestamps, JDBC reports the display size for the server format. And these @C1A
// GUIs actually internationalize the Strings before displaying them. So for @C1A
// these types, we need to compute our own display sizes. @C1A
switch(resultSetMetaData_.getColumnType(index+1)) { // @C1A
case Types.TIME: // @C1A
result = DBDateCellRenderer.getDisplaySize(DBDateCellRenderer.FORMAT_TIME); // @C1A
break; // @C1A
case Types.DATE: // @C1A
result = DBDateCellRenderer.getDisplaySize(DBDateCellRenderer.FORMAT_DATE); // @C1A
break; // @C1A
case Types.TIMESTAMP: // @C1A
result = DBDateCellRenderer.getDisplaySize(DBDateCellRenderer.FORMAT_TIMESTAMP);// @C1A
break; // @C1A
default: // @C1A
result = resultSetMetaData_.getColumnDisplaySize(index+1);
} // @C1A
}
catch (SQLException e)
{
errorListeners_.fireError(e);
}
return result;
}
/**
Returns the label for the column at the specified index.
Indices start at 0.
@param index Index of the column.
@return The label for the column.
**/
synchronized public String getColumnLabel(int index)
{
if (resultSet_ == null)
{
throw new IndexOutOfBoundsException("index");
}
try
{
return resultSetMetaData_.getColumnLabel(index+1);
}
catch (SQLException e)
{
errorListeners_.fireError(e);
return "";
}
}
/**
Returns the name for the column at the specified index.
Indices start at 0.
@param index Index of the column.
@return The name for the column.
**/
synchronized public String getColumnName(int index)
{
if (resultSet_ == null)
{
throw new IndexOutOfBoundsException("index");
}
try
{
return resultSetMetaData_.getColumnName(index+1);
}
catch (SQLException e)
{
errorListeners_.fireError(e);
return "";
}
}
/**
Returns the SQL type of the data in this column.
Indices start at 0.
@param index Index of the column.
@return The SQL type of data in this column.
**/
synchronized public int getColumnType(int index)
{
if (resultSet_ == null)
{
throw new IndexOutOfBoundsException("index");
}
try
{
return resultSetMetaData_.getColumnType(index+1);
}
catch (SQLException e)
{
errorListeners_.fireError(e);
return Types.CHAR;
}
}
/**
Returns the SQL connection used when load() is called..
@return The SQL connection.
**/
public SQLConnection getConnection()
{
return connection_;
}
/**
Returns the index of the last record which had been processed (tored
in the local cache). -1 indicates no records have been
processed.
@return The index of the last record processed.
**/
synchronized public int getLastRecordProcessed ()
{
return lastRecordRead_;
}
/**
Returns the number of fields in this result set.
If an error occurs, 0 is returned.
@return The number of fields in this result set.
**/
synchronized public int getNumberOfColumns()
{
return numColumns_;
}
/**
Returns the number of rows in the result set.
@return The number of rows in the result set.
**/
synchronized public int getNumberOfRows()
{
if (!allRecordsRead_)
{
readAllRecords();
}
return numRows_;
}
/**
Returns the SQL query used to get the result set when load()
is called.
@return The SQL query.
**/
public String getQuery ()
{
return query_;
}
/**
Returns the value at the specifed column and row.
Indices start at 0.
If an error occurs, an empty string is returned.
@param rowIndex The row index.
@param columnIndex The column index.
@return The value at the specified column and row.
**/
synchronized public Object getValueAt (int rowIndex,
int columnIndex)
{
if (resultSet_ == null)
{
throw new IndexOutOfBoundsException("rowIndex");
}
// If we haven't yet transferred this row from the
// result set to the table cache, do so.
if (lastRecordRead_ < rowIndex)
{
readMoreRecords(rowIndex - lastRecordRead_);
}
if (lastRecordRead_ < rowIndex)
{
throw new IndexOutOfBoundsException("rowIndex");
}
// return the value
return data_[rowIndex][columnIndex];
}
/**
Returns the warnings generated by the SQL connection, statement, and
result set.
Warnings are cleared when load or clearWarnings
is called.
The warnings from the result set will be
linked to the end of any statement warnings, which in turn are linked
to the end of any connection warnings.
@return The warnings generated by the connection, statement, and
result set, or null if none.
**/
synchronized public SQLWarning getWarnings ()
{
SQLWarning result = null;
// If no result set, we are not in a state to have warnings.
if (resultSet_ != null)
{
// get result set warnings
SQLWarning rs_warnings = null;
try
{
rs_warnings = resultSet_.getWarnings();
}
catch (SQLException e)
{
// pass on error, but continue
errorListeners_.fireError(e);
}
// If connection or statement warnings...
if (warnings_ != null)
{
if (rs_warnings != null)
{
// We have both connection and statement warnings.
// Find the last warning in the connection chain.
SQLWarning last = warnings_;
SQLWarning next;
while ((next = last.getNextWarning()) != null)
{
last = next;
}
// Add statement warnings to the end of connection chain.
last.setNextWarning(rs_warnings);
}
result = warnings_;
}
else
{
// otherwise, just return result set warnings
result = rs_warnings;
}
}
return result;
}
/**
Loads the table based on the state of the system. This causes the
query to be run.
**/
synchronized public void load ()
{
Trace.log(Trace.DIAGNOSTIC, "Doing data load.");
if (connection_ == null || query_ == null)
{
Trace.log(Trace.DIAGNOSTIC, "Data not set for load, exiting.");
return;
}
if (fireWorkingEvents_)
workingListeners_.fireStartWorking ();
// cleanup old data
if (resourceOpen_)
{
try
{
resultSet_.close();
// leave statement open
}
catch (SQLException e)
{
errorListeners_.fireError(e);
}
resourceOpen_ = false;
}
// Set back fields in case there is an error.
resultSetMetaData_ = null;
numColumns_ = 0;
lastRecordRead_ = -1;
allRecordsRead_ = true;
data_ = null;
numRows_ = 0;
// get data
try
{
// Create statement
if (stmt_ == null)
{
Connection conn = connection_.getConnection();
stmt_ = conn.createStatement();
// Save warnings from connection.
try
{
warnings_ = conn.getWarnings();
}
catch (SQLException e)
{ // pass on errors but continue
errorListeners_.fireError(e);
}
}
// Get result set
resultSet_ = stmt_.executeQuery(query_);
resourceOpen_ = true;
// Save warnings from statement.
try
{
SQLWarning s_warnings = stmt_.getWarnings();
if (s_warnings != null)
{
if (warnings_ == null)
{
// statement warnings but no connection warnings
warnings_ = s_warnings;
}
else
{
// We have both connection and statement warnings.
// Find the last warning in the connection chain.
SQLWarning last = warnings_;
SQLWarning next;
while ((next = last.getNextWarning()) != null)
{
last = next;
}
// Add statement warnings to the end of connection chain.
last.setNextWarning(s_warnings);
}
}
}
catch (SQLException e)
{ // pass on errors but continue
errorListeners_.fireError(e);
}
// get info about the columns returned
resultSetMetaData_ = resultSet_.getMetaData();
numColumns_ = resultSetMetaData_.getColumnCount();
// Move cursor to first record in result set.
// Note that if we can't get to first record, we scrap
// everything and fail.
if (!resultSet_.next())
{
// no records
try
{
resultSet_.close();
}
catch(SQLException e)
{
// pass on error, continue
errorListeners_.fireError(e);
}
}
else // there are records, but we don't know how many
{
allRecordsRead_ = false;
// Create new array to hold table values.
data_ = new Object[ROW_INCREMENT_][numColumns_];
}
}
catch (SQLException e)
{
// In case of error, set fields to init state
resultSet_ = null;
resultSetMetaData_ = null;
numColumns_ = 0;
errorListeners_.fireError(e);
error_ = true;
}
// Note we don't process any of the result set until the
// values are asked for.
if (fireWorkingEvents_)
workingListeners_.fireStopWorking ();
}
/**
Processes records in the result set, moving the data into local cache
so they can be retrieved via getValueAt.
**/
synchronized public void readAllRecords()
{
Trace.log(Trace.DIAGNOSTIC, "Reading all rows.");
workingListeners_.fireStartWorking ();
error_ = false;
while (!allRecordsRead_ && !error_)
{
fireWorkingEvents_ = false;
readMoreRecords(ROW_INCREMENT_);
fireWorkingEvents_ = true;
}
workingListeners_.fireStopWorking ();
}
/**
Processes records in the result set, moving the data into local cache
so they can be retrieved via getValueAt.
@param numberToRead The number of records to process.
**/
synchronized public void readMoreRecords(int numberToRead)
{
Trace.log(Trace.DIAGNOSTIC, "Reading more rows:", numberToRead);
if (fireWorkingEvents_)
workingListeners_.fireStartWorking ();
error_ = false;
// read only load done & more records to read
if (!allRecordsRead_)
{
// Make sure we have room in data_ for records.
if (lastRecordRead_ + numberToRead >= data_.length)
{
// increase by the greater of our increment or the number
// of rows needed.
int sizeNeeded = lastRecordRead_ + numberToRead + 1;
int increment = ROW_INCREMENT_>sizeNeeded?ROW_INCREMENT_:sizeNeeded;
Object[][] newData =
new Object[data_.length + increment][numColumns_];
System.arraycopy(data_, 0, newData, 0, data_.length);
data_ = newData;
}
// Try to avoid sending many errors if they are all the same.
java.util.Vector errors = null;
boolean dupErr;
for (int i=1; i<=numberToRead; ++i)
{
for (int j=1; j<=numColumns_; ++j)
{
try
{
// Note resultSet_ is always valid since load()
// and this loop ensure the next record is valid
// but do not transfer the data into data_.
data_[lastRecordRead_+1][j-1] = resultSet_.getObject(j);
}
catch (SQLException e)
{
// We don't set error_=true, since we are
// continuing to process, and are not aborting.
data_[lastRecordRead_+1][j-1] = null; // set data to null
// Don't send this error if it is a duplicate.
dupErr = false;
if (errors == null) // this is the first error
{
errors = new java.util.Vector();
errors.addElement(e);
}
else // we have had previous errors, look for duplicate
{
SQLException e1;
for (int k=0; k< errors.size(); ++k)
{
e1 = (SQLException)errors.elementAt(k);
if (e.getSQLState().equals(e1.getSQLState()) &&
e.getErrorCode() == e1.getErrorCode() &&
e.getMessage().equals(e1.getMessage()))
{
dupErr = true;
Trace.log(Trace.DIAGNOSTIC,
"Duplicate error getting column value, swallowing error: " +
e.getSQLState() +" : "+ e.getErrorCode() +" : "+ e.getMessage());
break;
}
}
errors.addElement(e);
}
if (!dupErr)
errorListeners_.fireError(e);
}
}
++lastRecordRead_;
// Move cursor to next record.
boolean valid = false;
try
{
valid = resultSet_.next();
}
catch(SQLException e)
{
errorListeners_.fireError(e);
error_ = true;
valid = false;
}
if (!valid)
{
// No more records or error reading next record.
allRecordsRead_ = true;
try
{
resultSet_.close();
resourceOpen_ = false;
// leave statement open
}
catch(SQLException e)
{
// fire error, continue
errorListeners_.fireError(e);
}
numRows_ = lastRecordRead_+1;
break;
}
}
} // end if able to read records
if (fireWorkingEvents_)
workingListeners_.fireStopWorking ();
}
/**
Restore the state of this object from an object input stream.
It is used when deserializing an object.
@param in The input stream of the object being deserialized.
@exception IOException
@exception ClassNotFoundException
**/
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException
{
// Restore the non-static and non-transient fields.
in.defaultReadObject();
// Initialize the transient fields.
data_ = null;
resultSet_ = null;
stmt_ = null;
resourceOpen_ = false;
numColumns_ = 0;
resultSetMetaData_ = null;
numRows_ = 0;
lastRecordRead_ = -1;
allRecordsRead_ = true;
errorListeners_ = new ErrorEventSupport (this);
workingListeners_ = new WorkingEventSupport (this);
}
/**
Removes a listener from being notified when an error occurs.
@param listener The listener.
**/
public void removeErrorListener (ErrorListener listener)
{
errorListeners_.removeErrorListener (listener);
}
/**
Removes a listener from being notified when work starts and stops.
@param listener The listener.
**/
public void removeWorkingListener (WorkingListener listener)
{
workingListeners_.removeWorkingListener (listener);
}
/**
Constructs a SQLResultSetData object.
The new attribute won't go into effect until load().
@param connection The SQL connection.
**/
public void setConnection(SQLConnection connection)
{
if (connection != connection_)
{
connection_ = connection;
if (stmt_ != null)
{
try
{
stmt_.close();
}
catch (SQLException e)
{
errorListeners_.fireError(e);
}
}
stmt_ = null;
warnings_ = null;
}
}
/**
Constructs a SQLResultSetData object.
The new attribute won't go into effect until load().
@param query The SQL query.
**/
public void setQuery(String query)
{
query_ = query;
}
}