001: /*
002: * Helma License Notice
003: *
004: * The contents of this file are subject to the Helma License
005: * Version 2.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://adele.helma.org/download/helma/license.txt
008: *
009: * Copyright 1998-2003 Helma Software. All Rights Reserved.
010: *
011: * $RCSfile$
012: * $Author: hannes $
013: * $Revision: 8622 $
014: * $Date: 2007-10-11 14:31:49 +0200 (Don, 11 Okt 2007) $
015: */
016:
017: package helma.objectmodel.db;
018:
019: import helma.util.ResourceProperties;
020:
021: import java.sql.Connection;
022: import java.sql.DriverManager;
023: import java.sql.SQLException;
024: import java.sql.Statement;
025: import java.util.Enumeration;
026: import java.util.Properties;
027: import java.util.Hashtable;
028:
029: /**
030: * This class describes a releational data source (URL, driver, user and password).
031: */
032: public class DbSource {
033: private static ResourceProperties defaultProps = null;
034: private Properties conProps;
035: private String name;
036: private ResourceProperties props, subProps;
037: protected String url;
038: private String driver;
039: private boolean isOracle, isMySQL, isPostgreSQL, isH2;
040: private long lastRead = 0L;
041: private Hashtable dbmappings = new Hashtable();
042: // compute hashcode statically because it's expensive and we need it often
043: private int hashcode;
044: // thread local connection holder for non-transactor threads
045: private ThreadLocal connection;
046:
047: /**
048: * Creates a new DbSource object.
049: *
050: * @param name the db source name
051: * @param props the properties
052: * @throws ClassNotFoundException if the JDBC driver couldn't be loaded
053: */
054: public DbSource(String name, ResourceProperties props)
055: throws ClassNotFoundException {
056: this .name = name;
057: this .props = props;
058: init();
059: }
060:
061: /**
062: * Get a JDBC connection to the db source.
063: *
064: * @return a JDBC connection
065: *
066: * @throws ClassNotFoundException if the JDBC driver couldn't be loaded
067: * @throws SQLException if the connection couldn't be created
068: */
069: public synchronized Connection getConnection()
070: throws ClassNotFoundException, SQLException {
071: Connection con;
072: Transactor tx = null;
073: if (Thread.currentThread() instanceof Transactor) {
074: tx = (Transactor) Thread.currentThread();
075: con = tx.getConnection(this );
076: } else {
077: con = getThreadLocalConnection();
078: }
079:
080: boolean fileUpdated = props.lastModified() > lastRead
081: || (defaultProps != null && defaultProps.lastModified() > lastRead);
082:
083: if (con == null || con.isClosed() || fileUpdated) {
084: init();
085: con = DriverManager.getConnection(url, conProps);
086:
087: // If we wanted to use SQL transactions, we'd set autoCommit to
088: // false here and make commit/rollback invocations in Transactor methods;
089: // System.err.println ("Created new Connection to "+url);
090: if (tx != null) {
091: tx.registerConnection(this , con);
092: } else {
093: connection.set(con);
094: }
095: }
096:
097: return con;
098: }
099:
100: /**
101: * Used for connections not managed by a Helma transactor
102: * @return a thread local tested connection, or null
103: */
104: private Connection getThreadLocalConnection() {
105: if (connection == null) {
106: connection = new ThreadLocal();
107: return null;
108: }
109: Connection con = (Connection) connection.get();
110: if (con != null) {
111: // test if connection is still ok
112: try {
113: Statement stmt = con.createStatement();
114: stmt.execute("SELECT 1");
115: stmt.close();
116: } catch (SQLException sx) {
117: try {
118: con.close();
119: } catch (SQLException ignore) {/* nothing to do */
120: }
121: return null;
122: }
123: }
124: return con;
125: }
126:
127: /**
128: * Set the db properties to newProps, and return the old properties.
129: * @param newProps the new properties to use for this db source
130: * @return the old properties
131: * @throws ClassNotFoundException if jdbc driver class couldn't be found
132: */
133: public synchronized ResourceProperties switchProperties(
134: ResourceProperties newProps) throws ClassNotFoundException {
135: ResourceProperties oldProps = props;
136: props = newProps;
137: init();
138: return oldProps;
139: }
140:
141: /**
142: * Initialize the db source from the properties
143: *
144: * @throws ClassNotFoundException if the JDBC driver couldn't be loaded
145: */
146: private synchronized void init() throws ClassNotFoundException {
147: lastRead = (defaultProps == null) ? props.lastModified() : Math
148: .max(props.lastModified(), defaultProps.lastModified());
149: // refresh sub-properties for this DbSource
150: subProps = props.getSubProperties(name + '.');
151: // use properties hashcode for ourselves
152: hashcode = subProps.hashCode();
153: // get JDBC URL and driver class name
154: url = subProps.getProperty("url");
155: driver = subProps.getProperty("driver");
156: // sanity checks
157: if (url == null) {
158: throw new NullPointerException(name
159: + ".url is not defined in db.properties");
160: }
161: if (driver == null) {
162: throw new NullPointerException(name
163: + ".driver class not defined in db.properties");
164: }
165: // test if this is an Oracle or MySQL driver
166: isOracle = driver.startsWith("oracle.jdbc.driver");
167: isMySQL = driver.startsWith("com.mysql.jdbc")
168: || driver.startsWith("org.gjt.mm.mysql");
169: isPostgreSQL = driver.equals("org.postgresql.Driver");
170: isH2 = driver.equals("org.h2.Driver");
171: // test if driver class is available
172: Class.forName(driver);
173:
174: // set up driver connection properties
175: conProps = new Properties();
176: String prop = subProps.getProperty("user");
177: if (prop != null) {
178: conProps.put("user", prop);
179: }
180: prop = subProps.getProperty("password");
181: if (prop != null) {
182: conProps.put("password", prop);
183: }
184:
185: // read any remaining extra properties to be passed to the driver
186: for (Enumeration e = subProps.keys(); e.hasMoreElements();) {
187: String key = (String) e.nextElement();
188:
189: // filter out properties we alread have
190: if ("url".equalsIgnoreCase(key)
191: || "driver".equalsIgnoreCase(key)
192: || "user".equalsIgnoreCase(key)
193: || "password".equalsIgnoreCase(key)) {
194: continue;
195: }
196: conProps.setProperty(key, subProps.getProperty(key));
197: }
198: }
199:
200: /**
201: * Return the class name of the JDBC driver
202: *
203: * @return the class name of the JDBC driver
204: */
205: public String getDriverName() {
206: return driver;
207: }
208:
209: /**
210: * Return the name of the db dource
211: *
212: * @return the name of the db dource
213: */
214: public String getName() {
215: return name;
216: }
217:
218: /**
219: * Set the default (server-wide) properties
220: *
221: * @param props server default db.properties
222: */
223: public static void setDefaultProps(ResourceProperties props) {
224: defaultProps = props;
225: }
226:
227: /**
228: * Check if this DbSource represents an Oracle database
229: *
230: * @return true if we're using an oracle JDBC driver
231: */
232: public boolean isOracle() {
233: return isOracle;
234: }
235:
236: /**
237: * Check if this DbSource represents a MySQL database
238: *
239: * @return true if we're using a MySQL JDBC driver
240: */
241: public boolean isMySQL() {
242: return isMySQL;
243: }
244:
245: /**
246: * Check if this DbSource represents a PostgreSQL database
247: *
248: * @return true if we're using a PostgreSQL JDBC driver
249: */
250: public boolean isPostgreSQL() {
251: return isPostgreSQL;
252: }
253:
254: /**
255: * Check if this DbSource represents a H2 database
256: *
257: * @return true if we're using a H2 JDBC driver
258: */
259: public boolean isH2() {
260: return isH2;
261: }
262:
263: /**
264: * Register a dbmapping by its table name.
265: *
266: * @param dbmap the DbMapping instance to register
267: */
268: protected void registerDbMapping(DbMapping dbmap) {
269: if (!dbmap.inheritsStorage() && dbmap.getTableName() != null) {
270: dbmappings.put(dbmap.getTableName().toUpperCase(), dbmap);
271: }
272: }
273:
274: /**
275: * Look up a DbMapping instance for the given table name.
276: *
277: * @param tablename the table name
278: * @return the matching DbMapping instance
279: */
280: protected DbMapping getDbMapping(String tablename) {
281: return (DbMapping) dbmappings.get(tablename.toUpperCase());
282: }
283:
284: /**
285: * Returns a hash code value for the object.
286: */
287: public int hashCode() {
288: return hashcode;
289: }
290:
291: /**
292: * Indicates whether some other object is "equal to" this one.
293: */
294: public boolean equals(Object obj) {
295: return obj instanceof DbSource
296: && subProps.equals(((DbSource) obj).subProps);
297: }
298: }
|