001: package org.gomba;
002:
003: import java.sql.Connection;
004: import java.sql.SQLException;
005: import java.util.Date;
006:
007: import javax.sql.DataSource;
008:
009: import org.gomba.utils.servlet.ServletLogger;
010:
011: /**
012: * Represents a JDBC transaction spanned across multiple HTTP requests.
013: *
014: * <p>
015: * We didn't use HttpSession because we were not happy with the standard
016: * unRESTful session tracking mechanisms (cookies, url rewriting). A positive
017: * side-effect is having strongly typed transaction data (instead of session
018: * "attributes").
019: * </p>
020: *
021: * @author Flavio Tordini
022: * @version $Id: Transaction.java,v 1.4 2005/12/07 11:19:16 flaviotordini Exp $
023: */
024: class Transaction {
025:
026: private final ServletLogger logger;
027:
028: private final String uri;
029:
030: private final int maxInactiveInterval;
031:
032: private final Date creationTime;
033:
034: private long lastAccessedTime;
035:
036: private final DataSource dataSource;
037:
038: private Connection connection;
039:
040: /**
041: * Constructor.
042: *
043: * @param logger
044: * Our custom logger.
045: * @param dataSource
046: * A JDBC DataSource
047: * @param uri
048: * A URI that uniquely identifies this transaction.
049: * @param maxInactiveInterval
050: * The maximum number of seconds for this transaction to remain
051: * active
052: */
053: protected Transaction(ServletLogger logger, DataSource dataSource,
054: String uri, int maxInactiveInterval) {
055: this .logger = logger;
056: this .dataSource = dataSource;
057: this .uri = uri;
058: this .maxInactiveInterval = maxInactiveInterval;
059: this .creationTime = new Date();
060: this .lastAccessedTime = this .creationTime.getTime();
061: }
062:
063: /**
064: * @return Returns the connection. Do not close this connection, it will be
065: * closed by commit() or rollback().
066: */
067: protected synchronized Connection getConnection()
068: throws SQLException {
069: if (this .connection == null) {
070: this .connection = this .dataSource.getConnection();
071: this .connection.setAutoCommit(false);
072: }
073: this .lastAccessedTime = System.currentTimeMillis();
074: return this .connection;
075: }
076:
077: /**
078: * @return true if the transaction has expired.
079: */
080: public boolean isExpired() {
081: return System.currentTimeMillis() - this .lastAccessedTime > this .maxInactiveInterval * 1000;
082: }
083:
084: /**
085: * Close a JDBC connection.
086: */
087: private synchronized void closeConnection() throws SQLException {
088: try {
089: this .connection.setAutoCommit(true);
090: } finally {
091: try {
092: if (!this .connection.isClosed()) {
093: this .connection.close();
094: }
095: } finally {
096: // set the class member to null. finalize() will check for non
097: // null values.
098: this .connection = null;
099: this .logger
100: .debug("Transaction "
101: + this .uri
102: + " completed in "
103: + (System.currentTimeMillis() - this .creationTime
104: .getTime()) + "ms");
105: }
106: }
107: }
108:
109: /**
110: * Commit this transaction.
111: */
112: public final void commit() throws IllegalStateException,
113: SQLException {
114: if (this .connection == null) {
115: this .logger.debug("Nothing to commit for transaction: "
116: + this .uri);
117: return;
118: }
119: try {
120: this .connection.commit();
121: } finally {
122: closeConnection();
123: }
124: }
125:
126: /**
127: * Rollback this transaction.
128: */
129: public final void rollback() throws IllegalStateException,
130: SQLException {
131: if (this .connection == null) {
132: // do not throw an exception here, because a client could have
133: // started the transaction, then encountered an error before being
134: // able to perform an operation for the transaction.
135: this .logger.debug("Nothing to rollback for transaction: "
136: + this .uri);
137: return;
138: }
139: try {
140: this .connection.rollback();
141: } finally {
142: closeConnection();
143: }
144:
145: }
146:
147: /**
148: * Returns the time when this transaction was created.
149: *
150: * @return a <code>java.util.Date</code> specifying when this transaction
151: * was created.
152: */
153: public Date getCreationTime() {
154: return this .creationTime;
155: }
156:
157: /**
158: * Returns the last time the client sent a request associated with this
159: * transaction, as the number of milliseconds since midnight January 1, 1970
160: * GMT, and marked by the time the container received the request.
161: *
162: * @return a long representing the last time the client sent a request
163: * associated with this transaction, expressed in milliseconds since
164: * 1/1/1970 GMT
165: */
166: public long getLastAccessedTime() {
167: return this .lastAccessedTime;
168: }
169:
170: /**
171: * Returns the maximum time interval, in seconds, that this transaction will
172: * be kept open between client accesses. After this interval, the
173: * transaction will be invalidated.
174: *
175: * @return an integer specifying the number of seconds this transaction
176: * remains open between client requests. It is always greater than
177: * zero.
178: */
179: public int getMaxInactiveInterval() {
180: return this .maxInactiveInterval;
181: }
182:
183: /**
184: * Returns a string containing the URI assigned to this transaction.
185: *
186: * @return a string specifying the URI assigned to this transaction
187: */
188: public String getUri() {
189: return this .uri;
190: }
191:
192: /**
193: * @see java.lang.Object#finalize()
194: */
195: protected final void finalize() throws Throwable {
196: if (this .connection != null) {
197: this .logger.log("Transaction " + this .uri
198: + " has not completed before finalization time.");
199: try {
200: rollback();
201: } catch (Exception e) {
202: this .logger.log("Error rolling back transaction "
203: + this .uri + " in finalize()", e);
204: }
205: }
206: }
207:
208: }
|