001: /*
002: * $Header: /cvsroot/webman-cms/source/webman/com/teamkonzept/db/TKDBConnectionManager.java,v 1.28 2002/01/28 14:41:54 alex Exp $
003: *
004: */
005: package com.teamkonzept.db;
006:
007: import java.sql.*;
008: import java.util.Enumeration;
009: import java.util.Stack;
010: import java.util.Properties;
011:
012: import com.teamkonzept.lib.*;
013: import org.apache.log4j.Category;
014:
015: /**
016: * Manages database connections for an application context.
017: * Each application context has its TKDBConnectionManager, which
018: * provides its threads with database connections.
019: * <br>
020: * The manager contains all data needed to maintain connections to a database like
021: * <ul><li>database name, <li>driver, <li>username, <li>password in {@link TKConnectData TKConnectData}</ul>
022: * <br>
023: * Furthermore the manager contains information about data types supported by the database in {@link TKSQLTypeConverter TKSQLTypeConverter},
024: * <br>
025: * a hash for the used query classes, a hash for the connections of threads,
026: * <br>
027: * a stack with currently unused connections,
028: * <br>
029: * and a mechanism for limiting simultaneously opened connections {@link TKLimiter TKLimiter}.
030: *
031: * <br>
032: *
033: * @author
034: * @version
035: */
036: public class TKDBConnectionManager {
037: private static final Category CAT = Category
038: .getInstance(TKDBConnectionManager.class);
039:
040: /**
041: * currently unused
042: */
043: //private String queryClassBasePackage = "";
044: /**
045: * currently unused
046: */
047: private int maxConnections = 0;
048:
049: /**
050: * ID of a database.
051: * <br>(e.g. "oracle", "sybase", "mssql") as a string.
052: */
053: private String dbIdentifier = null;
054:
055: /**
056: * Container of data needed for database connections, containing database name, user and password.
057: */
058: private TKConnectData jdbcConnectData;
059:
060: /**
061: * A hashtable, which maps a thread to its currently used database connection
062: * <br><br>
063: * key(Thread aThread) => value(TKDBConnection aConnection)
064: */
065: private TKHashtable tkConnectionHash = new TKHashtable();
066:
067: /**
068: * A hashtable, which maps a query's class name to the query class
069: * <br><br>
070: * key(String aClassName) => value(Class aQueryClass)
071: */
072: private TKHashtable queryClasses = new TKHashtable();
073:
074: /**
075: * A stack, which stores currently unused, released, but opened connections.
076: */
077: private Stack tkConnectionStack = new Stack();
078:
079: /**
080: * TKLimiter controls the maximum of simultaneously opened connections.
081: */
082: private TKLimiter limiter = new TKLimiter();
083:
084: /**
085: * A Vector, which stores after <code>_resetConnections</code> is called,
086: * all connections from <code>tkConnectionsHash</code>.
087: * <br>
088: * A connection is removed from the vector after call of <code>_freeConnection</code>
089: */
090: private TKVector connectionsToClose = new TKVector();
091:
092: //public TKHashtable prepQueries = new TKHashtable(); // of Connection, TKHashtable of queryID, PreparedQuery for reuse of Prepared Statements
093: //private TKHashtable tkdbConnectionHash = new TKHashtable(); // of Connection, TKDBConnection
094:
095: /**
096: * Returns the connection of the current thread.
097: *
098: * @return Connection the connection of the current thread.
099: */
100: public Connection getConnection() throws SQLException {
101: return getTKDBConnection().getConnection();
102: }
103:
104: /**
105: * Returns the connect data of the <code>TKDBConnectionManager</code>.
106: *
107: * @return TKConnectData the connect data of this manager.
108: */
109: public TKConnectData getConnectionData() {
110: return jdbcConnectData;
111: }
112:
113: /**
114: * Returns the <code>TKDBConnection</code> of the current thread.
115: * <br>
116: * If there is no connection available in {@link TKHashtable TKHashtable} <code>tkConnectionHash</code>
117: * and <code>Stack tkConnectionStack</code> is empty, a new connection will be opened.
118: * <br>
119: * If the limit, defined in the {@link TKLimiter TKLimiter} <code>limiter</code>, of opened connections
120: * will be exceeded, the current thread has to wait.
121: * @return TKDBConnection {@link TKDBConnection TKDBConnection} of current thread.
122: * @throws SQLException
123: */
124: public TKDBConnection getTKDBConnection() throws SQLException {
125: try {
126: Thread currentThread = Thread.currentThread();
127: TKDBConnection tkConn = (TKDBConnection) tkConnectionHash
128: .get(currentThread);
129:
130: // debug cached threads/connections
131: if (CAT.isDebugEnabled()) {
132: CAT.debug("Current Thread: [" + Thread.currentThread()
133: + "]"); // XTODO
134: CAT.debug("Limiter: (oid=" + limiter + ", used="
135: + limiter.used + ", pending=" + limiter.pending
136: + ", limit=" + limiter.limit
137: + ") !=? CACHE SIZE: "
138: + tkConnectionHash.size()); // XTODO
139: Enumeration en = tkConnectionHash.keys();
140: int cnt = 0;
141: while (en.hasMoreElements()) {
142: CAT.debug("|<" + cnt + "> " + en.nextElement()
143: + " |"); // XTODO
144: cnt++;
145: }
146: }
147: // check limit of opened connections
148: if (tkConn == null) {
149: limiter.take();
150: }
151: synchronized (this ) {
152: if (tkConn == null || tkConn.getConnection().isClosed()) {
153: if (!tkConnectionStack.empty()) {
154: try {
155: tkConn = (TKDBConnection) tkConnectionStack
156: .pop();
157: tkConnectionHash.put(currentThread, tkConn);
158: } catch (Throwable t) {
159: CAT.error(t);
160: }
161: } else {
162: CAT.debug("stack empty");
163: }
164: if (tkConn == null
165: || tkConn.getConnection().isClosed()) {
166: if (CAT.isDebugEnabled()) {
167: CAT.debug("open new connection now ...");
168: CAT.debug("using connectData:"
169: + jdbcConnectData);
170: }
171: Connection conn = DriverManager.getConnection(
172: jdbcConnectData.getConnectString(),
173: jdbcConnectData.getConnectProperties());
174: tkConn = new TKDBConnection(conn, this );
175: tkConnectionHash.put(currentThread, tkConn);
176: }
177: } else {
178: CAT.debug("reuse connection");
179: }
180: }
181: return tkConn;
182: } catch (SQLException e) {
183: CAT.error("Error during connection creation : ", e);
184: throw e;
185: }
186: }
187:
188: /**
189: * Instantiates an object of the specified class <code>inQueryClass</code> and adds it
190: * to the queries maintained by this <code>TKDBConnectionManager</code>
191: * within the {@link TKHashtable TKHashtable} <code>queryClasses</code>.
192: * <br>
193: * @param inQueryClass the class definition for the <code>TKQuery</code> to instantiate.
194: * @return the instantiated <code>TKQuery</code>
195: *
196: */
197: public TKQuery newQuery(Class inQueryClass) throws SQLException {
198: TKDBConnection tkConn = getTKDBConnection(); // Muss noch vereinfacht werden!
199: TKQuery query;
200: Class queryClass;
201: String queryId = TKLib.getClassName(inQueryClass);
202: // String basePackage = queryClass.getPackage().getName(); Oops, erst in Java 1.2 // NullPointerException falls kein Package! marwan
203: String basePackage = TKLib.getPackageName(inQueryClass);
204:
205: synchronized (this ) { // Warum synchronized ??? -alex
206: query = getInitializedPrepQuery(queryId, tkConn); // Attempt to reuse a TKPrepQuery
207: if (query != null) {
208: return query;
209: }
210: queryClass = (Class) queryClasses.get(queryId);
211: if (queryClass == null) {
212: if (dbIdentifier != null && dbIdentifier.length() > 0) {
213: try {
214: String classToLoad = "";
215: if (basePackage == null) {
216: classToLoad = dbIdentifier + '.' + queryId;
217: } else {
218: classToLoad = basePackage + '.'
219: + dbIdentifier + '.' + queryId;
220: }
221: queryClass = Class.forName(classToLoad);
222: } catch (ClassNotFoundException e) {
223: CAT.debug(e);
224: }
225: }
226:
227: if (queryClass == null) {
228: queryClass = inQueryClass;
229: }
230: queryClasses.put(queryId, queryClass);
231: }
232: }
233:
234: try {
235: if (query == null) {
236: query = (TKQuery) queryClass.newInstance();
237: }
238: query.initQuery(jdbcConnectData.getTypeConverter(), tkConn,
239: queryId);
240:
241: return query;
242: } catch (Throwable t) {
243: CAT.error("closeConnection", t);
244: throw new InstantiationError(t.getMessage());
245: }
246: }
247:
248: /*
249: public synchronized void setBasePackage( String basePackage )
250: {
251: queryClassBasePackage = basePackage;
252: }
253: */
254:
255: /**
256: * Sets the connection data object of this manager.
257: * <br>
258: * @param data the {@link TKConnectData TKConnectData} object to set.
259: *
260: */
261: public synchronized void setConnectionData(TKConnectData data) {
262: CAT.debug("jdbcConnectData set in setConnectionData: with: "
263: + data);
264: jdbcConnectData = data;
265: }
266:
267: /**
268: *
269: *
270: */
271: public synchronized void prepareConnection(Properties prop)
272: throws SQLException {
273: jdbcConnectData = null;
274:
275: dbIdentifier = prop.getProperty("database").toLowerCase();
276: CAT.debug("got DB Identifier " + dbIdentifier);
277: if (dbIdentifier.startsWith("mssql")) {
278: jdbcConnectData = new TKMSSQLConnectData(prop);
279: } else if (dbIdentifier.startsWith("sybase")) {
280: jdbcConnectData = new TKSybaseConnectData(prop);
281: } else if (dbIdentifier.startsWith("oracle")) {
282: jdbcConnectData = new TKOracleConnectData(prop);
283: } else if (dbIdentifier.startsWith("postgresql")) {
284: jdbcConnectData = new TKPostgreSQLConnectData(prop);
285: }
286: if (jdbcConnectData != null)
287: jdbcConnectData.initTypeConverter(getConnection());
288:
289: }
290:
291: public synchronized void closeConnection() throws SQLException {
292: Thread currentThread = Thread.currentThread();
293: TKDBConnection tkConn = (TKDBConnection) tkConnectionHash
294: .remove(currentThread);
295: if (tkConn != null) {
296: limiter.free();
297: closeConnection(tkConn);
298: } else {
299: CAT.warn(" closeConnection: no Connection found! ");
300: }
301: }
302:
303: public synchronized void closeConnection(TKDBConnection tkConn)
304: throws SQLException {
305: limiter.free();
306: try {
307: tkConn.closeNonsensitiveQueries(false);
308: } finally {// Die Connection wird in jedem Fall geschlossen!
309: Connection conn = tkConn.getConnection();
310: if (!conn.isClosed()) {
311: conn.close();
312:
313: }
314: }
315: }
316:
317: public synchronized void freeConnection() throws SQLException {
318: Thread currentThread = Thread.currentThread();
319:
320: TKDBConnection tkConn = (TKDBConnection) tkConnectionHash
321: .remove(currentThread);
322: if (tkConn != null) {
323: try {
324: tkConn.closeNonsensitiveQueries(false);
325: } catch (SQLException e) {
326: closeConnection(tkConn);
327: }
328: limiter.free();
329: Connection conn = tkConn.getConnection();
330: if (connectionsToClose.removeElement(conn)) {
331: // die Verbindung ist als zu schliessen markiert!
332: conn.close();
333:
334: }
335: if (!conn.isClosed()) {
336: tkConnectionStack.push(tkConn);
337: }
338: }
339: }
340:
341: public synchronized void resetConnections() {
342: maxConnections = 0;
343: dbIdentifier = null;
344: jdbcConnectData = null;
345:
346: // alle noch in Gebrauch befindlichen Verbindungen merken
347: Enumeration e = tkConnectionHash.elements();
348: while (e.hasMoreElements()) {
349: connectionsToClose.addElement(((TKDBConnection) e
350: .nextElement()).getConnection());
351: }
352:
353: // alle Query-Klassen vergessen
354: queryClasses = new TKHashtable();
355:
356: // alle offenen Verbindungen schliessen
357: while (!tkConnectionStack.empty()) {
358: Connection conn = ((TKDBConnection) tkConnectionStack.pop())
359: .getConnection();
360: try {
361: conn.close();
362:
363: } catch (SQLException sqle) {
364: throw new TKSQLError(sqle.getMessage(), sqle);
365: }
366: }
367: }
368:
369: public synchronized void limitConnections(int count) {
370: limiter.newLimit(count);
371: }
372:
373: public void beginTransaction() throws SQLException {
374: getTKDBConnection().beginTransaction();
375: }
376:
377: public void commitTransaction() throws SQLException {
378:
379: getTKDBConnection().commitTransaction();
380: }
381:
382: public void rollbackTransaction() throws SQLException {
383: getTKDBConnection().rollbackTransaction();
384: }
385:
386: public void closeNonsensitiveQueries() throws SQLException {
387: getTKDBConnection().closeNonsensitiveQueries();
388: }
389:
390: /** Method to get a TKPreparedQuery Object. Returns an initialized TKPrerQueyQuery
391: * of the class queryID, if a query of that class has allready been executed on the
392: * Connection. Otherwise null is returned.
393: */
394: private TKPrepQuery getInitializedPrepQuery(Object queryID,
395: TKDBConnection tkConn) {
396: TKHashtable queries = (TKHashtable) tkConn.getPrepQueries();
397: TKPrepQuery query = (TKPrepQuery) queries.remove(queryID);
398: return query;
399: }
400:
401: // protected Object clone()
402: // {
403: // final TKDBConnectionManager clone = new TKDBConnectionManager();
404:
405: // clone.jdbcConnectData = this.jdbcConnectData;
406: // clone.limiter = this.limiter;
407: // // clone.queryClassBasePackage = this.queryClassBasePackage;
408: // clone.maxConnections = this.maxConnections;
409: // clone.dbIdentifier = this.dbIdentifier;
410: // clone.tkConnectionHash = new TKHashtable();
411: // clone.queryClasses = this.queryClasses;
412: // clone.tkConnectionStack = this.tkConnectionStack;
413: // clone.connectionsToClose = this.connectionsToClose;
414: // // clone.transaction_level = this.transaction_level;
415:
416: // return clone;
417: // }
418:
419: private static TKDBConnectionManager singleton = null;
420:
421: public static TKDBConnectionManager getInstance() {
422: if (singleton == null)
423: singleton = new TKDBConnectionManager();
424: return singleton;
425: }
426:
427: private TKDBConnectionManager() {
428: }
429: }
|