001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.sql;
031:
032: import com.caucho.config.program.ConfigProgram;
033: import com.caucho.config.program.ContainerProgram;
034: import com.caucho.config.Config;
035: import com.caucho.config.ConfigException;
036: import com.caucho.config.program.PropertyValueProgram;
037: import com.caucho.config.types.InitParam;
038: import com.caucho.management.j2ee.J2EEManagedObject;
039: import com.caucho.management.j2ee.JDBCDriver;
040: import com.caucho.naming.Jndi;
041: import com.caucho.tools.profiler.ConnectionPoolDataSourceWrapper;
042: import com.caucho.tools.profiler.DriverWrapper;
043: import com.caucho.tools.profiler.ProfilerPoint;
044: import com.caucho.tools.profiler.ProfilerPointConfig;
045: import com.caucho.tools.profiler.XADataSourceWrapper;
046: import com.caucho.util.Alarm;
047: import com.caucho.util.L10N;
048: import com.caucho.lifecycle.Lifecycle;
049:
050: import javax.annotation.PostConstruct;
051: import javax.resource.spi.ManagedConnectionFactory;
052: import javax.sql.ConnectionPoolDataSource;
053: import javax.sql.PooledConnection;
054: import javax.sql.XADataSource;
055: import java.lang.reflect.*;
056: import java.sql.Connection;
057: import java.sql.Driver;
058: import java.sql.SQLException;
059: import java.util.HashMap;
060: import java.util.Iterator;
061: import java.util.Properties;
062: import java.util.logging.Level;
063: import java.util.logging.Logger;
064:
065: /**
066: * Configures the database driver.
067: */
068: public class DriverConfig {
069: protected static final Logger log = Logger
070: .getLogger(DriverConfig.class.getName());
071: private static final L10N L = new L10N(DriverConfig.class);
072:
073: private static final int TYPE_UNKNOWN = 0;
074: private static final int TYPE_DRIVER = 1;
075: private static final int TYPE_POOL = 2;
076: private static final int TYPE_XA = 3;
077: private static final int TYPE_JCA = 4;
078:
079: /**
080: * The beginning of the URL used to connect to a database with
081: * this pooled connection driver.
082: */
083: private static final String URL_PREFIX = "jdbc:caucho:";
084:
085: /**
086: * The key used to look into the properties passed to the
087: * connect method to find the username.
088: */
089: public static final String PROPERTY_USER = "user";
090: /**
091: * The key used to look into the properties passed to the
092: * connect method to find the password.
093: */
094: public static final String PROPERTY_PASSWORD = "password";
095:
096: private DBPoolImpl _dbPool;
097:
098: private Class _driverClass;
099:
100: private String _driverURL;
101: private String _user;
102: private String _password;
103: private Properties _info;
104:
105: private ContainerProgram _init = new ContainerProgram();
106:
107: private int _driverType;
108: private Object _driverObject;
109:
110: private ManagedConnectionFactory _jcaDataSource;
111: private ConnectionPoolDataSource _poolDataSource;
112: private XADataSource _xaDataSource;
113: private Driver _driver;
114:
115: private Lifecycle _lifecycle = new Lifecycle();
116: private DriverAdmin _admin = new DriverAdmin(this );
117:
118: // statistics
119: private long _connectionCountTotal;
120: private long _connectionFailCountTotal;
121: private long _lastFailTime;
122:
123: private ProfilerPoint _profilerPoint;
124:
125: /**
126: * Null constructor for the Driver interface; called by the JNDI
127: * configuration. Applications should not call this directly.
128: */
129: public DriverConfig(DBPoolImpl pool) {
130: _dbPool = pool;
131:
132: _info = new Properties();
133: }
134:
135: /**
136: * Returns the DBPool.
137: */
138: public DBPoolImpl getDBPool() {
139: return _dbPool;
140: }
141:
142: /**
143: * Sets the driver as data source.
144: */
145: public void setDriverType(String type) throws ConfigException {
146: if ("ConnectionPoolDataSource".equals(type)) {
147: _driverType = TYPE_POOL;
148: } else if ("XADataSource".equals(type)) {
149: _driverType = TYPE_XA;
150: } else if ("ManagedConnectionFactory".equals(type)) {
151: _driverType = TYPE_JCA;
152: } else if ("Driver".equals(type)) {
153: _driverType = TYPE_DRIVER;
154: } else
155: throw new ConfigException(
156: L
157: .l("'{0}' is an unknown driver type. Valid types are 'ConnectionPoolDataSource', 'XADataSource' and 'Driver'"));
158: }
159:
160: /**
161: * Sets the driver as data source.
162: */
163: public void setDataSource(Object dataSource) throws ConfigException {
164: if (dataSource instanceof String)
165: dataSource = Jndi.lookup((String) dataSource);
166:
167: if (_driverType == TYPE_XA)
168: _xaDataSource = (XADataSource) dataSource;
169: else if (_driverType == TYPE_POOL)
170: _poolDataSource = (ConnectionPoolDataSource) dataSource;
171: else if (dataSource instanceof XADataSource)
172: _xaDataSource = (XADataSource) dataSource;
173: else if (dataSource instanceof ConnectionPoolDataSource)
174: _poolDataSource = (ConnectionPoolDataSource) dataSource;
175: else if (dataSource instanceof ManagedConnectionFactory)
176: _jcaDataSource = (ManagedConnectionFactory) dataSource;
177: else
178: throw new ConfigException(
179: L
180: .l(
181: "data-source '{0}' is of type '{1}' which does not implement XADataSource or ConnectionPoolDataSource.",
182: dataSource, dataSource.getClass()
183: .getName()));
184: }
185:
186: /**
187: * Returns the JDBC driver class for the pooled object.
188: */
189: public Class getDriverClass() {
190: return _driverClass;
191: }
192:
193: /**
194: * Sets the JDBC driver class underlying the pooled object.
195: */
196: public void setType(Class driverClass) throws ConfigException {
197: _driverClass = driverClass;
198:
199: if (!Driver.class.isAssignableFrom(driverClass)
200: && !XADataSource.class.isAssignableFrom(driverClass)
201: && !ConnectionPoolDataSource.class
202: .isAssignableFrom(driverClass)
203: && !ManagedConnectionFactory.class
204: .isAssignableFrom(driverClass))
205: throw new ConfigException(L.l(
206: "'{0}' is not a valid database type.", driverClass
207: .getName()));
208:
209: Config.checkCanInstantiate(driverClass);
210: }
211:
212: public String getType() {
213: return _driverClass.getName();
214: }
215:
216: /**
217: * Returns the connection's JDBC url.
218: */
219: public String getURL() {
220: return _driverURL;
221: }
222:
223: /**
224: * Sets the connection's JDBC url.
225: */
226: public void setURL(String url) {
227: _driverURL = url;
228:
229: _lifecycle.setName("JdbcDriver[" + url + "]");
230: }
231:
232: /**
233: * Adds to the builder program.
234: */
235: public void addBuilderProgram(ConfigProgram program) {
236: _init.addProgram(program);
237: }
238:
239: /**
240: * Returns the connection's user.
241: */
242: public String getUser() {
243: return _user;
244: }
245:
246: /**
247: * Sets the connection's user.
248: */
249: public void setUser(String user) {
250: _user = user;
251: }
252:
253: /**
254: * Returns the connection's password
255: */
256: public String getPassword() {
257: return _password;
258: }
259:
260: /**
261: * Sets the connection's password
262: */
263: public void setPassword(String password) {
264: _password = password;
265: }
266:
267: /**
268: * Sets a property from the underlying driver. Used to set driver
269: * properties not handled by DBPool.
270: *
271: * @param name property name for the driver
272: * @param value the driver's value of the property name
273: */
274: public void setInitParam(InitParam initParam) {
275: validateInitParam();
276:
277: HashMap<String, String> paramMap = initParam.getParameters();
278:
279: Iterator<String> iter = paramMap.keySet().iterator();
280: while (iter.hasNext()) {
281: String key = iter.next();
282:
283: _info.setProperty(key, paramMap.get(key));
284: }
285: }
286:
287: /**
288: * Sets a property from the underlying driver. Used to set driver
289: * properties not handled by DBPool.
290: *
291: * @param name property name for the driver
292: * @param value the driver's value of the property name
293: */
294: public void setInitParam(String key, String value) {
295: _info.setProperty(key, value);
296: }
297:
298: /**
299: * Returns the properties.
300: */
301: public Properties getInfo() {
302: return _info;
303: }
304:
305: /**
306: * Returns the driver object.
307: */
308: public Driver getDriver() throws SQLException {
309: Object obj = getDriverObject();
310:
311: if (obj instanceof Driver)
312: return (Driver) obj;
313: else
314: return null;
315: }
316:
317: /**
318: * Sets the driver object.
319: */
320: public void setDriver(Driver driver) throws SQLException {
321: _driver = driver;
322: _driverObject = driver;
323: }
324:
325: /**
326: * Returns the driver pool.
327: */
328: public ConnectionPoolDataSource getPoolDataSource()
329: throws SQLException {
330: return _poolDataSource;
331: }
332:
333: /**
334: * Sets the pooled data source driver.
335: */
336: public void setPoolDataSource(ConnectionPoolDataSource pDataSource)
337: throws SQLException {
338: _poolDataSource = pDataSource;
339: _driverObject = _poolDataSource;
340: }
341:
342: /**
343: * Returns any XADataSource.
344: */
345: public XADataSource getXADataSource() {
346: return _xaDataSource;
347: }
348:
349: /**
350: * Sets the xa data source driver.
351: */
352: public void setXADataSource(XADataSource xaDataSource)
353: throws SQLException {
354: _xaDataSource = xaDataSource;
355: _driverObject = _xaDataSource;
356: }
357:
358: /**
359: * Returns the managed connection factory.
360: */
361: public ManagedConnectionFactory getManagedConnectionFactory() {
362: return _jcaDataSource;
363: }
364:
365: /**
366: * Returns true if the driver is XA enabled.
367: */
368: public boolean isXATransaction() {
369: return _xaDataSource != null && _dbPool.isXA();
370: }
371:
372: /**
373: * Returns true if the driver is XA enabled.
374: */
375: public boolean isLocalTransaction() {
376: return _dbPool.isXA();
377: }
378:
379: /**
380: * Configure a ProfilerPointConfig, used to create a ProfilerPoint
381: * that is then passed to setProfiler().
382: * The returned ProfilerPointConfig has a default name set to the URL of
383: * this driver,
384: */
385: public ProfilerPointConfig createProfilerPoint() {
386: ProfilerPointConfig profilerPointConfig = new ProfilerPointConfig();
387:
388: profilerPointConfig.setName(getURL());
389: profilerPointConfig.setCategorizing(true);
390:
391: return profilerPointConfig;
392: }
393:
394: /**
395: * Enables profiling for this driver.
396: */
397: public void setProfilerPoint(ProfilerPoint profilerPoint) {
398: _profilerPoint = profilerPoint;
399: }
400:
401: /**
402: * Initialize the pool's data source
403: *
404: * <ul>
405: * <li>If data-source is set, look it up in JNDI.
406: * <li>Else if the driver is a pooled or xa data source, use it.
407: * <li>Else create wrappers.
408: * </ul>
409: */
410: synchronized void initDataSource(boolean isTransactional,
411: boolean isSpy) throws SQLException {
412: if (!_lifecycle.toActive())
413: return;
414:
415: if (_xaDataSource == null && _poolDataSource == null) {
416: initDriver();
417:
418: Object driverObject = getDriverObject();
419:
420: if (driverObject == null) {
421: throw new SQLExceptionWrapper(
422: L
423: .l(
424: "driver '{0}' has not been configured for pool {1}. <database> needs a <driver type='...'>.",
425: _driverClass, getDBPool()
426: .getName()));
427: }
428:
429: if (_driverType == TYPE_XA)
430: _xaDataSource = (XADataSource) _driverObject;
431: else if (_driverType == TYPE_POOL)
432: _poolDataSource = (ConnectionPoolDataSource) _driverObject;
433: else if (_driverType == TYPE_DRIVER)
434: _driver = (Driver) _driverObject;
435: else if (driverObject instanceof XADataSource)
436: _xaDataSource = (XADataSource) _driverObject;
437: else if (_driverObject instanceof ConnectionPoolDataSource)
438: _poolDataSource = (ConnectionPoolDataSource) _driverObject;
439: else if (_driverObject instanceof ManagedConnectionFactory)
440: _jcaDataSource = (ManagedConnectionFactory) _driverObject;
441: else if (_driverObject instanceof Driver)
442: _driver = (Driver) _driverObject;
443: else
444: throw new SQLExceptionWrapper(
445: L
446: .l(
447: "driver '{0}' has not been configured for pool {1}. <database> needs a <driver type='...'>.",
448: _driverClass, getDBPool()
449: .getName()));
450:
451: /*
452: if (! isTransactional && _xaDataSource != null) {
453: throw new SQLExceptionWrapper(L.l("XADataSource '{0}' must be configured as transactional. Either configure it with <xa>true</xa> or use the database's ConnectionPoolDataSource driver or the old java.sql.Driver driver.",
454: _xaDataSource));
455: }
456: */
457: }
458:
459: _admin.register();
460:
461: if (_profilerPoint != null) {
462: if (log.isLoggable(Level.FINE))
463: log.fine(_profilerPoint.toString());
464:
465: if (_xaDataSource != null)
466: _xaDataSource = new XADataSourceWrapper(_profilerPoint,
467: _xaDataSource);
468: else if (_poolDataSource != null)
469: _poolDataSource = new ConnectionPoolDataSourceWrapper(
470: _profilerPoint, _poolDataSource);
471: else if (_driver != null)
472: _driver = new DriverWrapper(_profilerPoint, _driver);
473: }
474:
475: if (_info.size() != 0) {
476: validateInitParam();
477: }
478:
479: J2EEManagedObject.register(new JDBCDriver(this ));
480: }
481:
482: Lifecycle getLifecycle() {
483: return _lifecycle;
484: }
485:
486: boolean start() {
487: return _lifecycle.toActive();
488: }
489:
490: boolean stop() {
491: return _lifecycle.toStop();
492: }
493:
494: private void validateInitParam() {
495: if (_jcaDataSource != null) {
496: throw new ConfigException(
497: L
498: .l("<init-param> cannot be used with a JCA data source. Use the init-param key as a tag, like <key>value</key>"));
499: } else if (_poolDataSource != null) {
500: throw new ConfigException(
501: L
502: .l("<init-param> cannot be used with a ConnectionPoolDataSource. Use the init-param key as a tag, like <key>value</key>"));
503: } else if (_xaDataSource != null) {
504: throw new ConfigException(
505: L
506: .l("<init-param> cannot be used with an XADataSource. Use the init-param key as a tag, like <key>value</key>"));
507: }
508: }
509:
510: /**
511: * Returns the driver object configured for the database.
512: */
513: synchronized Object getDriverObject() throws SQLException {
514: if (_driverObject != null)
515: return _driverObject;
516: else if (_driverClass == null)
517: return null;
518:
519: if (log.isLoggable(Level.CONFIG))
520: log.config("loading driver: " + _driverClass.getName());
521:
522: try {
523: _driverObject = _driverClass.newInstance();
524: } catch (Exception e) {
525: throw new SQLExceptionWrapper(e);
526: }
527:
528: return _driverObject;
529: }
530:
531: /**
532: * Creates a connection.
533: */
534: PooledConnection createPooledConnection(String user, String password)
535: throws SQLException {
536: PooledConnection conn = null;
537: if (_xaDataSource != null) {
538: if (user == null && password == null)
539: conn = _xaDataSource.getXAConnection();
540: else
541: conn = _xaDataSource.getXAConnection(user, password);
542:
543: /*
544: if (! _isTransactional) {
545: throw new SQLExceptionWrapper(L.l("XADataSource '{0}' must be configured as transactional. Either configure it with <xa>true</xa> or use the database's ConnectionPoolDataSource driver or the old java.sql.Driver driver.",
546: _xaDataSource));
547: }
548: */
549: } else if (_poolDataSource != null) {
550: /*
551: if (_isTransactional) {
552: throw new SQLExceptionWrapper(L.l("ConnectionPoolDataSource '{0}' can not be configured as transactional. Either use the database's XADataSource driver or the old java.sql.Driver driver.",
553: _poolDataSource));
554: }
555: */
556:
557: if (user == null && password == null)
558: conn = _poolDataSource.getPooledConnection();
559: else
560: conn = _poolDataSource.getPooledConnection(user,
561: password);
562:
563: }
564:
565: return conn;
566: }
567:
568: /**
569: * Creates a connection.
570: */
571: Connection createDriverConnection(String user, String password)
572: throws SQLException {
573: if (!_lifecycle.isActive())
574: return null;
575:
576: if (_xaDataSource != null || _poolDataSource != null)
577: throw new IllegalStateException();
578:
579: if (_driver == null)
580: throw new IllegalStateException();
581:
582: Driver driver = _driver;
583: String url = getURL();
584:
585: if (url == null)
586: throw new SQLException(L
587: .l("can't create connection with null url"));
588:
589: try {
590: Properties properties = new Properties();
591: properties.putAll(getInfo());
592:
593: if (user != null)
594: properties.put("user", user);
595: else
596: properties.put("user", "");
597:
598: if (password != null)
599: properties.put("password", password);
600: else
601: properties.put("password", "");
602:
603: Connection conn;
604: if (driver != null)
605: conn = driver.connect(url, properties);
606: else
607: conn = java.sql.DriverManager.getConnection(url,
608: properties);
609:
610: synchronized (this ) {
611: _connectionCountTotal++;
612: }
613:
614: return conn;
615: } catch (SQLException e) {
616: synchronized (this ) {
617: _connectionFailCountTotal++;
618: _lastFailTime = Alarm.getCurrentTime();
619: }
620:
621: throw e;
622: }
623: }
624:
625: @PostConstruct
626: public void init() {
627: if (_driverClass == null && _poolDataSource == null
628: && _xaDataSource == null) {
629: if (_driverURL == null)
630: throw new ConfigException(L
631: .l("<driver> requires a 'type' or 'url'"));
632:
633: String driver = DatabaseManager.findDriverByUrl(_driverURL);
634:
635: if (driver == null)
636: throw new ConfigException(
637: L
638: .l(
639: "url='{0}' does not have a known driver. The driver class must be specified by a 'type' parameter.",
640: _driverURL));
641:
642: Class driverClass = null;
643: try {
644: ClassLoader loader = Thread.currentThread()
645: .getContextClassLoader();
646:
647: driverClass = Class.forName(driver, false, loader);
648: } catch (RuntimeException e) {
649: throw e;
650: } catch (Exception e) {
651: throw ConfigException.create(e);
652: }
653:
654: setType(driverClass);
655: }
656: }
657:
658: /**
659: * Initializes the JDBC driver.
660: */
661: public void initDriver() throws SQLException {
662: if (!_lifecycle.toInit())
663: return;
664:
665: Object driverObject = getDriverObject();
666:
667: if (driverObject != null) {
668: } else if (_xaDataSource != null || _poolDataSource != null)
669: return;
670: else {
671: throw new SQLExceptionWrapper(
672: L
673: .l(
674: "driver '{0}' has not been configured for pool {1}. <database> needs either a <data-source> or a <type>.",
675: _driverClass, getDBPool().getName()));
676: }
677:
678: try {
679: // server/14g1
680: if (_driverURL != null) {
681: PropertyValueProgram program;
682: program = new PropertyValueProgram("url", _driverURL);
683: program.configure(driverObject);
684: }
685: } catch (Exception e) {
686: if (driverObject instanceof Driver)
687: log.log(Level.FINEST, e.toString(), e);
688: else
689: throw new SQLExceptionWrapper(e);
690: }
691:
692: try {
693: if (_user != null) { // && ! (driverObject instanceof Driver)) {
694: PropertyValueProgram program;
695: program = new PropertyValueProgram("user", _user);
696: program.configure(driverObject);
697: }
698: } catch (Throwable e) {
699: log.log(Level.FINEST, e.toString(), e);
700:
701: if (!(driverObject instanceof Driver))
702: throw new SQLExceptionWrapper(e);
703: }
704:
705: try {
706: if (_password != null) { // && ! (driverObject instanceof Driver)) {
707: PropertyValueProgram program;
708: program = new PropertyValueProgram("password",
709: _password);
710: program.configure(driverObject);
711: }
712: } catch (Throwable e) {
713: log.log(Level.FINEST, e.toString(), e);
714:
715: if (!(driverObject instanceof Driver))
716: throw new SQLExceptionWrapper(e);
717: }
718:
719: try {
720: if (_init != null) {
721: _init.configure(driverObject);
722: _init = null;
723: }
724:
725: Config.init(driverObject);
726: } catch (Throwable e) {
727: log.log(Level.FINE, e.toString(), e);
728: throw new SQLExceptionWrapper(e);
729: }
730: }
731:
732: private boolean hasSetter(Class cl, String name) {
733: if (true)
734: return true;
735: for (Method method : cl.getMethods()) {
736: String methodName = method.getName();
737:
738: if (!methodName.startsWith("set"))
739: continue;
740: else if (method.getParameterTypes().length != 1)
741: continue;
742:
743: methodName = methodName.substring(3).toLowerCase();
744:
745: if (methodName.equals(name))
746: return true;
747: }
748:
749: return false;
750: }
751:
752: //
753: // statistics
754: //
755:
756: /**
757: * Returns the total number of connections made.
758: */
759: public long getConnectionCountTotal() {
760: return _connectionCountTotal;
761: }
762:
763: /**
764: * Returns the total number of failing connections
765: */
766: public long getConnectionFailCountTotal() {
767: return _connectionFailCountTotal;
768: }
769:
770: /**
771: * Returns the time of the last connection
772: */
773: public long getLastFailTime() {
774: return _lastFailTime;
775: }
776:
777: /**
778: * Returns a string description of the pool.
779: */
780: public String toString() {
781: return "JdbcDriver[" + _driverURL + "]";
782: }
783: }
|