1 /*
   2  * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.rowset.internal;
  27 
  28 import java.sql.*;
  29 import javax.sql.*;
  30 import javax.naming.*;
  31 import java.io.*;
  32 import java.lang.reflect.*;
  33 
  34 import com.sun.rowset.*;
  35 import javax.sql.rowset.*;
  36 import javax.sql.rowset.spi.*;
  37 
  38 /**
  39  * The facility called by the <code>RIOptimisticProvider</code> object
  40  * internally to read data into it.  The calling <code>RowSet</code> object
  41  * must have implemented the <code>RowSetInternal</code> interface
  42  * and have the standard <code>CachedRowSetReader</code> object set as its
  43  * reader.
  44  * <P>
  45  * This implementation always reads all rows of the data source,
  46  * and it assumes that the <code>command</code> property for the caller
  47  * is set with a query that is appropriate for execution by a
  48  * <code>PreparedStatement</code> object.
  49  * <P>
  50  * Typically the <code>SyncFactory</code> manages the <code>RowSetReader</code> and
  51  * the <code>RowSetWriter</code> implementations using <code>SyncProvider</code> objects.
  52  * Standard JDBC RowSet implementations provide an object instance of this
  53  * reader by invoking the <code>SyncProvider.getRowSetReader()</code> method.
  54  *
  55  * @author Jonathan Bruce
  56  * @see javax.sql.rowset.spi.SyncProvider
  57  * @see javax.sql.rowset.spi.SyncFactory
  58  * @see javax.sql.rowset.spi.SyncFactoryException
  59  */
  60 public class CachedRowSetReader implements RowSetReader, Serializable {
  61 
  62     /**
  63      * The field that keeps track of whether the writer associated with
  64      * this <code>CachedRowSetReader</code> object's rowset has been called since
  65      * the rowset was populated.
  66      * <P>
  67      * When this <code>CachedRowSetReader</code> object reads data into
  68      * its rowset, it sets the field <code>writerCalls</code> to 0.
  69      * When the writer associated with the rowset is called to write
  70      * data back to the underlying data source, its <code>writeData</code>
  71      * method calls the method <code>CachedRowSetReader.reset</code>,
  72      * which increments <code>writerCalls</code> and returns <code>true</code>
  73      * if <code>writerCalls</code> is 1. Thus, <code>writerCalls</code> equals
  74      * 1 after the first call to <code>writeData</code> that occurs
  75      * after the rowset has had data read into it.
  76      *
  77      * @serial
  78      */
  79     private int writerCalls = 0;
  80 
  81     private boolean userCon = false;
  82 
  83     private int startPosition;
  84 
  85     private JdbcRowSetResourceBundle resBundle;
  86 
  87     public CachedRowSetReader() {
  88         try {
  89                 resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();
  90         } catch(IOException ioe) {
  91             throw new RuntimeException(ioe);
  92         }
  93     }
  94 
  95 
  96     /**
  97      * Reads data from a data source and populates the given
  98      * <code>RowSet</code> object with that data.
  99      * This method is called by the rowset internally when
 100      * the application invokes the method <code>execute</code>
 101      * to read a new set of rows.
 102      * <P>
 103      * After clearing the rowset of its contents, if any, and setting
 104      * the number of writer calls to <code>0</code>, this reader calls
 105      * its <code>connect</code> method to make
 106      * a connection to the rowset's data source. Depending on which
 107      * of the rowset's properties have been set, the <code>connect</code>
 108      * method will use a <code>DataSource</code> object or the
 109      * <code>DriverManager</code> facility to make a connection to the
 110      * data source.
 111      * <P>
 112      * Once the connection to the data source is made, this reader
 113      * executes the query in the calling <code>CachedRowSet</code> object's
 114      * <code>command</code> property. Then it calls the rowset's
 115      * <code>populate</code> method, which reads data from the
 116      * <code>ResultSet</code> object produced by executing the rowset's
 117      * command. The rowset is then populated with this data.
 118      * <P>
 119      * This method's final act is to close the connection it made, thus
 120      * leaving the rowset disconnected from its data source.
 121      *
 122      * @param caller a <code>RowSet</code> object that has implemented
 123      *               the <code>RowSetInternal</code> interface and had
 124      *               this <code>CachedRowSetReader</code> object set as
 125      *               its reader
 126      * @throws SQLException if there is a database access error, there is a
 127      *         problem making the connection, or the command property has not
 128      *         been set
 129      */
 130     public void readData(RowSetInternal caller) throws SQLException
 131     {
 132         Connection con = null;
 133         try {
 134             CachedRowSet crs = (CachedRowSet)caller;
 135 
 136             // Get rid of the current contents of the rowset.
 137 
 138             /**
 139              * Checking added to verify whether page size has been set or not.
 140              * If set then do not close the object as certain parameters need
 141              * to be maintained.
 142              */
 143 
 144             if(crs.getPageSize() == 0 && crs.size() >0 ) {
 145                // When page size is not set,
 146                // crs.size() will show the total no of rows.
 147                crs.close();
 148             }
 149 
 150             writerCalls = 0;
 151 
 152             // Get a connection.  This reader assumes that the necessary
 153             // properties have been set on the caller to let it supply a
 154             // connection.
 155             userCon = false;
 156 
 157             con = this.connect(caller);
 158 
 159             // Check our assumptions.
 160             if (con == null || crs.getCommand() == null)
 161                 throw new SQLException(resBundle.handleGetObject("crsreader.connecterr").toString());
 162 
 163             try {
 164                 con.setTransactionIsolation(crs.getTransactionIsolation());
 165             } catch (Exception ex) {
 166                 ;
 167             }
 168             // Use JDBC to read the data.
 169             PreparedStatement pstmt = con.prepareStatement(crs.getCommand());
 170             // Pass any input parameters to JDBC.
 171 
 172             decodeParams(caller.getParams(), pstmt);
 173             try {
 174                 pstmt.setMaxRows(crs.getMaxRows());
 175                 pstmt.setMaxFieldSize(crs.getMaxFieldSize());
 176                 pstmt.setEscapeProcessing(crs.getEscapeProcessing());
 177                 pstmt.setQueryTimeout(crs.getQueryTimeout());
 178             } catch (Exception ex) {
 179                 /*
 180                  * drivers may not support the above - esp. older
 181                  * drivers being used by the bridge..
 182                  */
 183                 throw new SQLException(ex.getMessage());
 184             }
 185 
 186             if(crs.getCommand().toLowerCase().indexOf("select") != -1) {
 187                 // can be (crs.getCommand()).indexOf("select")) == 0
 188                 // because we will be getting resultset when
 189                 // it may be the case that some false select query with
 190                 // select coming in between instead of first.
 191 
 192                 // if ((crs.getCommand()).indexOf("?")) does not return -1
 193                 // implies a Prepared Statement like query exists.
 194 
 195                 ResultSet rs = pstmt.executeQuery();
 196                if(crs.getPageSize() == 0){
 197                       crs.populate(rs);
 198                }
 199                else {
 200                        /**
 201                         * If page size has been set then create a ResultSet object that is scrollable using a
 202                         * PreparedStatement handle.Also call the populate(ResultSet,int) function to populate
 203                         * a page of data as specified by the page size.
 204                         */
 205                        pstmt = con.prepareStatement(crs.getCommand(),ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);
 206                        decodeParams(caller.getParams(), pstmt);
 207                        try {
 208                                pstmt.setMaxRows(crs.getMaxRows());
 209                                pstmt.setMaxFieldSize(crs.getMaxFieldSize());
 210                                pstmt.setEscapeProcessing(crs.getEscapeProcessing());
 211                                pstmt.setQueryTimeout(crs.getQueryTimeout());
 212                            } catch (Exception ex) {
 213                           /*
 214                            * drivers may not support the above - esp. older
 215                            * drivers being used by the bridge..
 216                            */
 217                             throw new SQLException(ex.getMessage());
 218                           }
 219                        rs = pstmt.executeQuery();
 220                        crs.populate(rs,startPosition);
 221                }
 222                 rs.close();
 223             } else  {
 224                 pstmt.executeUpdate();
 225             }
 226 
 227             // Get the data.
 228             pstmt.close();
 229             try {
 230                 con.commit();
 231             } catch (SQLException ex) {
 232                 ;
 233             }
 234             // only close connections we created...
 235             if (getCloseConnection() == true)
 236                 con.close();
 237         }
 238         catch (SQLException ex) {
 239             // Throw an exception if reading fails for any reason.
 240             throw ex;
 241         } finally {
 242             try {
 243                 // only close connections we created...
 244                 if (con != null && getCloseConnection() == true) {
 245                     try {
 246                         if (!con.getAutoCommit()) {
 247                             con.rollback();
 248                         }
 249                     } catch (Exception dummy) {
 250                         /*
 251                          * not an error condition, we're closing anyway, but
 252                          * we'd like to clean up any locks if we can since
 253                          * it is not clear the connection pool will clean
 254                          * these connections in a timely manner
 255                          */
 256                     }
 257                     con.close();
 258                     con = null;
 259                 }
 260             } catch (SQLException e) {
 261                 // will get exception if something already went wrong, but don't
 262                 // override that exception with this one
 263             }
 264         }
 265     }
 266 
 267     /**
 268      * Checks to see if the writer associated with this reader needs
 269      * to reset its state.  The writer will need to initialize its state
 270      * if new contents have been read since the writer was last called.
 271      * This method is called by the writer that was registered with
 272      * this reader when components were being wired together.
 273      *
 274      * @return <code>true</code> if writer associated with this reader needs
 275      *         to reset the values of its fields; <code>false</code> otherwise
 276      * @throws SQLException if an access error occurs
 277      */
 278     public boolean reset() throws SQLException {
 279         writerCalls++;
 280         return writerCalls == 1;
 281     }
 282 
 283     /**
 284      * Establishes a connection with the data source for the given
 285      * <code>RowSet</code> object.  If the rowset's <code>dataSourceName</code>
 286      * property has been set, this method uses the JNDI API to retrieve the
 287      * <code>DataSource</code> object that it can use to make the connection.
 288      * If the url, username, and password properties have been set, this
 289      * method uses the <code>DriverManager.getConnection</code> method to
 290      * make the connection.
 291      * <P>
 292      * This method is used internally by the reader and writer associated with
 293      * the calling <code>RowSet</code> object; an application never calls it
 294      * directly.
 295      *
 296      * @param caller a <code>RowSet</code> object that has implemented
 297      *               the <code>RowSetInternal</code> interface and had
 298      *               this <code>CachedRowSetReader</code> object set as
 299      *               its reader
 300      * @return a <code>Connection</code> object that represents a connection
 301      *         to the caller's data source
 302      * @throws SQLException if an access error occurs
 303      */
 304     public Connection connect(RowSetInternal caller) throws SQLException {
 305 
 306         // Get a JDBC connection.
 307         if (caller.getConnection() != null) {
 308             // A connection was passed to execute(), so use it.
 309             // As we are using a connection the user gave us we
 310             // won't close it.
 311             userCon = true;
 312             return caller.getConnection();
 313         }
 314         else if (((RowSet)caller).getDataSourceName() != null) {
 315             // Connect using JNDI.
 316             try {
 317                 Context ctx = new InitialContext();
 318                 DataSource ds = (DataSource)ctx.lookup
 319                     (((RowSet)caller).getDataSourceName());
 320 
 321                 // Check for username, password,
 322                 // if it exists try getting a Connection handle through them
 323                 // else try without these
 324                 // else throw SQLException
 325 
 326                 if(((RowSet)caller).getUsername() != null) {
 327                      return ds.getConnection(((RowSet)caller).getUsername(),
 328                                             ((RowSet)caller).getPassword());
 329                 } else {
 330                      return ds.getConnection();
 331                 }
 332             }
 333             catch (javax.naming.NamingException ex) {
 334                 SQLException sqlEx = new SQLException(resBundle.handleGetObject("crsreader.connect").toString());
 335                 sqlEx.initCause(ex);
 336                 throw sqlEx;
 337             }
 338         } else if (((RowSet)caller).getUrl() != null) {
 339             // Connect using the driver manager.
 340             return DriverManager.getConnection(((RowSet)caller).getUrl(),
 341                                                ((RowSet)caller).getUsername(),
 342                                                ((RowSet)caller).getPassword());
 343         }
 344         else {
 345             return null;
 346         }
 347     }
 348 
 349     /**
 350      * Sets the parameter placeholders
 351      * in the rowset's command (the given <code>PreparedStatement</code>
 352      * object) with the parameters in the given array.
 353      * This method, called internally by the method
 354      * <code>CachedRowSetReader.readData</code>, reads each parameter, and
 355      * based on its type, determines the correct
 356      * <code>PreparedStatement.setXXX</code> method to use for setting
 357      * that parameter.
 358      *
 359      * @param params an array of parameters to be used with the given
 360      *               <code>PreparedStatement</code> object
 361      * @param pstmt  the <code>PreparedStatement</code> object that is the
 362      *               command for the calling rowset and into which
 363      *               the given parameters are to be set
 364      * @throws SQLException if an access error occurs
 365      */
 366     private void decodeParams(Object[] params,
 367                               PreparedStatement pstmt) throws SQLException {
 368     // There is a corresponding decodeParams in JdbcRowSetImpl
 369     // which does the same as this method. This is a design flaw.
 370     // Update the JdbcRowSetImpl.decodeParams when you update
 371     // this method.
 372 
 373     // Adding the same comments to JdbcRowSetImpl.decodeParams.
 374 
 375         int arraySize;
 376         Object[] param = null;
 377 
 378         for (int i=0; i < params.length; i++) {
 379             if (params[i] instanceof Object[]) {
 380                 param = (Object[])params[i];
 381 
 382                 if (param.length == 2) {
 383                     if (param[0] == null) {
 384                         pstmt.setNull(i + 1, ((Integer)param[1]).intValue());
 385                         continue;
 386                     }
 387 
 388                     if (param[0] instanceof java.sql.Date ||
 389                         param[0] instanceof java.sql.Time ||
 390                         param[0] instanceof java.sql.Timestamp) {
 391                         System.err.println(resBundle.handleGetObject("crsreader.datedetected").toString());
 392                         if (param[1] instanceof java.util.Calendar) {
 393                             System.err.println(resBundle.handleGetObject("crsreader.caldetected").toString());
 394                             pstmt.setDate(i + 1, (java.sql.Date)param[0],
 395                                        (java.util.Calendar)param[1]);
 396                             continue;
 397                         }
 398                         else {
 399                             throw new SQLException(resBundle.handleGetObject("crsreader.paramtype").toString());
 400                         }
 401                     }
 402 
 403                     if (param[0] instanceof Reader) {
 404                         pstmt.setCharacterStream(i + 1, (Reader)param[0],
 405                                               ((Integer)param[1]).intValue());
 406                         continue;
 407                     }
 408 
 409                     /*
 410                      * What's left should be setObject(int, Object, scale)
 411                      */
 412                     if (param[1] instanceof Integer) {
 413                         pstmt.setObject(i + 1, param[0], ((Integer)param[1]).intValue());
 414                         continue;
 415                     }
 416 
 417                 } else if (param.length == 3) {
 418 
 419                     if (param[0] == null) {
 420                         pstmt.setNull(i + 1, ((Integer)param[1]).intValue(),
 421                                    (String)param[2]);
 422                         continue;
 423                     }
 424 
 425                     if (param[0] instanceof java.io.InputStream) {
 426                         switch (((Integer)param[2]).intValue()) {
 427                         case CachedRowSetImpl.UNICODE_STREAM_PARAM:
 428                             pstmt.setUnicodeStream(i + 1,
 429                                                 (java.io.InputStream)param[0],
 430                                                 ((Integer)param[1]).intValue());
 431                         case CachedRowSetImpl.BINARY_STREAM_PARAM:
 432                             pstmt.setBinaryStream(i + 1,
 433                                                (java.io.InputStream)param[0],
 434                                                ((Integer)param[1]).intValue());
 435                         case CachedRowSetImpl.ASCII_STREAM_PARAM:
 436                             pstmt.setAsciiStream(i + 1,
 437                                               (java.io.InputStream)param[0],
 438                                               ((Integer)param[1]).intValue());
 439                         default:
 440                             throw new SQLException(resBundle.handleGetObject("crsreader.paramtype").toString());
 441                         }
 442                     }
 443 
 444                     /*
 445                      * no point at looking at the first element now;
 446                      * what's left must be the setObject() cases.
 447                      */
 448                     if (param[1] instanceof Integer && param[2] instanceof Integer) {
 449                         pstmt.setObject(i + 1, param[0], ((Integer)param[1]).intValue(),
 450                                      ((Integer)param[2]).intValue());
 451                         continue;
 452                     }
 453 
 454                     throw new SQLException(resBundle.handleGetObject("crsreader.paramtype").toString());
 455 
 456                 } else {
 457                     // common case - this catches all SQL92 types
 458                     pstmt.setObject(i + 1, params[i]);
 459                     continue;
 460                 }
 461             }  else {
 462                // Try to get all the params to be set here
 463                pstmt.setObject(i + 1, params[i]);
 464 
 465             }
 466         }
 467     }
 468 
 469     /**
 470      * Assists in determining whether the current connection was created by this
 471      * CachedRowSet to ensure incorrect connections are not prematurely terminated.
 472      *
 473      * @return a boolean giving the status of whether the connection has been closed.
 474      */
 475     protected boolean getCloseConnection() {
 476         if (userCon == true)
 477             return false;
 478 
 479         return true;
 480     }
 481 
 482     /**
 483      *  This sets the start position in the ResultSet from where to begin. This is
 484      * called by the Reader in the CachedRowSetImpl to set the position on the page
 485      * to begin populating from.
 486      * @param pos integer indicating the position in the <code>ResultSet</code> to begin
 487      *        populating from.
 488      */
 489     public void setStartPosition(int pos){
 490         startPosition = pos;
 491     }
 492 
 493     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
 494         // Default state initialization happens here
 495         ois.defaultReadObject();
 496         // Initialization of  Res Bundle happens here .
 497         try {
 498            resBundle = JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle();
 499         } catch(IOException ioe) {
 500             throw new RuntimeException(ioe);
 501         }
 502 
 503     }
 504 
 505     static final long serialVersionUID =5049738185801363801L;
 506 }