001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package com.sun.sql.rowset.internal;
043:
044: import java.sql.*;
045: import javax.sql.*;
046: import javax.naming.*;
047: import java.io.*;
048: import java.lang.reflect.*;
049:
050: //import com.sun.rowset.*;
051: import javax.sql.rowset.*;
052: import javax.sql.rowset.spi.*;
053:
054: import com.sun.sql.rowset.CachedRowSetX;
055: import com.sun.sql.rowset.CachedRowSetXImpl;
056:
057: import java.text.MessageFormat;
058: import java.util.Locale;
059: import java.util.ResourceBundle;
060:
061: /**
062: * An implementation of RowSetReader to work with CachedRowSetX rowsets.
063: *
064: * Based on the Sun reference implementation of CachedRowSetReader.
065: *
066: * @see javax.sql.rowset.spi.SyncProvider
067: * @see javax.sql.rowset.spi.SyncFactory
068: * @see javax.sql.rowset.spi.SyncFactoryException
069: */
070: public class CachedRowSetXReader implements RowSetReader, Serializable {
071:
072: private static ResourceBundle rb = ResourceBundle.getBundle(
073: "com.sun.sql.rowset.internal.Bundle", Locale.getDefault()); // NOI18N
074:
075: /**
076: * The field that keeps track of whether the writer associated with
077: * this <code>CachedRowSetXReader</code> object's rowset has been called since
078: * the rowset was populated.
079: * <P>
080: * When this <code>CachedRowSetXReader</code> object reads data into
081: * its rowset, it sets the field <code>writerCalls</code> to 0.
082: * When the writer associated with the rowset is called to write
083: * data back to the underlying data source, its <code>writeData</code>
084: * method calls the method <code>CachedRowSetXReader.reset</code>,
085: * which increments <code>writerCalls</code> and returns <code>true</code>
086: * if <code>writerCalls</code> is 1. Thus, <code>writerCalls</code> equals
087: * 1 after the first call to <code>writeData</code> that occurs
088: * after the rowset has had data read into it.
089: *
090: * @serial
091: */
092: private int writerCalls = 0;
093:
094: private boolean userCon = false;
095:
096: private int startPosition;
097:
098: /**
099: * Reads data from a data source and populates the given
100: * <code>RowSet</code> object with that data.
101: * This method is called by the rowset internally when
102: * the application invokes the method <code>execute</code>
103: * to read a new set of rows.
104: * <P>
105: * After clearing the rowset of its contents, if any, and setting
106: * the number of writer calls to <code>0</code>, this reader calls
107: * its <code>connect</code> method to make
108: * a connection to the rowset's data source. Depending on which
109: * of the rowset's properties have been set, the <code>connect</code>
110: * method will use a <code>DataSource</code> object or the
111: * <code>DriverManager</code> facility to make a connection to the
112: * data source.
113: * <P>
114: * Once the connection to the data source is made, this reader
115: * executes the query in the calling <code>CachedRowSet</code> object's
116: * <code>command</code> property. Then it calls the rowset's
117: * <code>populate</code> method, which reads data from the
118: * <code>ResultSet</code> object produced by executing the rowset's
119: * command. The rowset is then populated with this data.
120: * <P>
121: * This method's final act is to close the connection it made, thus
122: * leaving the rowset disconnected from its data source.
123: *
124: * @param caller a <code>RowSet</code> object that has implemented
125: * the <code>RowSetInternal</code> interface and had
126: * this <code>CachedRowSetXReader</code> object set as
127: * its reader
128: * @throws SQLException if there is a database access error, there is a
129: * problem making the connection, or the command property has not
130: * been set
131: */
132: public void readData(RowSetInternal caller) throws SQLException {
133: Connection con = null;
134: boolean printStatements = (caller instanceof CachedRowSetX) ? ((CachedRowSetX) caller)
135: .getPrintStatements()
136: : false;
137: try {
138: CachedRowSet crs = (CachedRowSet) caller;
139:
140: // Get rid of the current contents of the rowset.
141:
142: /**
143: * Checking added to verify whether page size has been set or not.
144: * If set then do not close the object as certain parameters need
145: * to be maintained.
146: */
147:
148: if (crs.getPageSize() == 0 && crs.size() > 0) {
149: // When page size is not set,
150: // crs.size() will show the total no of rows.
151: crs.close();
152: }
153:
154: writerCalls = 0;
155:
156: // Get a connection. This reader assumes that the necessary
157: // properties have been set on the caller to let it supply a
158: // connection.
159: userCon = false;
160:
161: con = this .connect(caller);
162:
163: // Check our assumptions.
164: if (con == null || crs.getCommand() == null)
165: throw new SQLException(
166: "Internal Error in RowSetReader: no connection or command.");
167:
168: try {
169: con.setTransactionIsolation(crs
170: .getTransactionIsolation());
171: } catch (Exception ex) {
172: if (printStatements) {
173: System.out
174: .println("setTransactionIsolation() failed "
175: + crs.getTransactionIsolation());
176: }
177: }
178: // Use JDBC to read the data.
179: PreparedStatement pstmt = con.prepareStatement(crs
180: .getCommand());
181: // Pass any input parameters to JDBC.
182:
183: decodeParams(caller.getParams(), pstmt);
184:
185: StringBuffer msg = null;
186: if (printStatements) {
187: msg = new StringBuffer(200);
188: msg.append(crs.getCommand());
189: msg.append("\n");
190: Object[] params = caller.getParams();
191: if (params.length > 0) {
192: for (int i = 0; i < params.length; i++) {
193: msg.append(" Param[" + (i + 1) + "]=(");
194: if (params[i] != null) {
195: msg.append(params[i].getClass().getName()
196: + "," + params[i].toString() + ")");
197: } else {
198: msg.append("null)");
199: }
200: }
201: } else {
202: msg.append(" No Params");
203: }
204: }
205: // drivers may not support the following, so ignore exceptions.
206: try {
207: pstmt.setMaxRows(crs.getMaxRows());
208: } catch (Exception ex) {
209: ;
210: }
211: try {
212: pstmt.setMaxFieldSize(crs.getMaxFieldSize());
213: } catch (Exception ex) {
214: ;
215: }
216: try {
217: pstmt.setEscapeProcessing(crs.getEscapeProcessing());
218: } catch (Exception ex) {
219: ;
220: }
221: try {
222: pstmt.setQueryTimeout(crs.getQueryTimeout());
223: } catch (Exception ex) {
224: ;
225: }
226:
227: if (crs.getCommand().toLowerCase().indexOf("select") != -1) { //NOI18N
228: // can be (crs.getCommand()).indexOf("select")) == 0 //NOI18N
229: // because we will be getting resultset when
230: // it may be the case that some false select query with
231: // select coming in between instead of first.
232:
233: // if ((crs.getCommand()).indexOf("?")) does not return -1 //NOI18N
234: // implies a Prepared Statement like query exists.
235:
236: ResultSet rs;
237: if (crs.getPageSize() == 0) {
238: if (printStatements) {
239: System.out
240: .println("Reader executing query ps=0 "
241: + msg);
242: }
243: rs = pstmt.executeQuery();
244: crs.populate(rs);
245: if (printStatements) {
246: System.out.println("Reader executing finished");
247: }
248: } else {
249: /**
250: * If page size has been set then create a ResultSet object that is scrollable using a
251: * PreparedStatement handle.Also call the populate(ResultSet,int) function to populate
252: * a page of data as specified by the page size.
253: */
254: pstmt = con.prepareStatement(crs.getCommand(),
255: ResultSet.TYPE_SCROLL_INSENSITIVE,
256: ResultSet.CONCUR_UPDATABLE);
257: decodeParams(caller.getParams(), pstmt);
258:
259: // drivers may not support the following, so ignore exceptions.
260: try {
261: pstmt.setMaxRows(crs.getMaxRows());
262: } catch (Exception ex) {
263: ;
264: }
265: try {
266: pstmt.setMaxFieldSize(crs.getMaxFieldSize());
267: } catch (Exception ex) {
268: ;
269: }
270: try {
271: pstmt.setEscapeProcessing(crs
272: .getEscapeProcessing());
273: } catch (Exception ex) {
274: ;
275: }
276: try {
277: pstmt.setQueryTimeout(crs.getQueryTimeout());
278: } catch (Exception ex) {
279: ;
280: }
281:
282: if (printStatements) {
283: System.out.println("Reader executing query ps="
284: + crs.getPageSize() + " " + msg);
285: }
286: rs = pstmt.executeQuery();
287: crs.populate(rs, startPosition);
288: if (printStatements) {
289: System.out.println("Reader executing finished");
290: }
291: }
292: rs.close();
293: } else {
294: if (printStatements) {
295: System.out.println("Reader executing non-select "
296: + msg);
297: }
298: pstmt.executeUpdate();
299: if (printStatements) {
300: System.out.println("Reader executing finished");
301: }
302: }
303:
304: // Get the data.
305: pstmt.close();
306: try {
307: con.commit();
308: } catch (SQLException ex) {
309: if (printStatements) {
310: ex.printStackTrace();
311: }
312: }
313: } catch (SQLException ex) {
314: // Throw an exception if reading fails for any reason.
315: if (printStatements) {
316: ex.printStackTrace();
317: }
318: throw ex;
319: } finally {
320: try {
321: // only close connections we created...
322: if (con != null && getCloseConnection() == true) {
323: try {
324: if (!con.getAutoCommit()) {
325: con.rollback();
326: }
327: } catch (Exception dummy) {
328: /*
329: * not an error condition, we're closing anyway, but
330: * we'd like to clean up any locks if we can since
331: * it is not clear the connection pool will clean
332: * these connections in a timely manner
333: */
334: }
335: con.close();
336: con = null;
337: }
338: } catch (SQLException e) {
339: // will get exception if something already went wrong, but don't
340: // override that exception with this one
341: }
342: }
343: }
344:
345: /**
346: * Checks to see if the writer associated with this reader needs
347: * to reset its state. The writer will need to initialize its state
348: * if new contents have been read since the writer was last called.
349: * This method is called by the writer that was registered with
350: * this reader when components were being wired together.
351: *
352: * @return <code>true</code> if writer associated with this reader needs
353: * to reset the values of its fields; <code>false</code> otherwise
354: * @throws SQLException if an access error occurs
355: */
356: public boolean reset() throws SQLException {
357: writerCalls++;
358: return writerCalls == 1;
359: }
360:
361: /**
362: * Establishes a connection with the data source for the given
363: * <code>RowSet</code> object. If the rowset's <code>dataSourceName</code>
364: * property has been set, this method uses the JNDI API to retrieve the
365: * <code>DataSource</code> object that it can use to make the connection.
366: * If the url, username, and password properties have been set, this
367: * method uses the <code>DriverManager.getConnection</code> method to
368: * make the connection.
369: * <P>
370: * This method is used internally by the reader and writer associated with
371: * the calling <code>RowSet</code> object; an application never calls it
372: * directly.
373: *
374: * @param caller a <code>RowSet</code> object that has implemented
375: * the <code>RowSetInternal</code> interface and had
376: * this <code>CachedRowSetXReader</code> object set as
377: * its reader
378: * @return a <code>Connection</code> object that represents a connection
379: * to the caller's data source
380: * @throws SQLException if an access error occurs
381: */
382: public Connection connect(RowSetInternal caller)
383: throws SQLException {
384:
385: // Get a JDBC connection.
386: if (caller.getConnection() != null) {
387: // A connection was passed to execute(), so use it.
388: // As we are using a connection the user gave us we
389: // won't close it.
390: userCon = true;
391: return caller.getConnection();
392: } else if (((RowSet) caller).getDataSourceName() != null) {
393: // Connect using JNDI.
394: try {
395: Context ctx = new InitialContext();
396: DataSource ds = (DataSource) ctx
397: .lookup(((RowSet) caller).getDataSourceName());
398:
399: // Check for username, password,
400: // if it exists try getting a Connection handle through them
401: // else try without these
402: // else throw SQLException
403:
404: if (((RowSet) caller).getUsername() != null) {
405: return ds.getConnection(((RowSet) caller)
406: .getUsername(), ((RowSet) caller)
407: .getPassword());
408: } else {
409: return ds.getConnection();
410: }
411: } catch (javax.naming.NamingException ex) {
412: SQLException sqlEx = new SQLException();
413: sqlEx.initCause(ex);
414: throw sqlEx;
415: }
416: } else if (((RowSet) caller).getUrl() != null) {
417: // Connect using the driver manager.
418: return DriverManager.getConnection(((RowSet) caller)
419: .getUrl(), ((RowSet) caller).getUsername(),
420: ((RowSet) caller).getPassword());
421: } else {
422: return null;
423: }
424: }
425:
426: /**
427: * Sets the parameter placeholders
428: * in the rowset's command (the given <code>PreparedStatement</code>
429: * object) with the parameters in the given array.
430: * This method, called internally by the method
431: * <code>CachedRowSetXReader.readData</code>, reads each parameter, and
432: * based on its type, determines the correct
433: * <code>PreparedStatement.setXXX</code> method to use for setting
434: * that parameter.
435: *
436: * @param params an array of parameters to be used with the given
437: * <code>PreparedStatement</code> object
438: * @param pstmt the <code>PreparedStatement</code> object that is the
439: * command for the calling rowset and into which
440: * the given parameters are to be set
441: * @throws SQLException if an access error occurs
442: */
443: private void decodeParams(Object[] params, PreparedStatement pstmt)
444: throws SQLException {
445: // There is a corresponding decodeParams in JdbcRowSetImpl
446: // which does the same as this method. This is a design flaw.
447: // Update the JdbcRowSetImpl.decodeParams when you update
448: // this method.
449:
450: // Adding the same comments to JdbcRowSetImpl.decodeParams.
451:
452: int arraySize;
453: Object[] param = null;
454:
455: for (int i = 0; i < params.length; i++) {
456: if (params[i] instanceof Object[]) {
457: param = (Object[]) params[i];
458:
459: if (param.length == 2) {
460: if (param[0] == null) {
461: pstmt.setNull(i + 1, ((Integer) param[1])
462: .intValue());
463: continue;
464: }
465:
466: if (param[0] instanceof java.sql.Date
467: || param[0] instanceof java.sql.Time
468: || param[0] instanceof java.sql.Timestamp) {
469: System.err.println(rb
470: .getString("DETECTED_A_DATE")); //NOI18N
471: if (param[1] instanceof java.util.Calendar) {
472: System.err.println(rb
473: .getString("DETECTED_A_CALENDAR")); //NOI18N
474: pstmt.setDate(i + 1,
475: (java.sql.Date) param[0],
476: (java.util.Calendar) param[1]);
477: continue;
478: } else {
479: throw new SQLException(
480: rb
481: .getString("UNABLE_TO_DEDUCE_PARAM_TYPE")); //NOI18N
482: }
483: }
484:
485: if (param[0] instanceof Reader) {
486: pstmt.setCharacterStream(i + 1,
487: (Reader) param[0], ((Integer) param[1])
488: .intValue());
489: continue;
490: }
491:
492: /*
493: * What's left should be setObject(int, Object, scale)
494: */
495: if (param[1] instanceof Integer) {
496: pstmt.setObject(i + 1, param[0],
497: ((Integer) param[1]).intValue());
498: continue;
499: }
500:
501: } else if (param.length == 3) {
502:
503: if (param[0] == null) {
504: pstmt.setNull(i + 1, ((Integer) param[1])
505: .intValue(), (String) param[2]);
506: continue;
507: }
508:
509: if (param[0] instanceof java.io.InputStream) {
510: switch (((Integer) param[2]).intValue()) {
511: case CachedRowSetXImpl.UNICODE_STREAM_PARAM:
512: pstmt.setUnicodeStream(i + 1,
513: (java.io.InputStream) param[0],
514: ((Integer) param[1]).intValue());
515: case CachedRowSetXImpl.BINARY_STREAM_PARAM:
516: pstmt.setBinaryStream(i + 1,
517: (java.io.InputStream) param[0],
518: ((Integer) param[1]).intValue());
519: case CachedRowSetXImpl.ASCII_STREAM_PARAM:
520: pstmt.setAsciiStream(i + 1,
521: (java.io.InputStream) param[0],
522: ((Integer) param[1]).intValue());
523: default:
524: throw new SQLException(
525: rb
526: .getString("UNABLE_TO_DEDUCE_PARAM_TYPE")); //NOI18N
527: }
528: }
529:
530: /*
531: * no point at looking at the first element now;
532: * what's left must be the setObject() cases.
533: */
534: if (param[1] instanceof Integer
535: && param[2] instanceof Integer) {
536: pstmt.setObject(i + 1, param[0],
537: ((Integer) param[1]).intValue(),
538: ((Integer) param[2]).intValue());
539: continue;
540: }
541:
542: throw new SQLException(rb
543: .getString("UNABLE_TO_DEDUCE_PARAM_TYPE")); //NOI18N
544:
545: } else {
546: // common case - this catches all SQL92 types
547: pstmt.setObject(i + 1, params[i]);
548: continue;
549: }
550: } else {
551: // Try to get all the params to be set here
552: pstmt.setObject(i + 1, params[i]);
553:
554: }
555: }
556: }
557:
558: /**
559: * Assists in determining whether the current connection was created by this
560: * CachedRowSet to ensure incorrect connections are not prematurely terminated.
561: *
562: * @return a boolean giving the status of whether the connection has been closed.
563: */
564: protected boolean getCloseConnection() {
565: if (userCon == true)
566: return false;
567:
568: return true;
569: }
570:
571: /**
572: * This sets the start position in the ResultSet from where to begin. This is
573: * called by the Reader in the CachedRowSetXImpl to set the position on the page
574: * to begin populating from.
575: * @param pos integer indicating the position in the <code>ResultSet</code> to begin
576: * populating from.
577: */
578: public void setStartPosition(int pos) {
579: startPosition = pos;
580: }
581:
582: }
|