001: /*
002: * Licensed under the X license (see http://www.x.org/terms.htm)
003: */
004: package org.ofbiz.minerva.pool.jdbc.xa.wrapper;
005:
006: import java.sql.Connection;
007: import java.sql.SQLException;
008: import java.util.ArrayList;
009: import java.util.Vector;
010:
011: import javax.sql.ConnectionEvent;
012: import javax.sql.ConnectionEventListener;
013: import javax.sql.XAConnection;
014: import javax.transaction.xa.XAResource;
015:
016: import org.ofbiz.minerva.pool.PoolEvent;
017: import org.ofbiz.minerva.pool.PoolEventListener;
018: import org.ofbiz.minerva.pool.PooledObject;
019: import org.ofbiz.minerva.pool.cache.ObjectCache;
020: import org.ofbiz.minerva.pool.jdbc.ConnectionInPool;
021:
022: /**
023: * A transaction wrapper around a java.sql.Connection. This provides access to
024: * an XAResource (there is a one-to-one mapping between XAResource and
025: * XAConnection) and a java.sql.Connection (in this implementation, there is
026: * also a one-to-one mapping between XAConnection and java.sql.Connection).
027: * In order to pool java.sql.Connections in a transactional environment, this
028: * is the class that should be pooled - though you could pool the connections,
029: * there is no need to create and destroy these wrappers so frequently.
030: *
031: * <P>Note that there con only be one transaction at a time accessing one of
032: * these wrappers, and requests to a pool for multiple connections on behalf of
033: * one transaction should use the same wrapper. This is because there is no
034: * distinction between connections and transactions in a java.sql.Connection,
035: * and work done by one connection on behalf of a transaction would not be
036: * visible to another connection working on behalf of the same transaction - you
037: * would have effectively created two transactions.</P>
038: *
039: * <P>This also implies that an XAConnection should not be released to a
040: * connection pool until the work has been committed or rolled back. However,
041: * it must sent the close notification as usual in order to be delisted from
042: * the transaction. So the ConnectionEventListener must not release the
043: * XAConnection to a pool when it receives the close event. Instead, it should
044: * also register a TransactionListener that will be notified when the
045: * Transaction is finished, and release the XAConnection at that time.</P>
046: * @see org.ofbiz.minerva.pool.jdbc.xa.wrapper.TransactionListener
047: *
048: * @author Aaron Mulder (ammulder@alumni.princeton.edu)
049: * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
050: *
051: * REVISIONS:
052: * 20010703 bill added code for transaction isolation
053: */
054: public class XAConnectionImpl implements XAConnection, PooledObject {
055:
056: private final static String CLOSED = "Connection has been closed!";
057: private Connection con;
058: private XAResourceImpl resource;
059: private Vector listeners, poolListeners;
060: private ArrayList clientConnections;
061: private TransactionListener transListener;
062: private int preparedStatementCacheSize = 0;
063: private int clientConnectionCount = 0;
064: /** The JDBC user name used to open an underlying connection */
065: private String user;
066: /** The JDBC password used to open an underlying connection */
067: private String password;
068: private boolean saveStackTrace;
069:
070: /**
071: * Creates a new transactional wrapper.
072: * @param con The underlying non-transactional Connection.
073: * @param resource The transaction resource used to enlist this
074: * connection in a transaction.
075: */
076: public XAConnectionImpl(Connection con, XAResourceImpl resource,
077: boolean saveStackTrace) {
078: this .con = con;
079: this .resource = resource;
080: listeners = new Vector();
081: poolListeners = new Vector();
082: clientConnections = new ArrayList();
083: this .saveStackTrace = saveStackTrace;
084: }
085:
086: /**
087: * Sets the transaction listener.
088: */
089: public void setTransactionListener(TransactionListener tl) {
090: transListener = tl;
091: }
092:
093: /**
094: * Clears the transaction listener.
095: */
096: public void clearTransactionListener() {
097: transListener = null;
098: }
099:
100: /**
101: * Sets the number of PreparedStatements to be cached for each
102: * Connection. Your DB product may impose a limit on the number
103: * of open PreparedStatements.
104: */
105: public void setPSCacheSize(int maxSize) {
106: preparedStatementCacheSize = maxSize;
107: }
108:
109: /**
110: * Gets the number of PreparedStatements to be cached for each
111: * Connection.
112: */
113: public int getPSCacheSize() {
114: return preparedStatementCacheSize;
115: }
116:
117: public void setTransactionIsolation(int iso) throws SQLException {
118: con.setTransactionIsolation(iso);
119: }
120:
121: /**
122: * Shuts down this wrapper (and the underlying Connection) permanently.
123: */
124: public void close() {
125: try {
126: con.close();
127: } catch (SQLException e) {
128: }
129: ObjectCache cache = (ObjectCache) ConnectionInPool.psCaches
130: .remove(con);
131: if (cache != null)
132: cache.close();
133: con = null;
134: resource = null;
135: listeners.clear();
136: listeners = null;
137: }
138:
139: /**
140: * Indicates that the connection given to the client has been closed.
141: * If there is currently a transaction, this object should not be closed or
142: * returned to a pool. If not, it can be closed or returned immediately.
143: */
144: public void clientConnectionClosed(XAClientConnection clientCon) {
145: synchronized (clientConnections) {
146: clientConnections.remove(clientCon);
147: }
148: if (clientConnections.size() > 0)
149: return; // Only take action if the last connection referring to this is closed
150: boolean trans = resource.isTransaction(); // could be committed directly on notification? Seems unlikely, but let's not rule it out.
151: Vector local = (Vector) listeners.clone();
152: for (int i = local.size() - 1; i >= 0; i--)
153: ((ConnectionEventListener) local.elementAt(i))
154: .connectionClosed(new ConnectionEvent(this ));
155: // if(!trans)
156: // transactionFinished();
157: }
158:
159: /**
160: * Indicates that the outstanding transaction has finished and this object
161: * can be closed or returned to a pool. This dispatches a close event to
162: * all listeners.
163: * @see #addConnectionEventListener
164: */
165: public void transactionFinished() {
166: if (transListener != null)
167: transListener.transactionFinished(this );
168: }
169:
170: /**
171: * Indicates that the outstanding transaction has finished with a fatal
172: * error, and this object should be closed or permanently removed from a
173: * pool. This dispatches a close event to all listeners.
174: * @see #addConnectionEventListener
175: */
176: public void transactionFailed() {
177: if (transListener != null)
178: transListener.transactionFailed(this );
179: }
180:
181: /**
182: * Indicates that the connection given to the client has had an error.
183: * If there is currently a transaction, this object should not be closed or
184: * returned to a pool. If not, it can be closed or returned immediately.
185: */
186: public void setConnectionError(SQLException e) {
187: Vector local = (Vector) listeners.clone();
188: for (int i = local.size() - 1; i >= 0; i--) {
189: try {
190: ((ConnectionEventListener) local.elementAt(i))
191: .connectionErrorOccurred(new ConnectionEvent(
192: this , e));
193: } catch (RuntimeException ex) {
194: // there can be thrown an induced exception,
195: // but we must report to client the original one, right?
196: ex.printStackTrace();
197: }
198: }
199: }
200:
201: /**
202: * Rolls back the underlying connection. This is used when there is no
203: * current transaction and the connection is returned to the pool - since
204: * no transaction will be committed or rolled back but this connection
205: * will be reused, we must roll it back. This is only done if autocommit is
206: * false.
207: */
208: public void rollback() throws SQLException {
209: if (con.getAutoCommit() == false)
210: con.rollback();
211: }
212:
213: // ---- Implementation of javax.sql.XAConnection ----
214:
215: public XAResource getXAResource() {
216: return resource;
217: }
218:
219: public void addConnectionEventListener(
220: ConnectionEventListener listener) {
221: listeners.addElement(listener);
222: }
223:
224: public void removeConnectionEventListener(
225: ConnectionEventListener listener) {
226: if (!listeners.remove(listener))
227: throw new IllegalArgumentException();
228: }
229:
230: public Connection getConnection() {
231: XAClientConnection xaCon;
232: synchronized (clientConnections) {
233: xaCon = new XAClientConnection(this , con, saveStackTrace);
234: xaCon.setPSCacheSize(preparedStatementCacheSize);
235: clientConnections.add(xaCon);
236: }
237: return xaCon;
238: }
239:
240: // ---- Implementation of javax.sql.XAConnection ----
241:
242: public void addPoolEventListener(PoolEventListener listener) {
243: poolListeners.addElement(listener);
244: }
245:
246: public void removePoolEventListener(PoolEventListener listener) {
247: poolListeners.removeElement(listener);
248: }
249:
250: /**
251: * Dispatches an event to the pool event listeners.
252: */
253: void firePoolEvent(PoolEvent evt) {
254: Vector local = (Vector) poolListeners.clone();
255: for (int i = local.size() - 1; i >= 0; i--)
256: if (evt.getType() == PoolEvent.OBJECT_CLOSED)
257: ((PoolEventListener) local.elementAt(i))
258: .objectClosed(evt);
259: else if (evt.getType() == PoolEvent.OBJECT_ERROR)
260: ((PoolEventListener) local.elementAt(i))
261: .objectError(evt);
262: else
263: ((PoolEventListener) local.elementAt(i))
264: .objectUsed(evt);
265: }
266:
267: /** Getter for property password.
268: * @return Value of property password.
269: */
270: public java.lang.String getPassword() {
271: return password;
272: }
273:
274: /** Setter for property password.
275: * @param password New value of property password.
276: */
277: public void setPassword(java.lang.String password) {
278: this .password = password;
279: }
280:
281: /** Getter for property user.
282: * @return Value of property user.
283: */
284: public java.lang.String getUser() {
285: return user;
286: }
287:
288: /** Setter for property user.
289: * @param user New value of property user.
290: */
291: public void setUser(java.lang.String user) {
292: this .user = user;
293: }
294:
295: public void forceClientConnectionsClose() {
296: for (int i = 0; i < clientConnections.size(); i++) {
297: XAClientConnection client = (XAClientConnection) clientConnections
298: .get(i);
299: try {
300: client.forcedClose();
301: } catch (SQLException ignored) {
302: }
303: }
304: clientConnections.clear();
305: }
306: }
|