001: /*
002: * Licensed under the X license (see http://www.x.org/terms.htm)
003: */
004: package org.ofbiz.minerva.pool.jdbc.xa;
005:
006: import java.sql.Connection;
007: import java.sql.SQLException;
008: import java.util.Collections;
009: import java.util.HashMap;
010: import java.util.Map;
011:
012: import javax.naming.InitialContext;
013: import javax.naming.NamingException;
014: import javax.sql.ConnectionEvent;
015: import javax.sql.ConnectionEventListener;
016: import javax.sql.XAConnection;
017: import javax.sql.XADataSource;
018: import javax.transaction.Status;
019: import javax.transaction.Transaction;
020: import javax.transaction.TransactionManager;
021: import javax.transaction.xa.XAResource;
022:
023: import org.apache.log4j.Logger;
024: import org.ofbiz.minerva.pool.ObjectPool;
025: import org.ofbiz.minerva.pool.PoolObjectFactory;
026: import org.ofbiz.minerva.pool.jdbc.xa.wrapper.TransactionListener;
027: import org.ofbiz.minerva.pool.jdbc.xa.wrapper.XAConnectionImpl;
028: import org.ofbiz.minerva.pool.jdbc.xa.wrapper.XADataSourceImpl;
029: import org.ofbiz.minerva.pool.jdbc.xa.wrapper.XAResourceImpl;
030:
031: /**
032: * Object factory for JDBC 2.0 standard extension XAConnections. You pool the
033: * XAConnections instead of the java.sql.Connections since with vendor
034: * conformant drivers, you don't have direct access to the java.sql.Connection,
035: * and any work done isn't associated with the java.sql.Connection anyway.
036: * <P><B>Note:</B> This implementation requires that the TransactionManager
037: * be bound to a JNDI name.</P>
038: * <P><B>Note:</B> This implementation has special handling for Minerva JDBC
039: * 1/2 XA Wrappers. Namely, when a request comes in, if it is for a wrapper
040: * connection and it has the same current transaction as a previous active
041: * connection, the same previous connection will be returned. Otherwise,
042: * you won't be able to share changes across connections like you can with
043: * the native JDBC 2 Standard Extension implementations.</P>
044: *
045: * @author Aaron Mulder (ammulder@alumni.princeton.edu)
046: * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
047: *
048: * REVISIONS:
049: * 20010703 bill added code for transaction isolation
050: @version $Revision: 1.2 $
051: */
052: public class XAConnectionFactory extends PoolObjectFactory {
053:
054: public static final int DEFAULT_ISOLATION = -1;
055:
056: private InitialContext ctx;
057: private XADataSource source;
058: private String userName;
059: private String password;
060: private int psCacheSize = 10;
061: private boolean releaseOnCommit = false;
062: private boolean saveStackTrace = false;
063: private int transactionIsolation = DEFAULT_ISOLATION;
064: private ConnectionEventListener listener, errorListener;
065: private TransactionListener transListener;
066: private ObjectPool pool;
067: private final Map wrapperTx = Collections
068: .synchronizedMap(new HashMap());
069: private final Map rms = Collections.synchronizedMap(new HashMap());
070: private TransactionManager tm;
071:
072: private static Logger log = Logger
073: .getLogger(XAConnectionFactory.class);
074:
075: /**
076: * Creates a new factory. You must set the XADataSource and
077: * TransactionManager JNDI name before the factory can be used.
078: */
079: public XAConnectionFactory() throws NamingException {
080: ctx = new InitialContext();
081: //wrapperTx = new HashMap();
082: //rms = new HashMap();
083: errorListener = new ConnectionEventListener() {
084: public void connectionErrorOccurred(ConnectionEvent evt) {
085: if (pool.isInvalidateOnError()) {
086: pool.markObjectAsInvalid(evt.getSource());
087: }
088: }
089:
090: public void connectionClosed(ConnectionEvent evt) {
091: }
092: };
093:
094: listener = new ConnectionEventListener() {
095:
096: public void connectionErrorOccurred(ConnectionEvent evt) {
097: if (pool.isInvalidateOnError()) {
098: pool.markObjectAsInvalid(evt.getSource());
099: }
100: // closeConnection(evt, XAResource.TMFAIL);
101: }
102:
103: public void connectionClosed(ConnectionEvent evt) {
104: closeConnection(evt, XAResource.TMSUCCESS);
105: }
106:
107: private void closeConnection(ConnectionEvent evt, int status) {
108: XAConnection con = (XAConnection) evt.getSource();
109: try {
110: con.removeConnectionEventListener(listener);
111: } catch (IllegalArgumentException e) {
112: return; // Removed twice somehow?
113: }
114: Transaction trans = null;
115: try {
116: if (tm.getStatus() != Status.STATUS_NO_TRANSACTION) {
117: trans = tm.getTransaction();
118: XAResource res = (XAResource) rms.remove(con);
119: if (res != null) {
120: trans.delistResource(res, status);
121: } // end of if ()
122: else {
123: log.warn("no xares in rms for con " + con);
124: } // end of else
125: }
126: } catch (Exception e) {
127: log
128: .error(
129: "Unable to deregister with TransactionManager",
130: e);
131: throw new RuntimeException(
132: "Unable to deregister with TransactionManager: "
133: + e);
134: }
135:
136: if (!(con instanceof XAConnectionImpl)) {
137: // Real XAConnection -> not associated w/ transaction
138: pool.releaseObject(con);
139: } else {
140: XAConnectionImpl xaCon = (XAConnectionImpl) con;
141: if (!((XAResourceImpl) xaCon.getXAResource())
142: .isTransaction()) {
143: // Wrapper - we can only release it if there's no current transaction
144: // Can't just check TM because con may have been committed but left open
145: // so if there's a current transaction it may not apply to the con.
146: log.warn("XAConnectionImpl: " + xaCon
147: + " has no current tx!");
148: try {
149: xaCon.rollback();
150: } catch (SQLException e) {
151: pool.markObjectAsInvalid(con);
152: }
153: pool.releaseObject(con);
154: } else {
155: // Still track errors, but don't try to close again.
156: con.addConnectionEventListener(errorListener);
157: }
158: }
159: }
160: };
161: transListener = new TransactionListener() {
162: public void transactionFinished(XAConnectionImpl con) {
163: con.clearTransactionListener();
164: Object tx = wrapperTx.remove(con);
165: //System.out.println("removing con: " + con + "from wrapperTx, tx: " + tx);
166: if (tx != null)
167: wrapperTx.remove(tx);
168: try {
169: con.removeConnectionEventListener(errorListener);
170: } catch (IllegalArgumentException e) {
171: // connection was not closed, but transaction ended
172: if (!releaseOnCommit) {
173: return;
174: } else {
175: rms.remove(con);
176: pool.markObjectAsInvalid(con);
177: con.forceClientConnectionsClose();
178: }
179: }
180:
181: pool.releaseObject(con);
182: }
183:
184: public void transactionFailed(XAConnectionImpl con) {
185: con.clearTransactionListener();
186: Object tx = wrapperTx.remove(con);
187: if (tx != null)
188: wrapperTx.remove(tx);
189: //System.out.println("removing con: " + con + "from wrapperTx, tx: " + tx);
190: pool.markObjectAsInvalid(con);
191: try {
192: con.removeConnectionEventListener(errorListener);
193: } catch (IllegalArgumentException e) {
194: if (!releaseOnCommit) {
195: return;
196: } else {
197: rms.remove(con);
198: con.forceClientConnectionsClose();
199: }
200: }
201: pool.releaseObject(con);
202: }
203: };
204: }
205:
206: /**
207: * Sets the user name used to generate XAConnections. This is optional,
208: * and will only be used if present.
209: */
210: public void setUser(String userName) {
211: this .userName = userName;
212: }
213:
214: /**
215: * Gets the user name used to generate XAConnections.
216: */
217: public String getUser() {
218: return userName;
219: }
220:
221: /**
222: * Sets the password used to generate XAConnections. This is optional,
223: * and will only be used if present.
224: */
225: public void setPassword(String password) {
226: this .password = password;
227: }
228:
229: /**
230: * Gets the password used to generate XAConnections.
231: */
232: public String getPassword() {
233: return password;
234: }
235:
236: public boolean getReleaseOnCommit() {
237: return releaseOnCommit;
238: }
239:
240: public void setReleaseOnCommit(boolean rel) {
241: releaseOnCommit = rel;
242: }
243:
244: /**
245: * Sets the number of PreparedStatements to be cached for each
246: * Connection. Your DB product may impose a limit on the number
247: * of open PreparedStatements. The default value is 10.
248: */
249: public void setPSCacheSize(int size) {
250: psCacheSize = size;
251: }
252:
253: /**
254: * Gets the number of PreparedStatements to be cached for each
255: * Connection. The default value is 10.
256: */
257: public int getPSCacheSize() {
258: return psCacheSize;
259: }
260:
261: /**
262: * Gets the transaction isolation level of connections. This defaults to
263: * whatever the connection's default iso level is.
264: */
265: public int getTransactionIsolation() {
266: return transactionIsolation;
267: }
268:
269: public void setTransactionIsolation(int iso) {
270: this .transactionIsolation = iso;
271: }
272:
273: public void setTransactionIsolation(String iso) {
274: if (iso.equals("TRANSACTION_NONE")) {
275: this .transactionIsolation = Connection.TRANSACTION_NONE;
276: } else if (iso.equals("TRANSACTION_READ_COMMITTED")) {
277: this .transactionIsolation = Connection.TRANSACTION_READ_COMMITTED;
278: } else if (iso.equals("TRANSACTION_READ_UNCOMMITTED")) {
279: this .transactionIsolation = Connection.TRANSACTION_READ_UNCOMMITTED;
280: } else if (iso.equals("TRANSACTION_REPEATABLE_READ")) {
281: this .transactionIsolation = Connection.TRANSACTION_REPEATABLE_READ;
282: } else if (iso.equals("TRANSACTION_SERIALIZABLE")) {
283: this .transactionIsolation = Connection.TRANSACTION_SERIALIZABLE;
284: } else {
285: throw new IllegalArgumentException(
286: "Setting Isolation level to unknown state: " + iso);
287: }
288: }
289:
290: /**
291: * Sets the XADataSource used to generate XAConnections. This may be
292: * supplied by the vendor, or it may use the wrappers for non-compliant
293: * drivers (see XADataSourceImpl).
294: * @see org.ofbiz.minerva.pool.jdbc.xa.wrapper.XADataSourceImpl
295: */
296: public void setDataSource(XADataSource dataSource) {
297: source = dataSource;
298: }
299:
300: /**
301: * Gets the XADataSource used to generate XAConnections.
302: */
303: public XADataSource getDataSource() {
304: return source;
305: }
306:
307: /**
308: * Sets the TransactionManager.
309: */
310: public void setTransactionManager(TransactionManager tm) {
311: this .tm = tm;
312: }
313:
314: /**
315: * Gets the TransactionManager.
316: */
317: public TransactionManager getTransactionManager() {
318: return this .tm;
319: }
320:
321: /**
322: * Have XAClientConnections save a stack trace on creation
323: * This is useful for debugging non-closed connections.
324: * It must be used with ReleaseOnCommit option
325: */
326: public boolean getSaveStackTrace() {
327: return saveStackTrace;
328: }
329:
330: public void setSaveStackTrace(boolean save) {
331: saveStackTrace = save;
332: }
333:
334: /**
335: * Verifies that the data source and transaction manager are accessible.
336: */
337: public void poolStarted(ObjectPool pool) {
338: if (log.isDebugEnabled())
339: log.debug("Starting");
340:
341: super .poolStarted(pool);
342: this .pool = pool;
343: if (source == null)
344: throw new IllegalStateException(
345: "Must specify XADataSource to "
346: + getClass().getName());
347: if (source instanceof XADataSourceImpl) {
348: ((XADataSourceImpl) source)
349: .setSaveStackTrace(saveStackTrace);
350: }
351:
352: /*
353: if(tmJndiName == null)
354: throw new IllegalStateException("Must specify TransactionManager JNDI Name to "+getClass().getName());
355: if(ctx == null)
356: throw new IllegalStateException("Must specify InitialContext to "+getClass().getName());
357: try {
358: tm = (TransactionManager)ctx.lookup(tmJndiName);
359: } catch(NamingException e) {
360: throw new IllegalStateException("Cannot lookup TransactionManager using specified context and name!");
361: }
362: */
363: }
364:
365: /**
366: * Creates a new XAConnection from the provided XADataSource.
367: */
368: public Object createObject(Object parameters) throws Exception {
369:
370: log.debug("Opening new XAConnection");
371:
372: Object obj = null;
373: try {
374: if (parameters != null) {
375: String credentials[] = (String[]) parameters;
376: if (credentials.length == 2)
377: obj = source.getXAConnection(credentials[0],
378: credentials[1]);
379: } else if (userName != null && userName.length() > 0)
380: obj = source.getXAConnection(userName, password);
381: else
382: obj = source.getXAConnection();
383: } catch (SQLException e) {
384: log.error("Can't get an XAConnection", e);
385: throw e;
386: }
387: return obj;
388: }
389:
390: /**
391: * Registers the XAConnection's XAResource with the current transaction (if
392: * there is one). Sets listeners that will handle deregistering and
393: * returning the XAConnection to the pool via callbacks.
394: */
395: public Object prepareObject(Object pooledObject) {
396: boolean trace = log.isDebugEnabled();
397: XAConnection con = (XAConnection) pooledObject;
398: con.addConnectionEventListener(listener);
399: Transaction trans = null;
400: try {
401: if (tm.getStatus() != Status.STATUS_NO_TRANSACTION) {
402: trans = tm.getTransaction();
403: XAResource res = con.getXAResource();
404: rms.put(con, res);
405: trans.enlistResource(res);
406: if (trace)
407: log.debug("Resource '" + res + "' enlisted for '"
408: + con + "'.");
409: } else {
410: if (trace)
411: log.debug("No transaction right now.");
412: }
413: } catch (Exception e) {
414: //System.out.println("error in prepareObject!!!!!");
415: e.printStackTrace();
416: log.error("Unable to register with TransactionManager", e);
417: con.removeConnectionEventListener(listener);
418: throw new RuntimeException(
419: "Unable to register with TransactionManager: " + e);
420: }
421:
422: if (con instanceof XAConnectionImpl) {
423: ((XAConnectionImpl) con)
424: .setTransactionListener(transListener);
425: ((XAConnectionImpl) con).setPSCacheSize(psCacheSize);
426: if (transactionIsolation != DEFAULT_ISOLATION) {
427: try {
428: ((XAConnectionImpl) con)
429: .setTransactionIsolation(transactionIsolation);
430: } catch (SQLException sex) {
431: throw new RuntimeException(
432: "Unable to setTransactionIsolation: "
433: + sex.getMessage());
434: }
435: }
436:
437: if (trans != null) {
438: //System.out.println("inserting con: " + con + "into wrapperTx, tx: " + trans);
439: wrapperTx.put(con, trans); // For JDBC 1/2 wrappers, remember which
440: wrapperTx.put(trans, con); // connection goes with a given transaction
441: }
442: }
443: return con;
444: }
445:
446: /**
447: * Closes a connection.
448: */
449: public void deleteObject(Object pooledObject) {
450: XAConnection con = (XAConnection) pooledObject;
451: try {
452: con.close();
453: } catch (SQLException e) {
454: }
455: }
456:
457: /**
458: * If a new object is requested and it is a JDBC 1/2 wrapper connection
459: * in the same Transaction as an existing connection, return that same
460: * connection.
461: */
462: public Object isUniqueRequest() {
463: try {
464: if (tm.getStatus() != Status.STATUS_NO_TRANSACTION) {
465: Transaction trans = tm.getTransaction();
466: //System.out.println("isUniqueRequest returning conn: " + wrapperTx.get(trans) + " attached to tx: " + trans);
467: return wrapperTx.get(trans);
468: }
469: } catch (Exception e) {
470: }
471: return null;
472: }
473:
474: /** For XAConnectionImpl check that parameters = String[2]{username, password}
475: and that these match the the source connection user and password. Return
476: true for non-XAConnectionImpl sources
477: */
478: public boolean checkValidObject(Object source, Object parameters) {
479: boolean validObject = true;
480: if (parameters != null && source instanceof XAConnectionImpl) {
481: XAConnectionImpl con = (XAConnectionImpl) source;
482: String credentials[] = (String[]) parameters;
483: if (credentials.length == 2) {
484: String user = con.getUser();
485: String password = con.getPassword();
486: boolean validUser = ((user == null) && (credentials[0] == null))
487: || ((user != null) && user
488: .equals(credentials[0]));
489: boolean validPassword = ((password == null) && (credentials[1] == null))
490: || ((password != null) && password
491: .equals(credentials[1]));
492: validObject = validUser && validPassword;
493: }
494: }
495: return validObject;
496: }
497:
498: }
|