001: /*
002: * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.rowset.internal;
027:
028: import java.sql.*;
029: import javax.sql.*;
030: import javax.naming.*;
031: import java.io.*;
032: import java.lang.reflect.*;
033:
034: import com.sun.rowset.*;
035: import javax.sql.rowset.*;
036: import javax.sql.rowset.spi.*;
037:
038: /**
039: * The facility called by the <code>RIOptimisticProvider</code> object
040: * internally to read data into it. The calling <code>RowSet</code> object
041: * must have implemented the <code>RowSetInternal</code> interface
042: * and have the standard <code>CachedRowSetReader</code> object set as its
043: * reader.
044: * <P>
045: * This implementation always reads all rows of the data source,
046: * and it assumes that the <code>command</code> property for the caller
047: * is set with a query that is appropriate for execution by a
048: * <code>PreparedStatement</code> object.
049: * <P>
050: * Typically the <code>SyncFactory</code> manages the <code>RowSetReader</code> and
051: * the <code>RowSetWriter</code> implementations using <code>SyncProvider</code> objects.
052: * Standard JDBC RowSet implementations provide an object instance of this
053: * reader by invoking the <code>SyncProvider.getRowSetReader()</code> method.
054: *
055: * @version 0.2
056: * @author Jonathan Bruce
057: * @see javax.sql.rowset.spi.SyncProvider
058: * @see javax.sql.rowset.spi.SyncFactory
059: * @see javax.sql.rowset.spi.SyncFactoryException
060: */
061: public class CachedRowSetReader implements RowSetReader, Serializable {
062:
063: /**
064: * The field that keeps track of whether the writer associated with
065: * this <code>CachedRowSetReader</code> object's rowset has been called since
066: * the rowset was populated.
067: * <P>
068: * When this <code>CachedRowSetReader</code> object reads data into
069: * its rowset, it sets the field <code>writerCalls</code> to 0.
070: * When the writer associated with the rowset is called to write
071: * data back to the underlying data source, its <code>writeData</code>
072: * method calls the method <code>CachedRowSetReader.reset</code>,
073: * which increments <code>writerCalls</code> and returns <code>true</code>
074: * if <code>writerCalls</code> is 1. Thus, <code>writerCalls</code> equals
075: * 1 after the first call to <code>writeData</code> that occurs
076: * after the rowset has had data read into it.
077: *
078: * @serial
079: */
080: private int writerCalls = 0;
081:
082: private boolean userCon = false;
083:
084: private int startPosition;
085:
086: private JdbcRowSetResourceBundle resBundle;
087:
088: public CachedRowSetReader() {
089: try {
090: resBundle = JdbcRowSetResourceBundle
091: .getJdbcRowSetResourceBundle();
092: } catch (IOException ioe) {
093: throw new RuntimeException(ioe);
094: }
095: }
096:
097: /**
098: * Reads data from a data source and populates the given
099: * <code>RowSet</code> object with that data.
100: * This method is called by the rowset internally when
101: * the application invokes the method <code>execute</code>
102: * to read a new set of rows.
103: * <P>
104: * After clearing the rowset of its contents, if any, and setting
105: * the number of writer calls to <code>0</code>, this reader calls
106: * its <code>connect</code> method to make
107: * a connection to the rowset's data source. Depending on which
108: * of the rowset's properties have been set, the <code>connect</code>
109: * method will use a <code>DataSource</code> object or the
110: * <code>DriverManager</code> facility to make a connection to the
111: * data source.
112: * <P>
113: * Once the connection to the data source is made, this reader
114: * executes the query in the calling <code>CachedRowSet</code> object's
115: * <code>command</code> property. Then it calls the rowset's
116: * <code>populate</code> method, which reads data from the
117: * <code>ResultSet</code> object produced by executing the rowset's
118: * command. The rowset is then populated with this data.
119: * <P>
120: * This method's final act is to close the connection it made, thus
121: * leaving the rowset disconnected from its data source.
122: *
123: * @param caller a <code>RowSet</code> object that has implemented
124: * the <code>RowSetInternal</code> interface and had
125: * this <code>CachedRowSetReader</code> object set as
126: * its reader
127: * @throws SQLException if there is a database access error, there is a
128: * problem making the connection, or the command property has not
129: * been set
130: */
131: public void readData(RowSetInternal caller) throws SQLException {
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(
162: "crsreader.connecterr").toString());
163:
164: try {
165: con.setTransactionIsolation(crs
166: .getTransactionIsolation());
167: } catch (Exception ex) {
168: ;
169: }
170: // Use JDBC to read the data.
171: PreparedStatement pstmt = con.prepareStatement(crs
172: .getCommand());
173: // Pass any input parameters to JDBC.
174:
175: decodeParams(caller.getParams(), pstmt);
176: try {
177: pstmt.setMaxRows(crs.getMaxRows());
178: pstmt.setMaxFieldSize(crs.getMaxFieldSize());
179: pstmt.setEscapeProcessing(crs.getEscapeProcessing());
180: pstmt.setQueryTimeout(crs.getQueryTimeout());
181: } catch (Exception ex) {
182: /*
183: * drivers may not support the above - esp. older
184: * drivers being used by the bridge..
185: */
186: throw new SQLException(ex.getMessage());
187: }
188:
189: if (crs.getCommand().toLowerCase().indexOf("select") != -1) {
190: // can be (crs.getCommand()).indexOf("select")) == 0
191: // because we will be getting resultset when
192: // it may be the case that some false select query with
193: // select coming in between instead of first.
194:
195: // if ((crs.getCommand()).indexOf("?")) does not return -1
196: // implies a Prepared Statement like query exists.
197:
198: ResultSet rs = pstmt.executeQuery();
199: if (crs.getPageSize() == 0) {
200: crs.populate(rs);
201: } else {
202: /**
203: * If page size has been set then create a ResultSet object that is scrollable using a
204: * PreparedStatement handle.Also call the populate(ResultSet,int) function to populate
205: * a page of data as specified by the page size.
206: */
207: pstmt = con.prepareStatement(crs.getCommand(),
208: ResultSet.TYPE_SCROLL_INSENSITIVE,
209: ResultSet.CONCUR_UPDATABLE);
210: decodeParams(caller.getParams(), pstmt);
211: try {
212: pstmt.setMaxRows(crs.getMaxRows());
213: pstmt.setMaxFieldSize(crs.getMaxFieldSize());
214: pstmt.setEscapeProcessing(crs
215: .getEscapeProcessing());
216: pstmt.setQueryTimeout(crs.getQueryTimeout());
217: } catch (Exception ex) {
218: /*
219: * drivers may not support the above - esp. older
220: * drivers being used by the bridge..
221: */
222: throw new SQLException(ex.getMessage());
223: }
224: rs = pstmt.executeQuery();
225: crs.populate(rs, startPosition);
226: }
227: rs.close();
228: } else {
229: pstmt.executeUpdate();
230: }
231:
232: // Get the data.
233: pstmt.close();
234: try {
235: con.commit();
236: } catch (SQLException ex) {
237: ;
238: }
239: // only close connections we created...
240: if (getCloseConnection() == true)
241: con.close();
242: } catch (SQLException ex) {
243: // Throw an exception if reading fails for any reason.
244: throw ex;
245: } finally {
246: try {
247: // only close connections we created...
248: if (con != null && getCloseConnection() == true) {
249: try {
250: if (!con.getAutoCommit()) {
251: con.rollback();
252: }
253: } catch (Exception dummy) {
254: /*
255: * not an error condition, we're closing anyway, but
256: * we'd like to clean up any locks if we can since
257: * it is not clear the connection pool will clean
258: * these connections in a timely manner
259: */
260: }
261: con.close();
262: con = null;
263: }
264: } catch (SQLException e) {
265: // will get exception if something already went wrong, but don't
266: // override that exception with this one
267: }
268: }
269: }
270:
271: /**
272: * Checks to see if the writer associated with this reader needs
273: * to reset its state. The writer will need to initialize its state
274: * if new contents have been read since the writer was last called.
275: * This method is called by the writer that was registered with
276: * this reader when components were being wired together.
277: *
278: * @return <code>true</code> if writer associated with this reader needs
279: * to reset the values of its fields; <code>false</code> otherwise
280: * @throws SQLException if an access error occurs
281: */
282: public boolean reset() throws SQLException {
283: writerCalls++;
284: return writerCalls == 1;
285: }
286:
287: /**
288: * Establishes a connection with the data source for the given
289: * <code>RowSet</code> object. If the rowset's <code>dataSourceName</code>
290: * property has been set, this method uses the JNDI API to retrieve the
291: * <code>DataSource</code> object that it can use to make the connection.
292: * If the url, username, and password properties have been set, this
293: * method uses the <code>DriverManager.getConnection</code> method to
294: * make the connection.
295: * <P>
296: * This method is used internally by the reader and writer associated with
297: * the calling <code>RowSet</code> object; an application never calls it
298: * directly.
299: *
300: * @param caller a <code>RowSet</code> object that has implemented
301: * the <code>RowSetInternal</code> interface and had
302: * this <code>CachedRowSetReader</code> object set as
303: * its reader
304: * @return a <code>Connection</code> object that represents a connection
305: * to the caller's data source
306: * @throws SQLException if an access error occurs
307: */
308: public Connection connect(RowSetInternal caller)
309: throws SQLException {
310:
311: // Get a JDBC connection.
312: if (caller.getConnection() != null) {
313: // A connection was passed to execute(), so use it.
314: // As we are using a connection the user gave us we
315: // won't close it.
316: userCon = true;
317: return caller.getConnection();
318: } else if (((RowSet) caller).getDataSourceName() != null) {
319: // Connect using JNDI.
320: try {
321: Context ctx = new InitialContext();
322: DataSource ds = (DataSource) ctx
323: .lookup(((RowSet) caller).getDataSourceName());
324:
325: // Check for username, password,
326: // if it exists try getting a Connection handle through them
327: // else try without these
328: // else throw SQLException
329:
330: if (((RowSet) caller).getUsername() != null) {
331: return ds.getConnection(((RowSet) caller)
332: .getUsername(), ((RowSet) caller)
333: .getPassword());
334: } else {
335: return ds.getConnection();
336: }
337: } catch (javax.naming.NamingException ex) {
338: SQLException sqlEx = new SQLException(resBundle
339: .handleGetObject("crsreader.connect")
340: .toString());
341: sqlEx.initCause(ex);
342: throw sqlEx;
343: }
344: } else if (((RowSet) caller).getUrl() != null) {
345: // Connect using the driver manager.
346: return DriverManager.getConnection(((RowSet) caller)
347: .getUrl(), ((RowSet) caller).getUsername(),
348: ((RowSet) caller).getPassword());
349: } else {
350: return null;
351: }
352: }
353:
354: /**
355: * Sets the parameter placeholders
356: * in the rowset's command (the given <code>PreparedStatement</code>
357: * object) with the parameters in the given array.
358: * This method, called internally by the method
359: * <code>CachedRowSetReader.readData</code>, reads each parameter, and
360: * based on its type, determines the correct
361: * <code>PreparedStatement.setXXX</code> method to use for setting
362: * that parameter.
363: *
364: * @param params an array of parameters to be used with the given
365: * <code>PreparedStatement</code> object
366: * @param pstmt the <code>PreparedStatement</code> object that is the
367: * command for the calling rowset and into which
368: * the given parameters are to be set
369: * @throws SQLException if an access error occurs
370: */
371: private void decodeParams(Object[] params, PreparedStatement pstmt)
372: throws SQLException {
373: // There is a corresponding decodeParams in JdbcRowSetImpl
374: // which does the same as this method. This is a design flaw.
375: // Update the JdbcRowSetImpl.decodeParams when you update
376: // this method.
377:
378: // Adding the same comments to JdbcRowSetImpl.decodeParams.
379:
380: int arraySize;
381: Object[] param = null;
382:
383: for (int i = 0; i < params.length; i++) {
384: if (params[i] instanceof Object[]) {
385: param = (Object[]) params[i];
386:
387: if (param.length == 2) {
388: if (param[0] == null) {
389: pstmt.setNull(i + 1, ((Integer) param[1])
390: .intValue());
391: continue;
392: }
393:
394: if (param[0] instanceof java.sql.Date
395: || param[0] instanceof java.sql.Time
396: || param[0] instanceof java.sql.Timestamp) {
397: System.err.println(resBundle.handleGetObject(
398: "crsreader.datedetected").toString());
399: if (param[1] instanceof java.util.Calendar) {
400: System.err.println(resBundle
401: .handleGetObject(
402: "crsreader.caldetected")
403: .toString());
404: pstmt.setDate(i + 1,
405: (java.sql.Date) param[0],
406: (java.util.Calendar) param[1]);
407: continue;
408: } else {
409: throw new SQLException(resBundle
410: .handleGetObject(
411: "crsreader.paramtype")
412: .toString());
413: }
414: }
415:
416: if (param[0] instanceof Reader) {
417: pstmt.setCharacterStream(i + 1,
418: (Reader) param[0], ((Integer) param[1])
419: .intValue());
420: continue;
421: }
422:
423: /*
424: * What's left should be setObject(int, Object, scale)
425: */
426: if (param[1] instanceof Integer) {
427: pstmt.setObject(i + 1, param[0],
428: ((Integer) param[1]).intValue());
429: continue;
430: }
431:
432: } else if (param.length == 3) {
433:
434: if (param[0] == null) {
435: pstmt.setNull(i + 1, ((Integer) param[1])
436: .intValue(), (String) param[2]);
437: continue;
438: }
439:
440: if (param[0] instanceof java.io.InputStream) {
441: switch (((Integer) param[2]).intValue()) {
442: case CachedRowSetImpl.UNICODE_STREAM_PARAM:
443: pstmt.setUnicodeStream(i + 1,
444: (java.io.InputStream) param[0],
445: ((Integer) param[1]).intValue());
446: case CachedRowSetImpl.BINARY_STREAM_PARAM:
447: pstmt.setBinaryStream(i + 1,
448: (java.io.InputStream) param[0],
449: ((Integer) param[1]).intValue());
450: case CachedRowSetImpl.ASCII_STREAM_PARAM:
451: pstmt.setAsciiStream(i + 1,
452: (java.io.InputStream) param[0],
453: ((Integer) param[1]).intValue());
454: default:
455: throw new SQLException(resBundle
456: .handleGetObject(
457: "crsreader.paramtype")
458: .toString());
459: }
460: }
461:
462: /*
463: * no point at looking at the first element now;
464: * what's left must be the setObject() cases.
465: */
466: if (param[1] instanceof Integer
467: && param[2] instanceof Integer) {
468: pstmt.setObject(i + 1, param[0],
469: ((Integer) param[1]).intValue(),
470: ((Integer) param[2]).intValue());
471: continue;
472: }
473:
474: throw new SQLException(resBundle.handleGetObject(
475: "crsreader.paramtype").toString());
476:
477: } else {
478: // common case - this catches all SQL92 types
479: pstmt.setObject(i + 1, params[i]);
480: continue;
481: }
482: } else {
483: // Try to get all the params to be set here
484: pstmt.setObject(i + 1, params[i]);
485:
486: }
487: }
488: }
489:
490: /**
491: * Assists in determining whether the current connection was created by this
492: * CachedRowSet to ensure incorrect connections are not prematurely terminated.
493: *
494: * @return a boolean giving the status of whether the connection has been closed.
495: */
496: protected boolean getCloseConnection() {
497: if (userCon == true)
498: return false;
499:
500: return true;
501: }
502:
503: /**
504: * This sets the start position in the ResultSet from where to begin. This is
505: * called by the Reader in the CachedRowSetImpl to set the position on the page
506: * to begin populating from.
507: * @param pos integer indicating the position in the <code>ResultSet</code> to begin
508: * populating from.
509: */
510: public void setStartPosition(int pos) {
511: startPosition = pos;
512: }
513:
514: }
|