Path: blob/master/src/java.sql.rowset/share/classes/com/sun/rowset/internal/CachedRowSetReader.java
40948 views
/*1* Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.sun.rowset.internal;2627import java.sql.*;28import javax.sql.*;29import javax.naming.*;30import java.io.*;31import java.lang.reflect.*;3233import com.sun.rowset.*;34import javax.sql.rowset.*;35import javax.sql.rowset.spi.*;3637/**38* The facility called by the <code>RIOptimisticProvider</code> object39* internally to read data into it. The calling <code>RowSet</code> object40* must have implemented the <code>RowSetInternal</code> interface41* and have the standard <code>CachedRowSetReader</code> object set as its42* reader.43* <P>44* This implementation always reads all rows of the data source,45* and it assumes that the <code>command</code> property for the caller46* is set with a query that is appropriate for execution by a47* <code>PreparedStatement</code> object.48* <P>49* Typically the <code>SyncFactory</code> manages the <code>RowSetReader</code> and50* the <code>RowSetWriter</code> implementations using <code>SyncProvider</code> objects.51* Standard JDBC RowSet implementations provide an object instance of this52* reader by invoking the <code>SyncProvider.getRowSetReader()</code> method.53*54* @author Jonathan Bruce55* @see javax.sql.rowset.spi.SyncProvider56* @see javax.sql.rowset.spi.SyncFactory57* @see javax.sql.rowset.spi.SyncFactoryException58*/59public class CachedRowSetReader implements RowSetReader, Serializable {6061/**62* The field that keeps track of whether the writer associated with63* this <code>CachedRowSetReader</code> object's rowset has been called since64* the rowset was populated.65* <P>66* When this <code>CachedRowSetReader</code> object reads data into67* its rowset, it sets the field <code>writerCalls</code> to 0.68* When the writer associated with the rowset is called to write69* data back to the underlying data source, its <code>writeData</code>70* method calls the method <code>CachedRowSetReader.reset</code>,71* which increments <code>writerCalls</code> and returns <code>true</code>72* if <code>writerCalls</code> is 1. Thus, <code>writerCalls</code> equals73* 1 after the first call to <code>writeData</code> that occurs74* after the rowset has had data read into it.75*76* @serial77*/78private int writerCalls = 0;7980private boolean userCon = false;8182private int startPosition;8384private JdbcRowSetResourceBundle resBundle;8586public CachedRowSetReader() {87try {88resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();89} catch(IOException ioe) {90throw new RuntimeException(ioe);91}92}939495/**96* Reads data from a data source and populates the given97* <code>RowSet</code> object with that data.98* This method is called by the rowset internally when99* the application invokes the method <code>execute</code>100* to read a new set of rows.101* <P>102* After clearing the rowset of its contents, if any, and setting103* the number of writer calls to <code>0</code>, this reader calls104* its <code>connect</code> method to make105* a connection to the rowset's data source. Depending on which106* of the rowset's properties have been set, the <code>connect</code>107* method will use a <code>DataSource</code> object or the108* <code>DriverManager</code> facility to make a connection to the109* data source.110* <P>111* Once the connection to the data source is made, this reader112* executes the query in the calling <code>CachedRowSet</code> object's113* <code>command</code> property. Then it calls the rowset's114* <code>populate</code> method, which reads data from the115* <code>ResultSet</code> object produced by executing the rowset's116* command. The rowset is then populated with this data.117* <P>118* This method's final act is to close the connection it made, thus119* leaving the rowset disconnected from its data source.120*121* @param caller a <code>RowSet</code> object that has implemented122* the <code>RowSetInternal</code> interface and had123* this <code>CachedRowSetReader</code> object set as124* its reader125* @throws SQLException if there is a database access error, there is a126* problem making the connection, or the command property has not127* been set128*/129public void readData(RowSetInternal caller) throws SQLException130{131Connection con = null;132try {133CachedRowSet crs = (CachedRowSet)caller;134135// Get rid of the current contents of the rowset.136137/**138* Checking added to verify whether page size has been set or not.139* If set then do not close the object as certain parameters need140* to be maintained.141*/142143if(crs.getPageSize() == 0 && crs.size() >0 ) {144// When page size is not set,145// crs.size() will show the total no of rows.146crs.close();147}148149writerCalls = 0;150151// Get a connection. This reader assumes that the necessary152// properties have been set on the caller to let it supply a153// connection.154userCon = false;155156con = this.connect(caller);157158// Check our assumptions.159if (con == null || crs.getCommand() == null)160throw new SQLException(resBundle.handleGetObject("crsreader.connecterr").toString());161162try {163con.setTransactionIsolation(crs.getTransactionIsolation());164} catch (Exception ex) {165;166}167// Use JDBC to read the data.168PreparedStatement pstmt = con.prepareStatement(crs.getCommand());169// Pass any input parameters to JDBC.170171decodeParams(caller.getParams(), pstmt);172try {173pstmt.setMaxRows(crs.getMaxRows());174pstmt.setMaxFieldSize(crs.getMaxFieldSize());175pstmt.setEscapeProcessing(crs.getEscapeProcessing());176pstmt.setQueryTimeout(crs.getQueryTimeout());177} catch (Exception ex) {178/*179* drivers may not support the above - esp. older180* drivers being used by the bridge..181*/182throw new SQLException(ex.getMessage());183}184185if(crs.getCommand().toLowerCase().indexOf("select") != -1) {186// can be (crs.getCommand()).indexOf("select")) == 0187// because we will be getting resultset when188// it may be the case that some false select query with189// select coming in between instead of first.190191// if ((crs.getCommand()).indexOf("?")) does not return -1192// implies a Prepared Statement like query exists.193194ResultSet rs = pstmt.executeQuery();195if(crs.getPageSize() == 0){196crs.populate(rs);197}198else {199/**200* If page size has been set then create a ResultSet object that is scrollable using a201* PreparedStatement handle.Also call the populate(ResultSet,int) function to populate202* a page of data as specified by the page size.203*/204pstmt = con.prepareStatement(crs.getCommand(),ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);205decodeParams(caller.getParams(), pstmt);206try {207pstmt.setMaxRows(crs.getMaxRows());208pstmt.setMaxFieldSize(crs.getMaxFieldSize());209pstmt.setEscapeProcessing(crs.getEscapeProcessing());210pstmt.setQueryTimeout(crs.getQueryTimeout());211} catch (Exception ex) {212/*213* drivers may not support the above - esp. older214* drivers being used by the bridge..215*/216throw new SQLException(ex.getMessage());217}218rs = pstmt.executeQuery();219crs.populate(rs,startPosition);220}221rs.close();222} else {223pstmt.executeUpdate();224}225226// Get the data.227pstmt.close();228try {229con.commit();230} catch (SQLException ex) {231;232}233// only close connections we created...234if (getCloseConnection() == true)235con.close();236}237catch (SQLException ex) {238// Throw an exception if reading fails for any reason.239throw ex;240} finally {241try {242// only close connections we created...243if (con != null && getCloseConnection() == true) {244try {245if (!con.getAutoCommit()) {246con.rollback();247}248} catch (Exception dummy) {249/*250* not an error condition, we're closing anyway, but251* we'd like to clean up any locks if we can since252* it is not clear the connection pool will clean253* these connections in a timely manner254*/255}256con.close();257con = null;258}259} catch (SQLException e) {260// will get exception if something already went wrong, but don't261// override that exception with this one262}263}264}265266/**267* Checks to see if the writer associated with this reader needs268* to reset its state. The writer will need to initialize its state269* if new contents have been read since the writer was last called.270* This method is called by the writer that was registered with271* this reader when components were being wired together.272*273* @return <code>true</code> if writer associated with this reader needs274* to reset the values of its fields; <code>false</code> otherwise275* @throws SQLException if an access error occurs276*/277public boolean reset() throws SQLException {278writerCalls++;279return writerCalls == 1;280}281282/**283* Establishes a connection with the data source for the given284* <code>RowSet</code> object. If the rowset's <code>dataSourceName</code>285* property has been set, this method uses the JNDI API to retrieve the286* <code>DataSource</code> object that it can use to make the connection.287* If the url, username, and password properties have been set, this288* method uses the <code>DriverManager.getConnection</code> method to289* make the connection.290* <P>291* This method is used internally by the reader and writer associated with292* the calling <code>RowSet</code> object; an application never calls it293* directly.294*295* @param caller a <code>RowSet</code> object that has implemented296* the <code>RowSetInternal</code> interface and had297* this <code>CachedRowSetReader</code> object set as298* its reader299* @return a <code>Connection</code> object that represents a connection300* to the caller's data source301* @throws SQLException if an access error occurs302*/303public Connection connect(RowSetInternal caller) throws SQLException {304305// Get a JDBC connection.306if (caller.getConnection() != null) {307// A connection was passed to execute(), so use it.308// As we are using a connection the user gave us we309// won't close it.310userCon = true;311return caller.getConnection();312}313else if (((RowSet)caller).getDataSourceName() != null) {314// Connect using JNDI.315try {316Context ctx = new InitialContext();317DataSource ds = (DataSource)ctx.lookup318(((RowSet)caller).getDataSourceName());319320// Check for username, password,321// if it exists try getting a Connection handle through them322// else try without these323// else throw SQLException324325if(((RowSet)caller).getUsername() != null) {326return ds.getConnection(((RowSet)caller).getUsername(),327((RowSet)caller).getPassword());328} else {329return ds.getConnection();330}331}332catch (javax.naming.NamingException ex) {333SQLException sqlEx = new SQLException(resBundle.handleGetObject("crsreader.connect").toString());334sqlEx.initCause(ex);335throw sqlEx;336}337} else if (((RowSet)caller).getUrl() != null) {338// Connect using the driver manager.339return DriverManager.getConnection(((RowSet)caller).getUrl(),340((RowSet)caller).getUsername(),341((RowSet)caller).getPassword());342}343else {344return null;345}346}347348/**349* Sets the parameter placeholders350* in the rowset's command (the given <code>PreparedStatement</code>351* object) with the parameters in the given array.352* This method, called internally by the method353* <code>CachedRowSetReader.readData</code>, reads each parameter, and354* based on its type, determines the correct355* <code>PreparedStatement.setXXX</code> method to use for setting356* that parameter.357*358* @param params an array of parameters to be used with the given359* <code>PreparedStatement</code> object360* @param pstmt the <code>PreparedStatement</code> object that is the361* command for the calling rowset and into which362* the given parameters are to be set363* @throws SQLException if an access error occurs364*/365@SuppressWarnings("deprecation")366private void decodeParams(Object[] params,367PreparedStatement pstmt) throws SQLException {368// There is a corresponding decodeParams in JdbcRowSetImpl369// which does the same as this method. This is a design flaw.370// Update the JdbcRowSetImpl.decodeParams when you update371// this method.372373// Adding the same comments to JdbcRowSetImpl.decodeParams.374375int arraySize;376Object[] param = null;377378for (int i=0; i < params.length; i++) {379if (params[i] instanceof Object[]) {380param = (Object[])params[i];381382if (param.length == 2) {383if (param[0] == null) {384pstmt.setNull(i + 1, ((Integer)param[1]).intValue());385continue;386}387388if (param[0] instanceof java.sql.Date ||389param[0] instanceof java.sql.Time ||390param[0] instanceof java.sql.Timestamp) {391System.err.println(resBundle.handleGetObject("crsreader.datedetected").toString());392if (param[1] instanceof java.util.Calendar) {393System.err.println(resBundle.handleGetObject("crsreader.caldetected").toString());394pstmt.setDate(i + 1, (java.sql.Date)param[0],395(java.util.Calendar)param[1]);396continue;397}398else {399throw new SQLException(resBundle.handleGetObject("crsreader.paramtype").toString());400}401}402403if (param[0] instanceof Reader) {404pstmt.setCharacterStream(i + 1, (Reader)param[0],405((Integer)param[1]).intValue());406continue;407}408409/*410* What's left should be setObject(int, Object, scale)411*/412if (param[1] instanceof Integer) {413pstmt.setObject(i + 1, param[0], ((Integer)param[1]).intValue());414continue;415}416417} else if (param.length == 3) {418419if (param[0] == null) {420pstmt.setNull(i + 1, ((Integer)param[1]).intValue(),421(String)param[2]);422continue;423}424425if (param[0] instanceof java.io.InputStream) {426switch (((Integer)param[2]).intValue()) {427case CachedRowSetImpl.UNICODE_STREAM_PARAM:428pstmt.setUnicodeStream(i + 1,429(java.io.InputStream)param[0],430((Integer)param[1]).intValue());431break;432case CachedRowSetImpl.BINARY_STREAM_PARAM:433pstmt.setBinaryStream(i + 1,434(java.io.InputStream)param[0],435((Integer)param[1]).intValue());436break;437case CachedRowSetImpl.ASCII_STREAM_PARAM:438pstmt.setAsciiStream(i + 1,439(java.io.InputStream)param[0],440((Integer)param[1]).intValue());441break;442default:443throw new SQLException(resBundle.handleGetObject("crsreader.paramtype").toString());444}445}446447/*448* no point at looking at the first element now;449* what's left must be the setObject() cases.450*/451if (param[1] instanceof Integer && param[2] instanceof Integer) {452pstmt.setObject(i + 1, param[0], ((Integer)param[1]).intValue(),453((Integer)param[2]).intValue());454continue;455}456457throw new SQLException(resBundle.handleGetObject("crsreader.paramtype").toString());458459} else {460// common case - this catches all SQL92 types461pstmt.setObject(i + 1, params[i]);462continue;463}464} else {465// Try to get all the params to be set here466pstmt.setObject(i + 1, params[i]);467468}469}470}471472/**473* Assists in determining whether the current connection was created by this474* CachedRowSet to ensure incorrect connections are not prematurely terminated.475*476* @return a boolean giving the status of whether the connection has been closed.477*/478protected boolean getCloseConnection() {479if (userCon == true)480return false;481482return true;483}484485/**486* This sets the start position in the ResultSet from where to begin. This is487* called by the Reader in the CachedRowSetImpl to set the position on the page488* to begin populating from.489* @param pos integer indicating the position in the <code>ResultSet</code> to begin490* populating from.491*/492public void setStartPosition(int pos){493startPosition = pos;494}495496private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {497// Default state initialization happens here498ois.defaultReadObject();499// Initialization of Res Bundle happens here .500try {501resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();502} catch(IOException ioe) {503throw new RuntimeException(ioe);504}505506}507508static final long serialVersionUID =5049738185801363801L;509}510511512