001: /*
002: * This software is released under a licence similar to the Apache Software Licence.
003: * See org.logicalcobwebs.proxool.package.html for details.
004: * The latest version is available at http://proxool.sourceforge.net
005: */
006: package org.logicalcobwebs.proxool;
007:
008: import org.logicalcobwebs.concurrent.WriterPreferenceReadWriteLock;
009: import org.apache.commons.logging.Log;
010: import org.apache.commons.logging.LogFactory;
011: import org.logicalcobwebs.proxool.util.FastArrayList;
012:
013: import java.sql.Connection;
014: import java.sql.SQLException;
015: import java.sql.Statement;
016: import java.util.Date;
017: import java.util.Set;
018: import java.util.HashSet;
019: import java.util.List;
020: import java.text.DecimalFormat;
021:
022: /**
023: * Manages a connection. This is wrapped up inside a...
024: *
025: * @version $Revision: 1.37 $, $Date: 2006/01/18 14:40:02 $
026: * @author bill
027: * @author $Author: billhorsman $ (current maintainer)
028: * @since Proxool 0.10
029: */
030: public class ProxyConnection implements ProxyConnectionIF {
031:
032: static final int STATUS_FORCE = -1;
033:
034: private WriterPreferenceReadWriteLock statusReadWriteLock = new WriterPreferenceReadWriteLock();
035:
036: private static final Log LOG = LogFactory
037: .getLog(ProxyConnection.class);
038:
039: private Connection connection;
040:
041: private String delegateUrl;
042:
043: private int mark;
044:
045: private String reasonForMark;
046:
047: private int status;
048:
049: private long id;
050:
051: private Date birthDate;
052:
053: private long timeLastStartActive;
054:
055: private long timeLastStopActive;
056:
057: private ConnectionPool connectionPool;
058:
059: private ConnectionPoolDefinitionIF definition;
060:
061: private String requester;
062:
063: private Set openStatements = new HashSet();
064:
065: private DecimalFormat idFormat = new DecimalFormat("0000");
066:
067: private List sqlCalls = new FastArrayList();
068:
069: /**
070: * Whether we have invoked a method that requires us to reset
071: */
072: private boolean needToReset = false;
073:
074: /**
075: *
076: * @param connection the real connection that is used
077: * @param id unique ID
078: * @param delegateUrl
079: * @param connectionPool the pool it is a member of
080: * @param definition the definition that was used to build it (could possibly be different from
081: * the one held in the connectionPool)
082: * @param status {@link #STATUS_ACTIVE}, {@link #STATUS_AVAILABLE}, {@link #STATUS_FORCE}, {@link #STATUS_NULL}, or {@link #STATUS_OFFLINE}
083: * @throws SQLException
084: */
085: protected ProxyConnection(Connection connection, long id,
086: String delegateUrl, ConnectionPool connectionPool,
087: ConnectionPoolDefinitionIF definition, int status)
088: throws SQLException {
089: this .connection = connection;
090: this .delegateUrl = delegateUrl;
091: setId(id);
092: this .connectionPool = connectionPool;
093: this .definition = definition;
094: setBirthTime(System.currentTimeMillis());
095:
096: this .status = status;
097: if (status == STATUS_ACTIVE) {
098: setTimeLastStartActive(System.currentTimeMillis());
099: }
100:
101: // We only need to call this for the first connection we make. But it returns really
102: // quickly and we don't call it that often so we shouldn't worry.
103: connectionPool.initialiseConnectionResetter(connection);
104:
105: if (connection == null) {
106: throw new SQLException("Unable to create new connection");
107: }
108: }
109:
110: /**
111: * Whether the underlying connections are equal
112: * @param obj the object (probably another connection) that we
113: * are being compared to
114: * @return whether they are the same
115: */
116: public boolean equals(Object obj) {
117: if (obj != null) {
118: if (obj instanceof ProxyConnection) {
119: return connection.hashCode() == ((ProxyConnection) obj)
120: .getConnection().hashCode();
121: } else if (obj instanceof Connection) {
122: return connection.hashCode() == obj.hashCode();
123: } else {
124: return super .equals(obj);
125: }
126: } else {
127: return false;
128: }
129: }
130:
131: /**
132: * Whether this connection is available. (When you close the connection
133: * it doesn't really close, it just becomes available for others to use).
134: * @return true if the connection is not active
135: */
136: public boolean isClosed() {
137: return (getStatus() != STATUS_ACTIVE);
138: }
139:
140: /**
141: * The subclass should call this to indicate that a change has been made to
142: * the connection that might mean it needs to be reset (like setting autoCommit
143: * to false or something). We don't reset unless this has been called to avoid
144: * the overhead of unnecessary resetting.
145: *
146: * @param needToReset true if the connection might need resetting.
147: */
148: protected void setNeedToReset(boolean needToReset) {
149: this .needToReset = needToReset;
150: }
151:
152: /**
153: * The ConnectionPool that this connection belongs to
154: * @return connectionPool
155: */
156: protected ConnectionPool getConnectionPool() {
157: return connectionPool;
158: }
159:
160: /**
161: * Get the definition that was used to create this connection
162: * @return definition
163: */
164: public ConnectionPoolDefinitionIF getDefinition() {
165: return definition;
166: }
167:
168: /**
169: * By calling this we can keep track of any statements that are
170: * left open when this connection is returned to the pool.
171: * @param statement the statement that we have just opened/created.
172: * @see #registerClosedStatement
173: */
174: protected void addOpenStatement(Statement statement) {
175: openStatements.add(statement);
176: }
177:
178: /**
179: * @see ProxyConnectionIF#registerClosedStatement
180: */
181: public void registerClosedStatement(Statement statement) {
182: if (openStatements.contains(statement)) {
183: openStatements.remove(statement);
184: } else {
185: connectionPool
186: .getLog()
187: .warn(
188: connectionPool.displayStatistics()
189: + " - #"
190: + getId()
191: + " registered a statement as closed which wasn't known to be open.");
192: }
193: }
194:
195: /**
196: * Close the connection for real
197: * @throws java.sql.SQLException if anything goes wrong
198: */
199: public void reallyClose() throws SQLException {
200: try {
201: connectionPool.registerRemovedConnection(getStatus());
202: // Clean up the actual connection
203: connection.close();
204: } catch (Throwable t) {
205: connectionPool
206: .getLog()
207: .error(
208: "#"
209: + idFormat.format(getId())
210: + " encountered errors during destruction: ",
211: t);
212: }
213:
214: }
215:
216: /**
217: * @see ProxyConnectionIF#isReallyClosed
218: */
219: public boolean isReallyClosed() throws SQLException {
220: if (connection == null) {
221: return true;
222: } else {
223: return connection.isClosed();
224: }
225: }
226:
227: /**
228: * @see ProxyConnectionIF#close
229: */
230: public void close() throws SQLException {
231: try {
232: boolean removed = false;
233: if (isMarkedForExpiry()) {
234: if (connectionPool.getLog().isDebugEnabled()) {
235: connectionPool
236: .getLog()
237: .debug(
238: "Closing connection quickly (without reset) because it's marked for expiry anyway");
239: }
240: } else {
241: // Close any open statements, as specified in JDBC
242: Statement[] statements = (Statement[]) openStatements
243: .toArray(new Statement[openStatements.size()]);
244: for (int j = 0; j < statements.length; j++) {
245: Statement statement = statements[j];
246: statement.close();
247: if (connectionPool.getLog().isDebugEnabled()) {
248: connectionPool.getLog().debug(
249: "Closing statement "
250: + Integer.toHexString(statement
251: .hashCode())
252: + " automatically");
253: }
254: }
255: openStatements.clear();
256:
257: if (needToReset) {
258: // This call should be as quick as possible. Should we consider only
259: // calling it if values have changed? The trouble with that is that it
260: // means keeping track when they change and that might be even
261: // slower
262: if (!connectionPool.resetConnection(connection, "#"
263: + getId())) {
264: connectionPool.removeProxyConnection(this ,
265: "it couldn't be reset", true, true);
266: removed = true;
267: }
268: needToReset = false;
269: }
270: }
271: // If we removed it above then putting it back will only cause a confusing log event later when
272: // it is unable to be changed from ACTIVE to AVAILABLE.
273: if (!removed) {
274: connectionPool.putConnection(this );
275: }
276: } catch (Throwable t) {
277: connectionPool
278: .getLog()
279: .error(
280: "#"
281: + idFormat.format(getId())
282: + " encountered errors during closure: ",
283: t);
284: }
285:
286: }
287:
288: /**
289: * This gets called /just/ before a connection is served. You can use it to reset some of the attributes.
290: * The lifecycle is: {@link #open()} then {@link #close()}
291: */
292: protected void open() {
293: sqlCalls.clear();
294: }
295:
296: public int getMark() {
297: return mark;
298: }
299:
300: public int getStatus() {
301: return status;
302: }
303:
304: /**
305: * @see ProxyConnectionIF#setStatus(int)
306: */
307: public boolean setStatus(int newStatus) {
308: return setStatus(STATUS_FORCE, newStatus);
309: }
310:
311: /**
312: * @see ProxyConnectionIF#setStatus(int, int)
313: */
314: public boolean setStatus(int oldStatus, int newStatus) {
315: boolean success = false;
316: try {
317: statusReadWriteLock.writeLock().acquire();
318: connectionPool.acquireConnectionStatusWriteLock();
319: if (this .status == oldStatus || oldStatus == STATUS_FORCE) {
320: connectionPool.changeStatus(this .status, newStatus);
321: this .status = newStatus;
322: success = true;
323:
324: if (newStatus == oldStatus) {
325: LOG
326: .warn("Unexpected attempt to change status from "
327: + oldStatus
328: + " to "
329: + newStatus
330: + ". Why would you want to do that?");
331: } else if (newStatus == STATUS_ACTIVE) {
332: setTimeLastStartActive(System.currentTimeMillis());
333: } else if (oldStatus == STATUS_ACTIVE) {
334: setTimeLastStopActive(System.currentTimeMillis());
335: }
336: }
337: } catch (InterruptedException e) {
338: LOG.error("Unable to acquire write lock for status");
339: } finally {
340: connectionPool.releaseConnectionStatusWriteLock();
341: statusReadWriteLock.writeLock().release();
342: }
343: return success;
344: }
345:
346: public long getId() {
347: return id;
348: }
349:
350: public void setId(long id) {
351: this .id = id;
352: }
353:
354: /**
355: * @see ConnectionInfoIF#getBirthTime
356: */
357: public long getBirthTime() {
358: return birthDate.getTime();
359: }
360:
361: /**
362: * @see ConnectionInfoIF#getBirthDate
363: */
364: public Date getBirthDate() {
365: return birthDate;
366: }
367:
368: /**
369: * @see ConnectionInfoIF#getAge
370: */
371: public long getAge() {
372: return System.currentTimeMillis() - getBirthTime();
373: }
374:
375: /**
376: * @see ConnectionInfoIF#getBirthTime
377: */
378: public void setBirthTime(long birthTime) {
379: birthDate = new Date(birthTime);
380: }
381:
382: /**
383: * @see ConnectionInfoIF#getTimeLastStartActive
384: */
385: public long getTimeLastStartActive() {
386: return timeLastStartActive;
387: }
388:
389: /**
390: * @see ConnectionInfoIF#getTimeLastStartActive
391: */
392: public void setTimeLastStartActive(long timeLastStartActive) {
393: this .timeLastStartActive = timeLastStartActive;
394: setTimeLastStopActive(0);
395: }
396:
397: /**
398: * @see ConnectionInfoIF#getTimeLastStopActive
399: */
400: public long getTimeLastStopActive() {
401: return timeLastStopActive;
402: }
403:
404: /**
405: * @see ConnectionInfoIF#getTimeLastStopActive
406: */
407: public void setTimeLastStopActive(long timeLastStopActive) {
408: this .timeLastStopActive = timeLastStopActive;
409: }
410:
411: /**
412: * @see ConnectionInfoIF#getRequester
413: */
414: public String getRequester() {
415: return requester;
416: }
417:
418: /**
419: * @see ConnectionInfoIF#getRequester
420: */
421: public void setRequester(String requester) {
422: this .requester = requester;
423: }
424:
425: /**
426: * @see ProxyConnectionIF#isNull
427: */
428: public boolean isNull() {
429: return getStatus() == STATUS_NULL;
430: }
431:
432: /**
433: * @see ProxyConnectionIF#isAvailable
434: */
435: public boolean isAvailable() {
436: return getStatus() == STATUS_AVAILABLE;
437: }
438:
439: /**
440: * @see ProxyConnectionIF#isActive
441: */
442: public boolean isActive() {
443: return getStatus() == STATUS_ACTIVE;
444: }
445:
446: /**
447: * @see ProxyConnectionIF#isOffline
448: */
449: public boolean isOffline() {
450: return getStatus() == STATUS_OFFLINE;
451: }
452:
453: /**
454: * @see ProxyConnectionIF#markForExpiry
455: */
456: public void markForExpiry(String reason) {
457: mark = MARK_FOR_EXPIRY;
458: reasonForMark = reason;
459: }
460:
461: /**
462: * @see ProxyConnectionIF#isMarkedForExpiry
463: */
464: public boolean isMarkedForExpiry() {
465: return getMark() == MARK_FOR_EXPIRY;
466: }
467:
468: /**
469: * @see ProxyConnectionIF#getReasonForMark
470: */
471: public String getReasonForMark() {
472: return reasonForMark;
473: }
474:
475: /**
476: * @see ProxyConnectionIF#getConnection
477: */
478: public Connection getConnection() {
479: return connection;
480: }
481:
482: /**
483: * @see Object#toString
484: */
485: public String toString() {
486: return getId() + " is "
487: + ConnectionPool.getStatusDescription(getStatus());
488: }
489:
490: /**
491: * @see ConnectionInfoIF#getDelegateUrl
492: */
493: public String getDelegateUrl() {
494: return delegateUrl;
495: }
496:
497: /**
498: * @see ConnectionInfoIF#getProxyHashcode
499: */
500: public String getProxyHashcode() {
501: return Integer.toHexString(hashCode());
502: }
503:
504: /**
505: * @see ConnectionInfoIF#getDelegateHashcode
506: */
507: public String getDelegateHashcode() {
508: if (connection != null) {
509: return Integer.toHexString(connection.hashCode());
510: } else {
511: return null;
512: }
513: }
514:
515: /**
516: * Compares using {@link #getId()}
517: * @param o must be another {@link ConnectionInfoIF} implementation
518: * @return the comparison
519: * @see Comparable#compareTo(Object)
520: */
521: public int compareTo(Object o) {
522: return new Long(((ConnectionInfoIF) o).getId())
523: .compareTo(new Long(getId()));
524: }
525:
526: public String[] getSqlCalls() {
527: return (String[]) sqlCalls.toArray(new String[0]);
528: }
529:
530: public String getLastSqlCall() {
531: if (sqlCalls != null && sqlCalls.size() > 0) {
532: return (String) sqlCalls.get(sqlCalls.size() - 1);
533: } else {
534: return null;
535: }
536: }
537:
538: public void addSqlCall(String sqlCall) {
539: this.sqlCalls.add(sqlCall);
540: }
541: }
|