/*
 * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.rowset.internal;

import java.sql.*;
import javax.sql.*;
import javax.naming.*;
import java.io.*;
import java.lang.reflect.*;

import com.sun.rowset.*;
import javax.sql.rowset.*;
import javax.sql.rowset.spi.*;

The facility called by the RIOptimisticProvider object internally to read data into it. The calling RowSet object must have implemented the RowSetInternal interface and have the standard CachedRowSetReader object set as its reader.

This implementation always reads all rows of the data source, and it assumes that the command property for the caller is set with a query that is appropriate for execution by a PreparedStatement object.

Typically the SyncFactory manages the RowSetReader and the RowSetWriter implementations using SyncProvider objects. Standard JDBC RowSet implementations provide an object instance of this reader by invoking the SyncProvider.getRowSetReader() method.

Author:Jonathan Bruce
See Also:
/** * The facility called by the <code>RIOptimisticProvider</code> object * internally to read data into it. The calling <code>RowSet</code> object * must have implemented the <code>RowSetInternal</code> interface * and have the standard <code>CachedRowSetReader</code> object set as its * reader. * <P> * This implementation always reads all rows of the data source, * and it assumes that the <code>command</code> property for the caller * is set with a query that is appropriate for execution by a * <code>PreparedStatement</code> object. * <P> * Typically the <code>SyncFactory</code> manages the <code>RowSetReader</code> and * the <code>RowSetWriter</code> implementations using <code>SyncProvider</code> objects. * Standard JDBC RowSet implementations provide an object instance of this * reader by invoking the <code>SyncProvider.getRowSetReader()</code> method. * * @author Jonathan Bruce * @see javax.sql.rowset.spi.SyncProvider * @see javax.sql.rowset.spi.SyncFactory * @see javax.sql.rowset.spi.SyncFactoryException */
public class CachedRowSetReader implements RowSetReader, Serializable {
The field that keeps track of whether the writer associated with this CachedRowSetReader object's rowset has been called since the rowset was populated.

When this CachedRowSetReader object reads data into its rowset, it sets the field writerCalls to 0. When the writer associated with the rowset is called to write data back to the underlying data source, its writeData method calls the method CachedRowSetReader.reset, which increments writerCalls and returns true if writerCalls is 1. Thus, writerCalls equals 1 after the first call to writeData that occurs after the rowset has had data read into it.

@serial
/** * The field that keeps track of whether the writer associated with * this <code>CachedRowSetReader</code> object's rowset has been called since * the rowset was populated. * <P> * When this <code>CachedRowSetReader</code> object reads data into * its rowset, it sets the field <code>writerCalls</code> to 0. * When the writer associated with the rowset is called to write * data back to the underlying data source, its <code>writeData</code> * method calls the method <code>CachedRowSetReader.reset</code>, * which increments <code>writerCalls</code> and returns <code>true</code> * if <code>writerCalls</code> is 1. Thus, <code>writerCalls</code> equals * 1 after the first call to <code>writeData</code> that occurs * after the rowset has had data read into it. * * @serial */
private int writerCalls = 0; private boolean userCon = false; private int startPosition; private JdbcRowSetResourceBundle resBundle; public CachedRowSetReader() { try { resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle(); } catch(IOException ioe) { throw new RuntimeException(ioe); } }
Reads data from a data source and populates the given RowSet object with that data. This method is called by the rowset internally when the application invokes the method execute to read a new set of rows.

After clearing the rowset of its contents, if any, and setting the number of writer calls to 0, this reader calls its connect method to make a connection to the rowset's data source. Depending on which of the rowset's properties have been set, the connect method will use a DataSource object or the DriverManager facility to make a connection to the data source.

Once the connection to the data source is made, this reader executes the query in the calling CachedRowSet object's command property. Then it calls the rowset's populate method, which reads data from the ResultSet object produced by executing the rowset's command. The rowset is then populated with this data.

This method's final act is to close the connection it made, thus leaving the rowset disconnected from its data source.

Params:
  • caller – a RowSet object that has implemented the RowSetInternal interface and had this CachedRowSetReader object set as its reader
Throws:
  • SQLException – if there is a database access error, there is a problem making the connection, or the command property has not been set
/** * Reads data from a data source and populates the given * <code>RowSet</code> object with that data. * This method is called by the rowset internally when * the application invokes the method <code>execute</code> * to read a new set of rows. * <P> * After clearing the rowset of its contents, if any, and setting * the number of writer calls to <code>0</code>, this reader calls * its <code>connect</code> method to make * a connection to the rowset's data source. Depending on which * of the rowset's properties have been set, the <code>connect</code> * method will use a <code>DataSource</code> object or the * <code>DriverManager</code> facility to make a connection to the * data source. * <P> * Once the connection to the data source is made, this reader * executes the query in the calling <code>CachedRowSet</code> object's * <code>command</code> property. Then it calls the rowset's * <code>populate</code> method, which reads data from the * <code>ResultSet</code> object produced by executing the rowset's * command. The rowset is then populated with this data. * <P> * This method's final act is to close the connection it made, thus * leaving the rowset disconnected from its data source. * * @param caller a <code>RowSet</code> object that has implemented * the <code>RowSetInternal</code> interface and had * this <code>CachedRowSetReader</code> object set as * its reader * @throws SQLException if there is a database access error, there is a * problem making the connection, or the command property has not * been set */
public void readData(RowSetInternal caller) throws SQLException { Connection con = null; try { CachedRowSet crs = (CachedRowSet)caller; // Get rid of the current contents of the rowset. /** * Checking added to verify whether page size has been set or not. * If set then do not close the object as certain parameters need * to be maintained. */ if(crs.getPageSize() == 0 && crs.size() >0 ) { // When page size is not set, // crs.size() will show the total no of rows. crs.close(); } writerCalls = 0; // Get a connection. This reader assumes that the necessary // properties have been set on the caller to let it supply a // connection. userCon = false; con = this.connect(caller); // Check our assumptions. if (con == null || crs.getCommand() == null) throw new SQLException(resBundle.handleGetObject("crsreader.connecterr").toString()); try { con.setTransactionIsolation(crs.getTransactionIsolation()); } catch (Exception ex) { ; } // Use JDBC to read the data. PreparedStatement pstmt = con.prepareStatement(crs.getCommand()); // Pass any input parameters to JDBC. decodeParams(caller.getParams(), pstmt); try { pstmt.setMaxRows(crs.getMaxRows()); pstmt.setMaxFieldSize(crs.getMaxFieldSize()); pstmt.setEscapeProcessing(crs.getEscapeProcessing()); pstmt.setQueryTimeout(crs.getQueryTimeout()); } catch (Exception ex) { /* * drivers may not support the above - esp. older * drivers being used by the bridge.. */ throw new SQLException(ex.getMessage()); } if(crs.getCommand().toLowerCase().indexOf("select") != -1) { // can be (crs.getCommand()).indexOf("select")) == 0 // because we will be getting resultset when // it may be the case that some false select query with // select coming in between instead of first. // if ((crs.getCommand()).indexOf("?")) does not return -1 // implies a Prepared Statement like query exists. ResultSet rs = pstmt.executeQuery(); if(crs.getPageSize() == 0){ crs.populate(rs); } else { /** * If page size has been set then create a ResultSet object that is scrollable using a * PreparedStatement handle.Also call the populate(ResultSet,int) function to populate * a page of data as specified by the page size. */ pstmt = con.prepareStatement(crs.getCommand(),ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE); decodeParams(caller.getParams(), pstmt); try { pstmt.setMaxRows(crs.getMaxRows()); pstmt.setMaxFieldSize(crs.getMaxFieldSize()); pstmt.setEscapeProcessing(crs.getEscapeProcessing()); pstmt.setQueryTimeout(crs.getQueryTimeout()); } catch (Exception ex) { /* * drivers may not support the above - esp. older * drivers being used by the bridge.. */ throw new SQLException(ex.getMessage()); } rs = pstmt.executeQuery(); crs.populate(rs,startPosition); } rs.close(); } else { pstmt.executeUpdate(); } // Get the data. pstmt.close(); try { con.commit(); } catch (SQLException ex) { ; } // only close connections we created... if (getCloseConnection() == true) con.close(); } catch (SQLException ex) { // Throw an exception if reading fails for any reason. throw ex; } finally { try { // only close connections we created... if (con != null && getCloseConnection() == true) { try { if (!con.getAutoCommit()) { con.rollback(); } } catch (Exception dummy) { /* * not an error condition, we're closing anyway, but * we'd like to clean up any locks if we can since * it is not clear the connection pool will clean * these connections in a timely manner */ } con.close(); con = null; } } catch (SQLException e) { // will get exception if something already went wrong, but don't // override that exception with this one } } }
Checks to see if the writer associated with this reader needs to reset its state. The writer will need to initialize its state if new contents have been read since the writer was last called. This method is called by the writer that was registered with this reader when components were being wired together.
Throws:
Returns:true if writer associated with this reader needs to reset the values of its fields; false otherwise
/** * Checks to see if the writer associated with this reader needs * to reset its state. The writer will need to initialize its state * if new contents have been read since the writer was last called. * This method is called by the writer that was registered with * this reader when components were being wired together. * * @return <code>true</code> if writer associated with this reader needs * to reset the values of its fields; <code>false</code> otherwise * @throws SQLException if an access error occurs */
public boolean reset() throws SQLException { writerCalls++; return writerCalls == 1; }
Establishes a connection with the data source for the given RowSet object. If the rowset's dataSourceName property has been set, this method uses the JNDI API to retrieve the DataSource object that it can use to make the connection. If the url, username, and password properties have been set, this method uses the DriverManager.getConnection method to make the connection.

This method is used internally by the reader and writer associated with the calling RowSet object; an application never calls it directly.

Params:
  • caller – a RowSet object that has implemented the RowSetInternal interface and had this CachedRowSetReader object set as its reader
Throws:
Returns:a Connection object that represents a connection to the caller's data source
/** * Establishes a connection with the data source for the given * <code>RowSet</code> object. If the rowset's <code>dataSourceName</code> * property has been set, this method uses the JNDI API to retrieve the * <code>DataSource</code> object that it can use to make the connection. * If the url, username, and password properties have been set, this * method uses the <code>DriverManager.getConnection</code> method to * make the connection. * <P> * This method is used internally by the reader and writer associated with * the calling <code>RowSet</code> object; an application never calls it * directly. * * @param caller a <code>RowSet</code> object that has implemented * the <code>RowSetInternal</code> interface and had * this <code>CachedRowSetReader</code> object set as * its reader * @return a <code>Connection</code> object that represents a connection * to the caller's data source * @throws SQLException if an access error occurs */
public Connection connect(RowSetInternal caller) throws SQLException { // Get a JDBC connection. if (caller.getConnection() != null) { // A connection was passed to execute(), so use it. // As we are using a connection the user gave us we // won't close it. userCon = true; return caller.getConnection(); } else if (((RowSet)caller).getDataSourceName() != null) { // Connect using JNDI. try { Context ctx = new InitialContext(); DataSource ds = (DataSource)ctx.lookup (((RowSet)caller).getDataSourceName()); // Check for username, password, // if it exists try getting a Connection handle through them // else try without these // else throw SQLException if(((RowSet)caller).getUsername() != null) { return ds.getConnection(((RowSet)caller).getUsername(), ((RowSet)caller).getPassword()); } else { return ds.getConnection(); } } catch (javax.naming.NamingException ex) { SQLException sqlEx = new SQLException(resBundle.handleGetObject("crsreader.connect").toString()); sqlEx.initCause(ex); throw sqlEx; } } else if (((RowSet)caller).getUrl() != null) { // Connect using the driver manager. return DriverManager.getConnection(((RowSet)caller).getUrl(), ((RowSet)caller).getUsername(), ((RowSet)caller).getPassword()); } else { return null; } }
Sets the parameter placeholders in the rowset's command (the given PreparedStatement object) with the parameters in the given array. This method, called internally by the method CachedRowSetReader.readData, reads each parameter, and based on its type, determines the correct PreparedStatement.setXXX method to use for setting that parameter.
Params:
  • params – an array of parameters to be used with the given PreparedStatement object
  • pstmt – the PreparedStatement object that is the command for the calling rowset and into which the given parameters are to be set
Throws:
/** * Sets the parameter placeholders * in the rowset's command (the given <code>PreparedStatement</code> * object) with the parameters in the given array. * This method, called internally by the method * <code>CachedRowSetReader.readData</code>, reads each parameter, and * based on its type, determines the correct * <code>PreparedStatement.setXXX</code> method to use for setting * that parameter. * * @param params an array of parameters to be used with the given * <code>PreparedStatement</code> object * @param pstmt the <code>PreparedStatement</code> object that is the * command for the calling rowset and into which * the given parameters are to be set * @throws SQLException if an access error occurs */
@SuppressWarnings("deprecation") private void decodeParams(Object[] params, PreparedStatement pstmt) throws SQLException { // There is a corresponding decodeParams in JdbcRowSetImpl // which does the same as this method. This is a design flaw. // Update the JdbcRowSetImpl.decodeParams when you update // this method. // Adding the same comments to JdbcRowSetImpl.decodeParams. int arraySize; Object[] param = null; for (int i=0; i < params.length; i++) { if (params[i] instanceof Object[]) { param = (Object[])params[i]; if (param.length == 2) { if (param[0] == null) { pstmt.setNull(i + 1, ((Integer)param[1]).intValue()); continue; } if (param[0] instanceof java.sql.Date || param[0] instanceof java.sql.Time || param[0] instanceof java.sql.Timestamp) { System.err.println(resBundle.handleGetObject("crsreader.datedetected").toString()); if (param[1] instanceof java.util.Calendar) { System.err.println(resBundle.handleGetObject("crsreader.caldetected").toString()); pstmt.setDate(i + 1, (java.sql.Date)param[0], (java.util.Calendar)param[1]); continue; } else { throw new SQLException(resBundle.handleGetObject("crsreader.paramtype").toString()); } } if (param[0] instanceof Reader) { pstmt.setCharacterStream(i + 1, (Reader)param[0], ((Integer)param[1]).intValue()); continue; } /* * What's left should be setObject(int, Object, scale) */ if (param[1] instanceof Integer) { pstmt.setObject(i + 1, param[0], ((Integer)param[1]).intValue()); continue; } } else if (param.length == 3) { if (param[0] == null) { pstmt.setNull(i + 1, ((Integer)param[1]).intValue(), (String)param[2]); continue; } if (param[0] instanceof java.io.InputStream) { switch (((Integer)param[2]).intValue()) { case CachedRowSetImpl.UNICODE_STREAM_PARAM: pstmt.setUnicodeStream(i + 1, (java.io.InputStream)param[0], ((Integer)param[1]).intValue()); break; case CachedRowSetImpl.BINARY_STREAM_PARAM: pstmt.setBinaryStream(i + 1, (java.io.InputStream)param[0], ((Integer)param[1]).intValue()); break; case CachedRowSetImpl.ASCII_STREAM_PARAM: pstmt.setAsciiStream(i + 1, (java.io.InputStream)param[0], ((Integer)param[1]).intValue()); break; default: throw new SQLException(resBundle.handleGetObject("crsreader.paramtype").toString()); } } /* * no point at looking at the first element now; * what's left must be the setObject() cases. */ if (param[1] instanceof Integer && param[2] instanceof Integer) { pstmt.setObject(i + 1, param[0], ((Integer)param[1]).intValue(), ((Integer)param[2]).intValue()); continue; } throw new SQLException(resBundle.handleGetObject("crsreader.paramtype").toString()); } else { // common case - this catches all SQL92 types pstmt.setObject(i + 1, params[i]); continue; } } else { // Try to get all the params to be set here pstmt.setObject(i + 1, params[i]); } } }
Assists in determining whether the current connection was created by this CachedRowSet to ensure incorrect connections are not prematurely terminated.
Returns:a boolean giving the status of whether the connection has been closed.
/** * Assists in determining whether the current connection was created by this * CachedRowSet to ensure incorrect connections are not prematurely terminated. * * @return a boolean giving the status of whether the connection has been closed. */
protected boolean getCloseConnection() { if (userCon == true) return false; return true; }
This sets the start position in the ResultSet from where to begin. This is called by the Reader in the CachedRowSetImpl to set the position on the page to begin populating from.
Params:
  • pos – integer indicating the position in the ResultSet to begin populating from.
/** * This sets the start position in the ResultSet from where to begin. This is * called by the Reader in the CachedRowSetImpl to set the position on the page * to begin populating from. * @param pos integer indicating the position in the <code>ResultSet</code> to begin * populating from. */
public void setStartPosition(int pos){ startPosition = pos; } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // Default state initialization happens here ois.defaultReadObject(); // Initialization of Res Bundle happens here . try { resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle(); } catch(IOException ioe) { throw new RuntimeException(ioe); } } static final long serialVersionUID =5049738185801363801L; }