001: /* Copyright 2001, 2005 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal;
007:
008: import java.io.IOException;
009: import java.io.InputStream;
010: import java.io.PrintWriter;
011: import java.sql.Connection;
012: import java.sql.Driver;
013: import java.sql.ResultSet;
014: import java.sql.SQLException;
015: import java.sql.Statement;
016: import java.util.Collections;
017: import java.util.HashMap;
018: import java.util.Map;
019: import java.util.Properties;
020:
021: import javax.naming.Context;
022: import javax.naming.InitialContext;
023: import javax.sql.DataSource;
024:
025: import org.apache.commons.logging.Log;
026: import org.apache.commons.logging.LogFactory;
027: import org.jasig.portal.properties.PropertiesManager;
028: import org.jasig.portal.rdbm.DatabaseMetaDataImpl;
029: import org.jasig.portal.rdbm.IDatabaseMetadata;
030: import org.jasig.portal.rdbm.pool.IPooledDataSourceFactory;
031: import org.jasig.portal.rdbm.pool.PooledDataSourceFactoryFactory;
032: import org.jasig.portal.utils.MovingAverage;
033: import org.jasig.portal.utils.MovingAverageSample;
034: import org.springframework.dao.DataAccessResourceFailureException;
035:
036: import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicInteger;
037:
038: /**
039: * Provides relational database access and helper methods.
040: * A static routine determines if the database/driver supports
041: * prepared statements and/or outer joins.
042: *
043: * <p>This class provides database access as a service. Via the class, uPortal
044: * code can obtain a connection to the core uPortal database as well as to other
045: * databases available via JNDI. (Doing that JNDI lookup directly allows your
046: * code to avoid dependence upon this class.) This class provides
047: * traditional getConnection() methods as well as static covers for getting a
048: * reference to the backing DataSource.</p>
049: *
050: * <p>This class also provides helper methods for manipulating connections.
051: * Mostof the methods are wrappers around methods on the underlying Connection
052: * that handle (log and swallow) the SQLExceptions that the underlying methods
053: * declare to be thrown (these helpers also catch
054: * and log RuntimeExceptions encountered). They provide an alternative to trying
055: * and catching those methods using the JDBC APIs directly.</p>
056: *
057: * @author Ken Weiner, kweiner@unicon.net
058: * @author George Lindholm, george.lindholm@ubc.ca
059: * @author Eric Dalquist <a href="mailto:edalquist@unicon.net">edalquist@unicon.net</a>
060: * @author Susan Bramhall <a href="mailto:susan.bramhall@yale.edu">susan.bramhall@yale.edu</a>
061: * @version $Revision: 36804 $ $Date: 2007-02-12 16:23:45 -0700 (Mon, 12 Feb 2007) $
062: */
063: public class RDBMServices {
064: public static final String BASE_JNDI_CONTEXT = PropertiesManager
065: .getProperty(
066: "org.jasig.portal.RDBMServices.baseJndiContext",
067: "java:comp/env/jdbc");
068: public static final String PORTAL_DB = PropertiesManager
069: .getProperty(
070: "org.jasig.portal.RDBMServices.PortalDatasourceJndiName",
071: "PortalDb"); // JNDI name for portal database
072: public static final String DEFAULT_DATABASE = "DEFAULT_DATABASE";
073:
074: private static boolean getDatasourceFromJndi = PropertiesManager
075: .getPropertyAsBoolean(
076: "org.jasig.portal.RDBMServices.getDatasourceFromJndi",
077: true);
078: private static final Log LOG = LogFactory
079: .getLog(RDBMServices.class);
080:
081: //DBFlag constants
082: private static final String FLAG_TRUE = "Y";
083: private static final String FLAG_TRUE_OTHER = "T";
084: private static final String FLAG_FALSE = "N";
085:
086: /** Specifies how long to wait before trying to look a JNDI data source that previously failed */
087: private static final int JNDI_RETRY_TIME = PropertiesManager
088: .getPropertyAsInt(
089: "org.jasig.portal.RDBMServices.jndiRetryDelay",
090: 30000); // JNDI retry delay;
091:
092: private static Map namedDataSources = Collections
093: .synchronizedMap(new HashMap());
094: private static Map namedDbServerFailures = Collections
095: .synchronizedMap(new HashMap());
096:
097: /* info legacy utilities */
098: private static String jdbcUrl;
099: private static String jdbcUser;
100: private static String jdbcDriver;
101:
102: private static IDatabaseMetadata dbMetaData = null;
103:
104: // Metric counters
105: private static final MovingAverage databaseTimes = new MovingAverage();
106: public static MovingAverageSample lastDatabase = new MovingAverageSample();
107: private static AtomicInteger activeConnections = new AtomicInteger();
108: private static int maxConnections = 0;
109:
110: /**
111: * Gets the default DataSource. If no server is found
112: * a runtime exception will be thrown. This method will never return null.
113: * @return the core uPortal DataSource
114: * @throws RuntimeException on failure
115: */
116: public static DataSource getDataSource() {
117: return getDataSource(PORTAL_DB);
118: }
119:
120: /**
121: * Gets a named DataSource from JNDI, with special handling for the PORTAL_DB
122: * datasource. Successful lookups
123: * are cached and not done again. Lookup failure is remembered and blocks retry
124: * for a
125: * number of milliseconds specified by {@link #JNDI_RETRY_TIME} to reduce
126: * JNDI overhead and log spam.
127: *
128: * There are two ways in which we handle the core uPortal DataSource
129: * specially.
130: *
131: * We determine and remember metadata in an DbMetaData object for the core
132: * uPortal DataSource. We do not compute this DbMetaData for any other
133: * DataSource.
134: *
135: * We fall back on using rdbm.properties to construct our core uPortal
136: * DataSource in the case where we cannot find it from JNDI. If the portal
137: * property org.jasig.portal.RDBMServices.getDatasourceFromJNDI is true,
138: * we first
139: * first try to get the connection by looking in the
140: * JNDI context for the name defined by the portal property
141: * org.jasig.portal.RDBMServices.PortalDatasourceJndiName .
142: *
143: * If we were not configured to check JNDI or we didn't find it in JNDI having
144: * checked, we then fall back on rdbm.properties.
145: *
146: * @param name The name of the DataSource to get.
147: * @return A named DataSource or <code>null</code> if one cannot be found.
148: */
149: public static DataSource getDataSource(String name) {
150: final String PROP_FILE = "/properties/rdbm.properties";
151:
152: if (DEFAULT_DATABASE.equals(name)) {
153: name = PORTAL_DB;
154: }
155:
156: DataSource ds = (DataSource) namedDataSources.get(name);
157:
158: // if already have a dtasource just return it
159: if (ds != null)
160: return ds;
161:
162: // For non default database cache the datasource and return it
163: if (!PORTAL_DB.equals(name)) {
164: ds = getJndiDataSource(name);
165: namedDataSources.put(name, ds);
166: // For non default database return whatever we have (could be null)
167: return ds;
168: }
169:
170: // portal database is special - create metadata object too.
171: if (getDatasourceFromJndi) {
172: ds = getJndiDataSource(name);
173: if (ds != null) {
174: if (LOG.isInfoEnabled())
175: LOG
176: .info("Creating DataSource instance for "
177: + name);
178: dbMetaData = new DatabaseMetaDataImpl(ds);
179: namedDataSources.put(name, ds);
180: return ds;
181: }
182: }
183:
184: // get here if not getDatasourceFromJndi OR jndi lookup returned null
185: // try to get datasource via properties
186: try {
187: final InputStream jdbcPropStream = RDBMServices.class
188: .getResourceAsStream(PROP_FILE);
189:
190: try {
191: final Properties jdbpProperties = new Properties();
192: jdbpProperties.load(jdbcPropStream);
193:
194: final IPooledDataSourceFactory pdsf = PooledDataSourceFactoryFactory
195: .getPooledDataSourceFactory();
196:
197: final String driverClass = jdbpProperties
198: .getProperty("jdbcDriver");
199: final String username = jdbpProperties
200: .getProperty("jdbcUser");
201: final String password = jdbpProperties
202: .getProperty("jdbcPassword");
203: final String url = jdbpProperties
204: .getProperty("jdbcUrl");
205: boolean usePool = true;
206: if (jdbpProperties.getProperty("jdbcUsePool") != null)
207: usePool = Boolean.valueOf(
208: jdbpProperties.getProperty("jdbcUsePool"))
209: .booleanValue();
210:
211: if (usePool) {
212: //Try using a pooled DataSource
213: try {
214: final boolean poolPreparedStatements = Boolean
215: .valueOf(
216: jdbpProperties
217: .getProperty("poolPreparedStatements"))
218: .booleanValue();
219: ds = pdsf.createPooledDataSource(driverClass,
220: username, password, url,
221: poolPreparedStatements);
222:
223: if (LOG.isInfoEnabled())
224: LOG
225: .info("Creating DataSource instance for pooled JDBC");
226:
227: namedDataSources.put(PORTAL_DB, ds);
228: jdbcUrl = url;
229: jdbcUser = username;
230: jdbcDriver = driverClass;
231: dbMetaData = new DatabaseMetaDataImpl(ds);
232: } catch (Exception e) {
233: LOG.error(
234: "Error using pooled JDBC data source.",
235: e);
236: }
237: }
238:
239: if (ds == null && driverClass != null) {
240: //Pooled DataSource isn't being used or failed during creation
241: try {
242: final Driver d = (Driver) Class.forName(
243: driverClass).newInstance();
244: ds = new GenericDataSource(d, url, username,
245: password);
246:
247: if (LOG.isInfoEnabled())
248: LOG
249: .info("Creating DataSource for JDBC native");
250:
251: namedDataSources.put(PORTAL_DB, ds);
252: jdbcUrl = url;
253: jdbcUser = username;
254: jdbcDriver = driverClass;
255: dbMetaData = new DatabaseMetaDataImpl(ds);
256: } catch (Exception e) {
257: LOG.error("JDBC Driver Creation Failed. ("
258: + driverClass + ")", e);
259: }
260: }
261: } finally {
262: jdbcPropStream.close();
263: }
264:
265: } catch (IOException ioe) {
266: LOG.error("An error occured while reading " + PROP_FILE,
267: ioe);
268: }
269: // if we failed to find a datasource then throw a runtime exception
270: if (ds == null) {
271: throw new RuntimeException(
272: "No JDBC DataSource or JNDI DataSource avalable.");
273: }
274: return ds;
275: }
276:
277: /**
278: * Does the JNDI lookup and returns datasource
279: * @param name
280: * @return
281: */
282: private static DataSource getJndiDataSource(String name) {
283:
284: final Long failTime = (Long) namedDbServerFailures.get(name);
285: DataSource ds = null;
286:
287: if (failTime == null
288: || (failTime.longValue() + JNDI_RETRY_TIME) <= System
289: .currentTimeMillis()) {
290: if (failTime != null) {
291: namedDbServerFailures.remove(name);
292: }
293:
294: try {
295: final Context initCtx = new InitialContext();
296: final Context envCtx = (Context) initCtx
297: .lookup(BASE_JNDI_CONTEXT);
298: ds = (DataSource) envCtx.lookup(name);
299:
300: } catch (Throwable t) {
301: //Cache the failure to decrease lookup attempts and reduce log spam.
302: namedDbServerFailures.put(name, new Long(System
303: .currentTimeMillis()));
304: if (LOG.isWarnEnabled())
305: LOG.warn("Error getting DataSource named (" + name
306: + ") from JNDI.", t);
307: }
308: } else {
309: if (LOG.isDebugEnabled()) {
310: final long waitTime = (failTime.longValue() + JNDI_RETRY_TIME)
311: - System.currentTimeMillis();
312: LOG
313: .debug("Skipping lookup on failed JNDI lookup for name ("
314: + name
315: + ") for approximately "
316: + waitTime + " more milliseconds.");
317:
318: }
319: }
320: return ds;
321: }
322:
323: /**
324: * Return the current number of active connections
325: * @return int
326: */
327: public static int getActiveConnectionCount() {
328: return activeConnections.intValue();
329: }
330:
331: /**
332: * Return the maximum number of connections
333: * @return int
334: */
335: public static int getMaxConnectionCount() {
336: return maxConnections;
337: }
338:
339: /**
340: * Gets a database connection to the portal database.
341: * If datasource not available a runtime exception is thrown
342: * @return a database Connection object
343: * @throws DataAccessException if unable to return a connection
344: */
345: public static Connection getConnection() {
346: return getConnection(PORTAL_DB);
347: }
348:
349: /**
350: * Returns a connection produced by a DataSource found in the
351: * JNDI context. The DataSource should be configured and
352: * loaded into JNDI by the J2EE container or may be the portal
353: * default database.
354: *
355: * @param dbName the database name which will be retrieved from
356: * the JNDI context relative to "jdbc/"
357: * @return a database Connection object or <code>null</code> if no Connection
358: */
359: public static Connection getConnection(String dbName) {
360: if (DEFAULT_DATABASE.equals(dbName)) {
361: dbName = PORTAL_DB;
362: }
363: DataSource ds = (DataSource) namedDataSources.get(dbName);
364: if (ds == null) {
365: ds = getDataSource(dbName);
366: }
367:
368: if (ds != null) {
369: try {
370: final long start = System.currentTimeMillis();
371: final Connection c = ds.getConnection();
372: lastDatabase = databaseTimes.add(System
373: .currentTimeMillis()
374: - start); // metric
375: final int current = activeConnections.incrementAndGet();
376: if (current > maxConnections) {
377: maxConnections = current;
378: }
379: return c;
380: } catch (SQLException e) {
381: throw new DataAccessResourceFailureException(
382: "RDBMServices sql error trying to get connection to "
383: + dbName, e);
384: }
385: }
386: // datasource is still null so give up
387: throw new DataAccessResourceFailureException(
388: "RDBMServices fatally misconfigured such that getDataSource() returned null.");
389: }
390:
391: /**
392: * Releases database connection.
393: * Unlike the underlying connection.close(), this method does not throw
394: * SQLException or any other exception. It will fail silently from the
395: * perspective of calling code, logging errors using Commons Logging.
396: * @param con a database Connection object
397: */
398: public static void releaseConnection(final Connection con) {
399: try {
400: int active = activeConnections.decrementAndGet();
401:
402: con.close();
403: } catch (Exception e) {
404: if (LOG.isWarnEnabled())
405: LOG.warn("Error closing Connection: " + con, e);
406: }
407: }
408:
409: //******************************************
410: // Utility Methods
411: //******************************************
412:
413: /**
414: * Close a ResultSet
415: * @param rs a database ResultSet object
416: */
417: public static void closeResultSet(final ResultSet rs) {
418: try {
419: rs.close();
420: } catch (Exception e) {
421: if (LOG.isWarnEnabled())
422: LOG.warn("Error closing ResultSet: " + rs, e);
423: }
424: }
425:
426: /**
427: * Close a Statement
428: * @param st a database Statement object
429: */
430: public static void closeStatement(final Statement st) {
431: try {
432: st.close();
433: } catch (Exception e) {
434: if (LOG.isWarnEnabled())
435: LOG.warn("Error closing Statement: " + st, e);
436: }
437: }
438:
439: /**
440: * Close a PreparedStatement. Simply delegates the call to
441: * {@link #closeStatement(Statement)}
442: * @param pst a database PreparedStatement object
443: * @deprecated Use {@link #closeStatement(Statement)}.
444: */
445: public static void closePreparedStatement(
446: final java.sql.PreparedStatement pst) {
447: closeStatement(pst);
448: }
449:
450: /**
451: * Commit pending transactions.
452: * Unlike the underlying commit(), this method does not throw SQLException or
453: * any other exception. It will fail silently from the perspective of calling code,
454: * logging any errors using Commons Logging.
455: * @param connection
456: */
457: static final public void commit(final Connection connection) {
458: try {
459: connection.commit();
460: } catch (Exception e) {
461: if (LOG.isWarnEnabled())
462: LOG.warn("Error committing Connection: " + connection,
463: e);
464: }
465: }
466:
467: /**
468: * Set auto commit state for the connection.
469: * Unlike the underlying connection.setAutoCommit(), this method does not
470: * throw SQLException or any other Exception. It fails silently from the
471: * perspective of calling code, logging any errors encountered using
472: * Commons Logging.
473: * @param connection
474: * @param autocommit
475: */
476: public static final void setAutoCommit(final Connection connection,
477: boolean autocommit) {
478: try {
479: connection.setAutoCommit(autocommit);
480: } catch (Exception e) {
481: if (LOG.isWarnEnabled())
482: LOG.warn("Error committing Connection: " + connection
483: + " to: " + autocommit, e);
484: }
485: }
486:
487: /**
488: * rollback unwanted changes to the database
489: * @param connection
490: */
491: public static final void rollback(final Connection connection)
492: throws SQLException {
493: try {
494: connection.rollback();
495: } catch (Exception e) {
496: if (LOG.isWarnEnabled())
497: LOG.warn(
498: "Error rolling back Connection: " + connection,
499: e);
500: }
501: }
502:
503: /**
504: * Returns the name of the JDBC driver being used for the default
505: * uPortal database connections.
506: *
507: * @return the name of the JDBC Driver.
508: */
509: public static String getJdbcDriver() {
510: return jdbcDriver;
511: }
512:
513: /**
514: * Gets the JDBC URL of the default uPortal database connections.
515: *
516: */
517: public static String getJdbcUrl() {
518: return jdbcUrl;
519: }
520:
521: /**
522: * Get the username under which we are connecting for the default uPortal
523: * database connections.
524: */
525: public static String getJdbcUser() {
526: return jdbcUser;
527: }
528:
529: //******************************************
530: // Data Type / Formatting Methods
531: //******************************************
532:
533: /**
534: * Return DB format of a boolean. "Y" for true and "N" for false.
535: * @param flag true or false
536: * @return either "Y" or "N"
537: */
538: public static final String dbFlag(final boolean flag) {
539: if (flag)
540: return FLAG_TRUE;
541: else
542: return FLAG_FALSE;
543: }
544:
545: /**
546: * Return boolean value of DB flag, "Y" or "N".
547: * @param flag either "Y" or "N"
548: * @return boolean true or false
549: */
550: public static final boolean dbFlag(final String flag) {
551: return flag != null
552: && (FLAG_TRUE.equalsIgnoreCase(flag) || FLAG_TRUE_OTHER
553: .equalsIgnoreCase(flag));
554: }
555:
556: /**
557: * Make a string SQL safe
558: * @param sql
559: * @return SQL safe string
560: */
561: public static final String sqlEscape(final String sql) {
562: if (sql == null) {
563: return "";
564: } else {
565: int primePos = sql.indexOf("'");
566:
567: if (primePos == -1) {
568: return sql;
569: } else {
570: final StringBuffer sb = new StringBuffer(
571: sql.length() + 4);
572: int startPos = 0;
573:
574: do {
575: sb.append(sql.substring(startPos, primePos + 1));
576: sb.append("'");
577: startPos = primePos + 1;
578: primePos = sql.indexOf("'", startPos);
579: } while (primePos != -1);
580:
581: sb.append(sql.substring(startPos));
582: return sb.toString();
583: }
584: }
585: }
586:
587: /**
588: * Get metadata about the default DataSource.
589: * @return metadata about the default DataSource.
590: */
591: public static IDatabaseMetadata getDbMetaData() {
592: if (dbMetaData == null) {
593: // if metadata not yet populated, call getDataSource(), which
594: // has side effect of populating dbMetaData.
595: getDataSource();
596: }
597: return dbMetaData;
598: }
599:
600: /**
601: * Inner class implementation of DataSource. We currently construct an instance
602: * of this class from the properties defined in rdbm.properties when we are using
603: * rdbm.properties to define our core uPortal DataSource.
604: * @author Eric Dalquist <a href="mailto:edalquist@unicon.net">edalquist@unicon.net</a>
605: */
606: public static class GenericDataSource implements DataSource {
607:
608: final private Driver driverRef;
609: final private String userName;
610: final private String password;
611: final private String jdbcUrl;
612: final private Properties jdbcProperties = new Properties();
613: private PrintWriter log = null;
614:
615: /**
616: * Create a new {@link GenericDataSource} with the wraps the specified
617: * {@link Driver}.
618: *
619: * @param d The {@link Driver} to wrap.
620: */
621: public GenericDataSource(final Driver d, final String url,
622: final String user, final String pass) {
623: String argErr = "";
624: if (d == null) {
625: argErr += "Driver cannot be null. ";
626: }
627: if (url == null) {
628: argErr += "url cannot be null. ";
629: }
630: if (user == null) {
631: argErr += "user cannot be null. ";
632: }
633: if (pass == null) {
634: argErr += "pass cannot be null. ";
635: }
636: if (!argErr.equals("")) {
637: throw new IllegalArgumentException(argErr);
638: }
639:
640: this .driverRef = d;
641: this .jdbcUrl = url;
642: this .userName = user;
643: this .password = pass;
644:
645: this .jdbcProperties.put("user", this .userName);
646: this .jdbcProperties.put("password", this .password);
647: }
648:
649: /**
650: * @see javax.sql.DataSource#getLoginTimeout()
651: */
652: public int getLoginTimeout() throws SQLException {
653: return 0;
654: }
655:
656: /**
657: * @see javax.sql.DataSource#setLoginTimeout(int)
658: */
659: public void setLoginTimeout(final int timeout)
660: throws SQLException {
661: //NOOP our timeout is always 0
662: }
663:
664: /**
665: * @see javax.sql.DataSource#getLogWriter()
666: */
667: public PrintWriter getLogWriter() throws SQLException {
668: return this .log;
669: }
670:
671: /**
672: * @see javax.sql.DataSource#setLogWriter(java.io.PrintWriter)
673: */
674: public void setLogWriter(final PrintWriter out)
675: throws SQLException {
676: this .log = out;
677: }
678:
679: /**
680: * @see javax.sql.DataSource#getConnection()
681: */
682: public Connection getConnection() throws SQLException {
683: return this .getConnection(this .userName, this .password);
684: }
685:
686: /**
687: * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
688: */
689: public Connection getConnection(final String user,
690: final String pass) throws SQLException {
691: final Properties tempProperties = new Properties();
692: tempProperties.putAll(this .jdbcProperties);
693: tempProperties.put("user", user);
694: tempProperties.put("password", pass);
695:
696: return this .driverRef.connect(this .jdbcUrl, tempProperties);
697: }
698:
699: /**
700: * This method was introduced in Java SE 6 as part of the java.sql.Wrapper
701: * interface that javax.sql.DataSource was changed to extend from.
702: *
703: * Returns true if this either implements the interface argument
704: * or is directly or indirectly a wrapper for an object that does.
705: * Returns false otherwise. If this implements the interface then
706: * return true, else if this is a wrapper then return the result
707: * of recursively calling <code>isWrapperFor</code> on the wrapped
708: * object. If this does not implement the interface and is not a
709: * wrapper, return false. This method should be implemented as a
710: * low-cost operation compared to <code>unwrap</code> so that
711: * callers can use this method to avoid expensive <code>unwrap</code>
712: * calls that may fail. If this method returns true then calling
713: * <code>unwrap</code> with the same argument should succeed.
714: *
715: * @param iface a Class defining an interface.
716: * @return true if this implements the interface or directly or
717: * indirectly wraps an object that does.
718: * @throws java.sql.SQLException if an error occurs while determining
719: * whether this is a wrapper for an object with the given interface.
720: * @see java.sql.Wrapper#isWrapperFor(java.lang.Class)
721: */
722: public boolean isWrapperFor(Class<?> iface) throws SQLException {
723: return (iface != null && iface.isAssignableFrom(this
724: .getClass()));
725: }
726:
727: /**
728: * This method was introduced in Java SE 6 as part of the java.sql.Wrapper
729: * interface that javax.sql.DataSource was changed to extend from.
730: *
731: * If the receiving object implements the interface passed in, then
732: * the receiving object or a wrapper for the receiving object should
733: * be returned.
734: *
735: * @param iface A Class defining an interface that the result must
736: * implement
737: * @return an object that implements the interface. May be a proxy for
738: * the actual implementing object.
739: * @throws java.sql.SQLException if a class is not a wrapper for another
740: * class and does not implement the interface passed.
741: * @see java.sql.Wrapper#unwrap(java.lang.Class)
742: */
743: public <T> T unwrap(Class<T> iface) throws SQLException {
744: if (isWrapperFor(iface)) {
745: return iface.cast(this );
746: }
747: throw new SQLException(
748: "org.jasig.portal.RDBMServices.GenericDataSource is not a Wrapper for "
749: + iface.toString());
750: }
751: }
752:
753: /**
754: * Wrapper for/Emulator of PreparedStatement class
755: * @deprecated Instead of this class a wrapper around the DataSource, Connection and Prepared statement should be done in {@link DatabaseMetaDataImpl}
756: */
757: public static final class PreparedStatement {
758: private String query;
759: private String activeQuery;
760: private java.sql.PreparedStatement pstmt;
761: private Statement stmt;
762: private int lastIndex;
763:
764: public PreparedStatement(Connection con, String query)
765: throws SQLException {
766: this .query = query;
767: activeQuery = this .query;
768: if (dbMetaData.supportsPreparedStatements()) {
769: pstmt = con.prepareStatement(query);
770: } else {
771: stmt = con.createStatement();
772: }
773: }
774:
775: public void clearParameters() throws SQLException {
776: if (dbMetaData.supportsPreparedStatements()) {
777: pstmt.clearParameters();
778: } else {
779: lastIndex = 0;
780: activeQuery = query;
781: }
782: }
783:
784: public void setDate(int index, java.sql.Date value)
785: throws SQLException {
786: if (dbMetaData.supportsPreparedStatements()) {
787: pstmt.setDate(index, value);
788: } else {
789: if (index != lastIndex + 1) {
790: throw new SQLException("Out of order index");
791: } else {
792: int pos = activeQuery.indexOf("?");
793: if (pos == -1) {
794: throw new SQLException("Missing '?'");
795: }
796: activeQuery = activeQuery.substring(0, pos)
797: + dbMetaData.sqlTimeStamp(value)
798: + activeQuery.substring(pos + 1);
799: lastIndex = index;
800: }
801: }
802: }
803:
804: public void setInt(int index, int value) throws SQLException {
805: if (dbMetaData.supportsPreparedStatements()) {
806: pstmt.setInt(index, value);
807: } else {
808: if (index != lastIndex + 1) {
809: throw new SQLException("Out of order index");
810: } else {
811: int pos = activeQuery.indexOf("?");
812: if (pos == -1) {
813: throw new SQLException("Missing '?'");
814: }
815: activeQuery = activeQuery.substring(0, pos) + value
816: + activeQuery.substring(pos + 1);
817: lastIndex = index;
818: }
819: }
820: }
821:
822: public void setNull(int index, int sqlType) throws SQLException {
823: if (dbMetaData.supportsPreparedStatements()) {
824: pstmt.setNull(index, sqlType);
825: } else {
826: if (index != lastIndex + 1) {
827: throw new SQLException("Out of order index");
828: } else {
829: int pos = activeQuery.indexOf("?");
830: if (pos == -1) {
831: throw new SQLException("Missing '?'");
832: }
833: activeQuery = activeQuery.substring(0, pos)
834: + "NULL" + activeQuery.substring(pos + 1);
835: lastIndex = index;
836: }
837: }
838: }
839:
840: public void setString(int index, String value)
841: throws SQLException {
842: if (value == null || value.length() == 0) {
843: setNull(index, java.sql.Types.VARCHAR);
844: } else {
845: if (dbMetaData.supportsPreparedStatements()) {
846: pstmt.setString(index, value);
847: } else {
848: if (index != lastIndex + 1) {
849: throw new SQLException("Out of order index");
850: } else {
851: int pos = activeQuery.indexOf("?");
852: if (pos == -1) {
853: throw new SQLException("Missing '?'");
854: }
855: activeQuery = activeQuery.substring(0, pos)
856: + "'" + sqlEscape(value) + "'"
857: + activeQuery.substring(pos + 1);
858: lastIndex = index;
859: }
860: }
861: }
862: }
863:
864: public void setTimestamp(int index, java.sql.Timestamp value)
865: throws SQLException {
866: if (dbMetaData.supportsPreparedStatements()) {
867: pstmt.setTimestamp(index, value);
868: } else {
869: if (index != lastIndex + 1) {
870: throw new SQLException("Out of order index");
871: } else {
872: int pos = activeQuery.indexOf("?");
873: if (pos == -1) {
874: throw new SQLException("Missing '?'");
875: }
876: activeQuery = activeQuery.substring(0, pos)
877: + dbMetaData.sqlTimeStamp(value)
878: + activeQuery.substring(pos + 1);
879: lastIndex = index;
880: }
881: }
882: }
883:
884: public ResultSet executeQuery() throws SQLException {
885: if (dbMetaData.supportsPreparedStatements()) {
886: return pstmt.executeQuery();
887: } else {
888: return stmt.executeQuery(activeQuery);
889: }
890: }
891:
892: public int executeUpdate() throws SQLException {
893: if (dbMetaData.supportsPreparedStatements()) {
894: return pstmt.executeUpdate();
895: } else {
896: return stmt.executeUpdate(activeQuery);
897: }
898: }
899:
900: public String toString() {
901: if (dbMetaData.supportsPreparedStatements()) {
902: return query;
903: } else {
904: return activeQuery;
905: }
906: }
907:
908: public void close() throws SQLException {
909: if (dbMetaData.supportsPreparedStatements()) {
910: pstmt.close();
911: } else {
912: stmt.close();
913: }
914: }
915: }
916:
917: /**
918: * @return Returns the getDatasourceFromJndi.
919: */
920: public static boolean isGetDatasourceFromJndi() {
921: return getDatasourceFromJndi;
922: }
923:
924: /**
925: * @param getDatasourceFromJndi The getDatasourceFromJndi to set.
926: */
927: public static void setGetDatasourceFromJndi(
928: boolean getDatasourceFromJndi) {
929: RDBMServices.getDatasourceFromJndi = getDatasourceFromJndi;
930: }
931: }
|