001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: * $Id: Datasource.java 3636 2007-01-09 11:28:24Z gbevin $
007: */
008: package com.uwyn.rife.database;
009:
010: import com.uwyn.rife.database.exceptions.*;
011:
012: import com.uwyn.rife.database.capabilities.CapabilitiesCompensator;
013: import com.uwyn.rife.database.types.SqlConversion;
014: import com.uwyn.rife.tools.ExceptionUtils;
015: import com.uwyn.rife.tools.StringUtils;
016: import java.sql.Connection;
017: import java.sql.DriverManager;
018: import java.sql.SQLException;
019: import java.util.HashMap;
020: import java.util.logging.Logger;
021: import javax.sql.DataSource;
022:
023: /**
024: * Contains all the information required to connect to a database and
025: * centralizes the creation of connections to a database. These connections can
026: * optionally be pooled.
027: * <p>
028: * The initial connection will only be made and the pool will only be
029: * initialized when a connection is obtained for the first time. The
030: * instantiation only stores the connection parameters.
031: * <p>
032: * A <code>Datasource</code> also defines the type of database that is used for
033: * all database-independent logic such as sql to java and java to sql type
034: * mappings, query builders, database-based authentication, database-based
035: * scheduling, ... The key that identifies a supported type is the class name of
036: * the jdbc driver.
037: * <p>
038: * A <code>Datasource</code> instance can be created through it's constructor,
039: * but it's recommended to work with a <code>Datasources</code> collection
040: * that is created and populated through XML. This can easily be achieved by
041: * using a <code>ParticipantDatasources</code> which participates in the
042: * application-wide repository.
043: * <p>
044: * Once a connection has been obtained from a pooled datasource, modifying its
045: * connection parameters is not possible anymore, a new instance has to be
046: * created to set the parameters to different values.
047: *
048: * @author Geert Bevin (gbevin[remove] at uwyn dot com)
049: * @version $Revision: 3636 $
050: * @see com.uwyn.rife.database.Datasources
051: * @see com.uwyn.rife.database.Xml2Datasources
052: * @see com.uwyn.rife.rep.Rep
053: * @see com.uwyn.rife.rep.participants.ParticipantDatasources
054: * @since 1.0
055: */
056: public class Datasource implements Cloneable {
057: static HashMap<String, String> sDriverAliases = new HashMap<String, String>();
058: static HashMap<String, String> sDriverNames = new HashMap<String, String>();
059:
060: static {
061: sDriverAliases.put("org.gjt.mm.mysql.Driver",
062: "com.mysql.jdbc.Driver");
063: sDriverAliases.put("in.co.daffodil.db.rmi.RmiDaffodilDBDriver",
064: "in.co.daffodil.db.jdbc.DaffodilDBDriver");
065: sDriverAliases.put("oracle.jdbc.OracleDriver",
066: "oracle.jdbc.driver.OracleDriver");
067: sDriverAliases.put("org.apache.derby.jdbc.ClientDriver",
068: "org.apache.derby.jdbc.EmbeddedDriver");
069:
070: sDriverNames.put("Apache Derby Embedded JDBC Driver",
071: "org.apache.derby.jdbc.EmbeddedDriver");
072: sDriverNames.put("Apache Derby Network Client JDBC Driver",
073: "org.apache.derby.jdbc.EmbeddedDriver");
074: sDriverNames.put("DaffodilDBDriver",
075: "in.co.daffodil.db.jdbc.DaffodilDBDriver");
076: sDriverNames.put("H2 JDBC Driver", "org.h2.Driver");
077: sDriverNames.put("HSQL Database Engine Driver",
078: "org.hsqldb.jdbcDriver");
079: sDriverNames.put("Jaybird JCA/JDBC driver",
080: "org.firebirdsql.jdbc.FBDriver");
081: sDriverNames.put("Mckoi JDBC Driver", "com.mckoi.JDBCDriver");
082: sDriverNames.put("MySQL-AB JDBC Driver",
083: "com.mysql.jdbc.Driver");
084: sDriverNames.put("Oracle JDBC driver",
085: "oracle.jdbc.driver.OracleDriver");
086: sDriverNames.put("PostgreSQL Native Driver",
087: "org.postgresql.Driver");
088: }
089:
090: private String mDriver = null;
091: private String mUrl = null;
092: private String mUser = null;
093: private String mPassword = null;
094: private SqlConversion mSqlConversion = null;
095: private CapabilitiesCompensator mCapabilitiesCompensator = null;
096: private ConnectionPool mConnectionPool = new ConnectionPool();
097: private DataSource mDataSource = null;
098:
099: /**
100: * Instantiates a new <code>Datasource</code> object with no connection
101: * information. The setters need to be used afterwards to provide each
102: * required parameter before the <code>Datasource</code> can be used.
103: *
104: * @see #setDriver(String)
105: * @see #setUrl(String)
106: * @see #setUser(String)
107: * @see #setPassword(String)
108: * @see #setPoolsize(int)
109: * @see #setDataSource(DataSource)
110: *
111: * @since 1.0
112: */
113: public Datasource() {
114: }
115:
116: /**
117: * Instantiates a new <code>Datasource</code> object with all the
118: * connection parameters that are required.
119: *
120: * @param driver the fully-qualified classname of the jdbc driver that will
121: * be used to connect to the database
122: * @param url the connection url which identifies the database to which the
123: * connection will be made, this is entirely driver-dependent
124: * @param user the user that will be used to connect to the database
125: * @param password the password that will be used to connect to the database
126: * @param poolsize the size of the connection pool, <code>0</code> means
127: * that the connections will not be pooled
128: *
129: * @since 1.0
130: */
131: public Datasource(String driver, String url, String user,
132: String password, int poolsize) {
133: setDriver(driver);
134: setUrl(url);
135: setUser(user);
136: setPassword(password);
137: setPoolsize(poolsize);
138:
139: assert mDriver != null;
140: assert mDriver.length() > 0;
141: assert mUrl != null;
142: assert mUrl.length() > 0;
143: }
144:
145: /**
146: * Instantiates a new <code>Datasource</code> object from a standard
147: * <code>javax.sql.DataSource</code>.
148: * <p>
149: * The driver will be detected from the connection that is provided by this
150: * <code>DataSource</code>. If the driver couldn't be detected, an exception
151: * will be thrown upon connect.
152: *
153: * @param dataSource the standard datasource that will be used to obtain the
154: * connection
155: * @param poolsize the size of the connection pool, <code>0</code> means
156: * that the connections will not be pooled
157: *
158: * @since 1.3
159: */
160: public Datasource(DataSource dataSource, int poolsize) {
161: setDataSource(dataSource);
162: setPoolsize(poolsize);
163:
164: assert dataSource != null;
165: }
166:
167: /**
168: * Instantiates a new <code>Datasource</code> object from a standard
169: * <code>javax.sql.DataSource</code>.
170: *
171: * @param dataSource the standard datasource that will be used to obtain the
172: * connection
173: * @param driver the fully-qualified classname of the jdbc driver that will
174: * be used to provide an identifier for the database abstraction functionalities,
175: * <code>null</code> will let RIFE try to figure it out by itself
176: * @param user the user that will be used to connect to the database
177: * @param password the password that will be used to connect to the database
178: * @param poolsize the size of the connection pool, <code>0</code> means
179: * that the connections will not be pooled
180: *
181: * @since 1.3
182: */
183: public Datasource(DataSource dataSource, String driver,
184: String user, String password, int poolsize) {
185: setDataSource(dataSource);
186: mDriver = driver;
187: mSqlConversion = null;
188: setUser(user);
189: setPassword(password);
190: setPoolsize(poolsize);
191:
192: assert dataSource != null;
193: }
194:
195: /**
196: * Creates a new connection by using all the parameters that have been
197: * defined in the <code>Datasource</code>.
198: *
199: * @return the newly created <code>DbConnection</code> instance
200: *
201: * @throws DatabaseException when an error occured during the creation of
202: * the connection
203: *
204: * @since 1.0
205: */
206: DbConnection createConnection() throws DatabaseException {
207: Connection connection = null;
208:
209: if (this .mDataSource != null) {
210: // try to create a datasource connection
211: if (null != mUser && null != mPassword) {
212: try {
213: connection = this .mDataSource.getConnection(mUser,
214: mPassword);
215: } catch (SQLException e) {
216: throw new ConnectionOpenErrorException(null, mUser,
217: mPassword, e);
218: }
219: } else {
220: try {
221: connection = this .mDataSource.getConnection();
222: } catch (SQLException e) {
223: throw new ConnectionOpenErrorException(null, e);
224: }
225: }
226:
227: if (null == mDriver) {
228: try {
229: String driver_name = connection.getMetaData()
230: .getDriverName();
231: mDriver = sDriverNames.get(driver_name);
232: if (null == mDriver) {
233: throw new UnsupportedDriverNameException(
234: driver_name);
235: }
236: } catch (SQLException e) {
237: throw new DriverNameRetrievalErrorException(e);
238: }
239: }
240: } else {
241:
242: // obtain the jdbc driver instance
243: try {
244: Class.forName(mDriver).newInstance();
245: } catch (InstantiationException e) {
246: throw new DriverInstantiationErrorException(mDriver, e);
247: } catch (ClassNotFoundException e) {
248: throw new DriverInstantiationErrorException(mDriver, e);
249: } catch (IllegalAccessException e) {
250: throw new DriverInstantiationErrorException(mDriver, e);
251: }
252:
253: // try to create a jdbc connection
254: if (null != mUser && null != mPassword) {
255: try {
256: connection = DriverManager.getConnection(mUrl,
257: mUser, mPassword);
258: } catch (SQLException e) {
259: throw new ConnectionOpenErrorException(mUrl, mUser,
260: mPassword, e);
261: }
262: } else {
263: try {
264: connection = DriverManager.getConnection(mUrl);
265: } catch (SQLException e) {
266: throw new ConnectionOpenErrorException(mUrl, e);
267: }
268: }
269: }
270:
271: // returns a new DbConnection instance with contains the new jdbc
272: // connection and is linked to this datasource
273: return new DbConnection(connection, this );
274: }
275:
276: /**
277: * Retrieves a free database connection. If no connection pool is used, a
278: * new <code>DbConnection</code> will always be created, otherwise the first
279: * available connection in the pool will be returned.
280: *
281: * @return a free <code>DbConnection</code> instance which can be used to
282: * create an execute statements
283: *
284: * @throws DatabaseException when errors occured during the creation of a
285: * new connection or during the obtainance of a connection from the pool
286: *
287: * @since 1.0
288: */
289: public DbConnection getConnection() throws DatabaseException {
290: return mConnectionPool.getConnection(this );
291: }
292:
293: /**
294: * Retrieves the fully qualified class name of the jdbc driver that's used
295: * by this <code>Datasource</code>.
296: *
297: * @return a <code>String</code> with the name of the jdbc driver; or
298: * <p>
299: * <code>null</code> if the driver hasn't been set
300: *
301: * @see #setDriver(String)
302: * @see #getAliasedDriver()
303: *
304: * @since 1.0
305: */
306: public String getDriver() {
307: // make sure that a JNDI connection has been made first, so that the database name can be looked up
308: if (mDataSource != null && null == mDriver) {
309: getConnection();
310: }
311:
312: return mDriver;
313: }
314:
315: /**
316: * Retrieves the fully qualified class name of the jdbc driver that's used
317: * by this <code>Datasource</code>. Instead of straight retrieval of the
318: * internal value, it looks for jdbc driver aliases and changes the driver
319: * classname if it's not supported by RIFE, but its alias is.
320: *
321: * @return a <code>String</code> with the name of the jdbc driver; or
322: * <p>
323: * <code>null</code> if the driver hasn't been set
324: *
325: * @see #getDriver()
326: * @see #setDriver(String)
327: *
328: * @since 1.0
329: */
330: public String getAliasedDriver() {
331: String driver = getDriver();
332: if (null == driver) {
333: return null;
334: }
335:
336: String alias = sDriverAliases.get(driver);
337:
338: if (null == alias) {
339: return driver;
340: }
341:
342: return alias;
343: }
344:
345: /**
346: * Sets the jdbc driver that will be used to connect to the database. This
347: * has to be a fully qualified class name and will be looked up through
348: * reflection. It's not possible to change the driver after a connection
349: * has been obtained from a pooled datasource.
350: * <p>
351: * If the class name can't be resolved, an exception is thrown during the
352: * creation of the first connection.
353: *
354: * @param driver a <code>String</code> with the fully qualified class name
355: * of the jdbc driver that will be used
356: *
357: * @see #getDriver()
358: *
359: * @since 1.0
360: */
361: public void setDriver(String driver) {
362: if (null == driver)
363: throw new IllegalArgumentException("driver can't be null.");
364: if (0 == driver.length())
365: throw new IllegalArgumentException("driver can't be empty.");
366: if (mConnectionPool.isInitialized())
367: throw new IllegalArgumentException(
368: "driver can't be changed after the connection pool has been set up.");
369:
370: mDriver = driver;
371: mSqlConversion = null;
372: }
373:
374: /**
375: * Retrieves the standard datasource that is used by this RIFE datasource
376: * to obtain a database connection.
377: *
378: * @return a standard <code>DataSource</code>; or
379: * <p>
380: * <code>null</code> if the standard datasource hasn't been set
381: *
382: * @see #setDataSource(DataSource)
383: *
384: * @since 1.3
385: */
386: public DataSource getDataSource() {
387: return mDataSource;
388: }
389:
390: /**
391: * Sets the standard datasource that will be used to connect to the database.
392: *
393: * @param dataSource a standard <code>DataSource</code> that will be used
394: * by this RIFE datasource to obtain a database connection.
395: *
396: * @see #getDataSource()
397: *
398: * @since 1.0
399: */
400: public void setDataSource(DataSource dataSource) {
401: mDataSource = dataSource;
402: }
403:
404: /**
405: * Retrieves the connection url that's used by this <code>Datasource</code>.
406: *
407: * @return a <code>String</code> with the connection url; or
408: * <p>
409: * <code>null</code> if the url hasn't been set
410: *
411: * @see #setUrl(String)
412: *
413: * @since 1.0
414: */
415: public String getUrl() {
416: return mUrl;
417: }
418:
419: /**
420: * Sets the connection url that will be used to connect to the database.
421: * It's not possible to change the url after a connection has been obtained
422: * from a pooled datasource.
423: *
424: * @param url a <code>String</code> with the connection url that will be
425: * used
426: *
427: * @see #getUrl()
428: *
429: * @since 1.0
430: */
431: public void setUrl(String url) {
432: if (null == url)
433: throw new IllegalArgumentException("url can't be null.");
434: if (0 == url.length())
435: throw new IllegalArgumentException("url can't be empty.");
436: if (mConnectionPool.isInitialized())
437: throw new IllegalArgumentException(
438: "url can't be changed after the connection pool has been set up.");
439:
440: mUrl = url;
441: }
442:
443: /**
444: * Retrieves the user that's used by this <code>Datasource</code>.
445: *
446: * @return a <code>String>/code> with the user; or
447: * <p>
448: * <code>null</code> if the user hasn't been set
449: *
450: * @see #setUser(String)
451: *
452: * @since 1.0
453: */
454: public String getUser() {
455: return mUser;
456: }
457:
458: /**
459: * Sets the user that will be used to connect to the database.
460: * It's not possible to change the user after a connection has been obtained
461: * from a pooled datasource.
462: *
463: * @param user a <code>String</code> with the user that will be used
464: *
465: * @see #getUser()
466: *
467: * @since 1.0
468: */
469: public void setUser(String user) {
470: if (mConnectionPool.isInitialized())
471: throw new IllegalArgumentException(
472: "user can't be changed after the connection pool has been set up.");
473:
474: mUser = user;
475: }
476:
477: /**
478: * Retrieves the password that's used by this <code>Datasource</code>.
479: *
480: * @return a <code>String>/code> with the password; or
481: * <p>
482: * <code>null</code> if the password hasn't been set
483: *
484: * @see #setPassword(String)
485: *
486: * @since 1.0
487: */
488: public String getPassword() {
489: return mPassword;
490: }
491:
492: /**
493: * Sets the password that will be used to connect to the database.
494: * It's not possible to change the password after a connection has been
495: * obtained from a pooled datasource.
496: *
497: * @param password a <code>String</code> with the password that will be used
498: *
499: * @see #getPassword()
500: *
501: * @since 1.0
502: */
503: public void setPassword(String password) {
504: if (mConnectionPool.isInitialized())
505: throw new IllegalArgumentException(
506: "password can't be changed after the connection pool has been set up.");
507:
508: mPassword = password;
509: }
510:
511: /**
512: * Retrieves the size of the pool that's used by this
513: * <code>Datasource</code>.
514: *
515: * @return a positive <code>int</code> with the size of the pool; or
516: * <p>
517: * <code>0</code> if no pool is being used
518: *
519: * @see #isPooled()
520: * @see #setPoolsize(int)
521: *
522: * @since 1.0
523: */
524: public int getPoolsize() {
525: return mConnectionPool.getPoolsize();
526: }
527:
528: /**
529: * Indicates whether the <code>Datasource</code> uses a connection pool or
530: * not
531: *
532: * @return <code>true</code> if a pool is being used by this
533: * <code>Datasource</code>; or
534: * <p>
535: * <code>false</code> otherwise
536: *
537: * @see #getPoolsize()
538: * @see #setPoolsize(int)
539: *
540: * @since 1.0
541: */
542: public boolean isPooled() {
543: return getPoolsize() > 0;
544: }
545:
546: /**
547: * Sets the size of the connection pool that will be used to connect to the
548: * database.
549: *
550: * @param poolsize a positive <code>int</code> with the size of the pool,
551: * providing <code>0</code> will disable the use of a connection pool for
552: * this <code>Datasource</code>.
553: *
554: * @see #getPoolsize()
555: * @see #isPooled()
556: *
557: * @since 1.0
558: */
559: public void setPoolsize(int poolsize) {
560: if (poolsize < 0)
561: throw new IllegalArgumentException(
562: "poolsize can't be negative.");
563:
564: mConnectionPool.setPoolsize(poolsize);
565: }
566:
567: /**
568: * Retrieves the sql to java and java to sql type mapping logic that
569: * corresponds to the provide driver class name.
570: *
571: * @return a <code>SqlConversion</code> instance that is able to perform
572: * the required type conversions for the provided jdbc driver
573: *
574: * @throws UnsupportedJdbcDriverException when the provided jdbc isn't
575: * supported
576: *
577: * @since 1.0
578: */
579: public SqlConversion getSqlConversion()
580: throws UnsupportedJdbcDriverException {
581: String driver = getDriver();
582: if (null == mSqlConversion && null != driver) {
583: try {
584: mSqlConversion = (SqlConversion) Class
585: .forName(
586: "com.uwyn.rife.database.types.databasedrivers."
587: + StringUtils
588: .encodeClassname(getAliasedDriver()))
589: .newInstance();
590: } catch (InstantiationException e) {
591: throw new UnsupportedJdbcDriverException(driver, e);
592: } catch (IllegalAccessException e) {
593: throw new UnsupportedJdbcDriverException(driver, e);
594: } catch (ClassNotFoundException e) {
595: throw new UnsupportedJdbcDriverException(driver, e);
596: }
597: }
598:
599: return mSqlConversion;
600: }
601:
602: /**
603: * Retrieves a <code>CapabilitiesCompensator</code> instance that is able to
604: * compensate for certain missing database features
605: *
606: * @return the requested <code>CapabilitiesCompensator</code> instance
607: *
608: * @throws UnsupportedJdbcDriverException when the provided jdbc isn't
609: * supported
610: *
611: * @since 1.0
612: */
613: public CapabilitiesCompensator getCapabilitiesCompensator()
614: throws UnsupportedJdbcDriverException {
615: String driver = getDriver();
616: if (null == mCapabilitiesCompensator && null != driver) {
617: try {
618: mCapabilitiesCompensator = (CapabilitiesCompensator) Class
619: .forName(
620: "com.uwyn.rife.database.capabilities."
621: + StringUtils
622: .encodeClassname(getAliasedDriver()))
623: .newInstance();
624: } catch (InstantiationException e) {
625: throw new UnsupportedJdbcDriverException(driver, e);
626: } catch (IllegalAccessException e) {
627: throw new UnsupportedJdbcDriverException(driver, e);
628: } catch (ClassNotFoundException e) {
629: throw new UnsupportedJdbcDriverException(driver, e);
630: }
631: }
632:
633: return mCapabilitiesCompensator;
634: }
635:
636: /**
637: * Returns a hash code value for the <code>Datasource</code>. This method is
638: * supported for the benefit of hashtables such as those provided by
639: * <code>java.util.Hashtable</code>.
640: *
641: * @return an <code>int</code> with the hash code value for this
642: * <code>Datasource</code>.
643: *
644: * @see #equals(Object)
645: *
646: * @since 1.0
647: */
648: public int hashCode() {
649: int dataSourceHash = mDataSource == null ? 1 : mDataSource
650: .hashCode();
651: int driverHash = mDriver == null ? 1 : mDriver.hashCode();
652: int urlHash = mUrl == null ? 1 : mUrl.hashCode();
653: int userHash = mUser == null ? 1 : mUser.hashCode();
654: int passwordHash = mPassword == null ? 1 : mPassword.hashCode();
655: return dataSourceHash * driverHash * urlHash * userHash
656: * passwordHash;
657: }
658:
659: /**
660: * Indicates whether some other object is "equal to" this one. Only the
661: * real connection parameters will be taken into account. The size of the
662: * pool is not used for the comparison.
663: *
664: * @param object the reference object with which to compare.
665: * @return <code>true</code> if this object is the same as the object
666: * argument; or
667: * <p>
668: * <code>false</code> otherwise
669: *
670: * @see #hashCode()
671: *
672: * @since 1.0
673: */
674: public boolean equals(Object object) {
675: if (this == object) {
676: return true;
677: }
678:
679: if (null == object) {
680: return false;
681: }
682:
683: if (!(object instanceof Datasource)) {
684: return false;
685: }
686:
687: Datasource other_datasource = (Datasource) object;
688: if (!other_datasource.getDriver().equals(getDriver())) {
689: return false;
690: }
691: if (other_datasource.getUrl() != null || getUrl() != null) {
692: if (null == other_datasource.getUrl() || null == getUrl()) {
693: return false;
694: }
695: if (!other_datasource.getUrl().equals(getUrl())) {
696: return false;
697: }
698: }
699: if (other_datasource.getDataSource() != null
700: || getDataSource() != null) {
701: if (null == other_datasource.getDataSource()
702: || null == getDataSource()) {
703: return false;
704: }
705: if (!other_datasource.getDataSource().equals(
706: getDataSource())) {
707: return false;
708: }
709: }
710: if (other_datasource.getUser() != null || getUser() != null) {
711: if (null == other_datasource.getUser() || null == getUser()) {
712: return false;
713: }
714: if (!other_datasource.getUser().equals(getUser())) {
715: return false;
716: }
717: }
718: if (other_datasource.getPassword() != null
719: || getPassword() != null) {
720: if (null == other_datasource.getPassword()
721: || null == getPassword()) {
722: return false;
723: }
724: if (!other_datasource.getPassword().equals(getPassword())) {
725: return false;
726: }
727: }
728:
729: return true;
730: }
731:
732: /**
733: * Simply clones the instance with the default clone method. This creates a
734: * shallow copy of all fields and the clone will in fact just be another
735: * reference to the same underlying data. The independence of each cloned
736: * instance is consciously not respected since they rely on resources
737: * that can't be cloned.
738: *
739: * @since 1.0
740: */
741: public Datasource clone() {
742: Datasource other = null;
743: try {
744: other = (Datasource) super .clone();
745: } catch (CloneNotSupportedException e) {
746: // this should never happen
747: Logger.getLogger("com.uwyn.rife.database").severe(
748: ExceptionUtils.getExceptionStackTrace(e));
749: return null;
750: }
751:
752: other.mSqlConversion = mSqlConversion;
753: other.mConnectionPool = mConnectionPool;
754:
755: return other;
756: }
757:
758: /**
759: * Cleans up all connections that have been reserved by this datasource.
760: *
761: * @throws DatabaseException when an error occured during the cleanup
762: *
763: * @since 1.0
764: */
765: public void cleanup() throws DatabaseException {
766: mConnectionPool.cleanup();
767: }
768:
769: /**
770: * Retrieves the instance of the connection pool that is provided by this
771: * dtaasource.
772: *
773: * @return the requested instance of <code>ConnectionPool</code>
774: *
775: */
776: public ConnectionPool getPool() {
777: return mConnectionPool;
778: }
779: }
|