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 org.apache.log4j.Logger;
007:
008: import java.sql.Connection;
009: import java.sql.SQLException;
010:
011: import javax.transaction.xa.XAException;
012: import javax.transaction.xa.XAResource;
013: import javax.transaction.xa.Xid;
014:
015: /**
016: * JTA resource implementation for JDBC 1.0 connections. This is somewhat
017: * limited in two respects. First, it does not support two-phase commits since
018: * JDBC 1.0 does not. It will operate in the presence of two-phase commits, but
019: * will throw heuristic exceptions if there is a failure during a commit or
020: * rollback. Second, it can only be associated with one transaction
021: * at a time, and will throw exceptions if a second transaction tries to
022: * attach before the first has called commit, rollback, or forget.
023: * <P><FONT COLOR="RED"><B>Warning:</B></FONT></P> This implementation assumes
024: * that forget will be called after a failed commit or rollback. Otherwise,
025: * the database connection will never be closed.</P>
026: *
027: * @author Aaron Mulder (ammulder@alumni.princeton.edu)
028: */
029: public class XAResourceImpl implements XAResource {
030:
031: private Connection con;
032: private XAConnectionImpl xaCon;
033: private Xid current;
034: private boolean active = false;
035: private int timeout_ignored = 0;
036: private Logger log = Logger.getLogger(XAResourceImpl.class);
037:
038: /**
039: * Creates a new instance as the transactional resource for the specified
040: * underlying connection.
041: */
042: public XAResourceImpl(Connection con) {
043: this .con = con;
044: }
045:
046: /**
047: * Sets the XAConnection associated with this XAResource. This is required,
048: * but both classes cannot include an instance of the other in their
049: * constructor!
050: * @throws java.lang.IllegalStateException
051: * Occurs when this is called more than once.
052: */
053: void setXAConnection(XAConnectionImpl xaCon) {
054: if (this .xaCon != null)
055: throw new IllegalStateException();
056: this .xaCon = xaCon;
057: }
058:
059: public XAConnectionImpl getXAConnection() {
060: return xaCon;
061: }
062:
063: /**
064: * Gets whether there is outstanding work on behalf of a Transaction. If
065: * there is not, then a connection that is closed will cause the
066: * XAConnection to be closed or returned to a pool. If there is, then the
067: * XAConnection must be kept open until commit or rollback is called.
068: */
069: public boolean isTransaction() {
070: return current != null;
071: }
072:
073: /**
074: * Closes this instance permanently.
075: */
076: public void close() {
077: con = null;
078: current = null;
079: xaCon = null;
080: }
081:
082: /**
083: * Commits a transaction.
084: * @throws XAException
085: * Occurs when the state was not correct (end never called), the
086: * transaction ID is wrong, the connection was set to Auto-Commit,
087: * or the commit on the underlying connection fails. The error code
088: * differs depending on the exact situation.
089: */
090: public void commit(Xid id, boolean twoPhase) throws XAException {
091: // System.out.println("commit: " + xaCon + ", current: " + current + ", xid: " + id + ", active: " + active);
092: if (active && !twoPhase) // End was not called!
093: System.err
094: .println("WARNING: Connection not closed before transaction commit.\nConnection will not participate in any future transactions.\nAre you sure you want to be doing this?");
095: if (current == null || !id.equals(current)) // wrong Xid
096: {
097: throwXAException(XAException.XAER_NOTA);
098: }
099:
100: try {
101: if (con.getAutoCommit()) {
102: throwXAException(XAException.XA_HEURCOM);
103: }
104: } catch (SQLException e) {
105: log.error(e);
106: }
107:
108: try {
109: con.commit();
110: } catch (SQLException e) {
111: log.error(e);
112: try {
113: con.rollback();
114: if (!twoPhase) {
115: throwXAException(XAException.XA_RBROLLBACK);
116: }
117: } catch (SQLException e2) {
118: }
119: if (twoPhase) {
120: throwXAException(XAException.XA_HEURRB); // no 2PC!
121: } else {
122: throwXAException(XAException.XA_RBOTHER); // no 2PC!
123: }
124: // Truly, neither committed nor rolled back. Ouch!
125: }
126: current = null;
127: if (active) {
128: active = false; // No longer associated with the original transaction
129: } else {
130: xaCon.transactionFinished(); // No longer in use at all
131: }
132: }
133:
134: /**
135: * Dissociates a resource from a global transaction.
136: * @throws XAException
137: * Occurs when the state was not correct (end called twice), or the
138: * transaction ID is wrong.
139: */
140: public void end(Xid id, int flags) throws XAException {
141: //System.out.println("end: " + xaCon + ", current: " + current + ", xid: " + id + ", active: " + active);
142: if (!active) // End was called twice!
143: {
144: throwXAException(XAException.XAER_PROTO);
145: }
146: if (current == null || !id.equals(current)) {
147: throwXAException(XAException.XAER_NOTA);
148: }
149: active = false;
150: }
151:
152: /**
153: * Indicates that no further action will be taken on behalf of this
154: * transaction (after a heuristic failure). It is assumed this will be
155: * called after a failed commit or rollback.
156: * @throws XAException
157: * Occurs when the state was not correct (end never called), or the
158: * transaction ID is wrong.
159: */
160: public void forget(Xid id) throws XAException {
161: if (current == null || !id.equals(current)) {
162: throwXAException(XAException.XAER_NOTA);
163: }
164: current = null;
165: xaCon.transactionFailed();
166: if (active) // End was not called!
167: System.err
168: .println("WARNING: Connection not closed before transaction forget.\nConnection will not participate in any future transactions.\nAre you sure you want to be doing this?");
169: }
170:
171: /**
172: * Gets the transaction timeout.
173: */
174: public int getTransactionTimeout() throws XAException {
175: return timeout_ignored;
176: }
177:
178: /**
179: * Since the concept of resource managers does not really apply here (all
180: * JDBC connections must be managed individually), indicates whether the
181: * specified resource is the same as this one.
182: */
183: public boolean isSameRM(XAResource res) throws XAException {
184: return res == this ;
185: }
186:
187: /**
188: * Prepares a transaction to commit. Since JDBC 1.0 does not support
189: * 2-phase commits, this claims the commit is OK (so long as some work was
190: * done on behalf of the specified transaction).
191: * @throws XAException
192: * Occurs when the state was not correct (end never called), the
193: * transaction ID is wrong, or the connection was set to Auto-Commit.
194: */
195: public int prepare(Xid id) throws XAException {
196: //System.out.println("prepare: " + xaCon + ", current: " + current + ", xid: " + id + ", active: " + active);
197: if (active) // End was not called!
198: System.err
199: .println("WARNING: Connection not closed before transaction commit.\nConnection will not participate in any future transactions.\nAre you sure you want to be doing this?");
200: if (current == null || !id.equals(current)) // wrong Xid
201: {
202: throwXAException(XAException.XAER_NOTA);
203: }
204:
205: try {
206: if (con.getAutoCommit()) {
207: throwXAException(XAException.XA_HEURCOM);
208: }
209: } catch (SQLException e) {
210: log.error(e);
211: }
212:
213: return XA_OK;
214: }
215:
216: /**
217: * Returns all transaction IDs where work was done with no corresponding
218: * commit, rollback, or forget. Not really sure why this is useful in the
219: * context of JDBC drivers.
220: */
221: public Xid[] recover(int flag)
222: throws javax.transaction.xa.XAException {
223: if (current == null)
224: return new Xid[0];
225: else
226: return new Xid[] { current };
227: }
228:
229: /**
230: * Rolls back the work, assuming it was done on behalf of the specified
231: * transaction.
232: * @throws XAException
233: * Occurs when the state was not correct (end never called), the
234: * transaction ID is wrong, the connection was set to Auto-Commit,
235: * or the rollback on the underlying connection fails. The error code
236: * differs depending on the exact situation.
237: */
238: public void rollback(Xid id) throws XAException {
239: //System.out.println("rollback: " + xaCon + ", current: " + current + ", xid: " + id + ", active: " + active);
240: if (active) // End was not called!
241: log
242: .error("WARNING: Connection not closed before transaction rollback. Connection will not participate in any future transactions. Are you sure you want to be doing this?");
243: if (current == null || !id.equals(current)) { // wrong Xid
244: throwXAException(XAException.XAER_NOTA);
245: }
246: try {
247: if (con.getAutoCommit()) {
248: throwXAException(XAException.XA_HEURCOM);
249: }
250: } catch (SQLException e) {
251: log.error(e);
252: }
253:
254: try {
255: con.rollback();
256: } catch (SQLException e) {
257: log.error(e);
258: throwXAException("Rollback failed: " + e.getMessage());
259: }
260: current = null;
261: if (active) {
262: active = false; // No longer associated with the original transaction
263: } else {
264: xaCon.transactionFinished(); // No longer in use at all
265: }
266: }
267:
268: /**
269: * Sets the transaction timeout. This is saved, but the value is not used
270: * by the current implementation.
271: */
272: public boolean setTransactionTimeout(int timeout)
273: throws XAException {
274: timeout_ignored = timeout;
275: return true;
276: }
277:
278: /**
279: * Associates a JDBC connection with a global transaction. We assume that
280: * end will be called followed by prepare, commit, or rollback.
281: * If start is called after end but before commit or rollback, there is no
282: * way to distinguish work done by different transactions on the same
283: * connection). If start is called more than once before
284: * end, either it's a duplicate transaction ID or illegal transaction ID
285: * (since you can't have two transactions associated with one DB
286: * connection).
287: * @throws XAException
288: * Occurs when the state was not correct (start called twice), the
289: * transaction ID is wrong, or the instance has already been closed.
290: */
291: public void start(Xid id, int flags) throws XAException {
292: //System.out.println("start: " + xaCon + ", current: " + current + ", xid: " + id + ", active: " + active);
293: if (active) {// Start was called twice!
294: if (current != null && id.equals(current)) {
295: throwXAException(XAException.XAER_DUPID);
296: } else {
297: throwXAException(XAException.XAER_PROTO);
298: }
299: }
300: if (current != null && !id.equals(current)) {
301: //System.out.println("current xid: " + current + ", new xid: " + id);
302: throwXAException(XAException.XAER_NOTA);
303: }
304: if (con == null) {
305: throwXAException(XAException.XA_RBOTHER);
306: }
307: current = id;
308: active = true;
309: }
310:
311: protected void throwXAException(int code) throws XAException {
312: xaCon.setConnectionError(new SQLException(
313: "XAException occured with code: " + code));
314: throw new XAException(code);
315: }
316:
317: protected void throwXAException(String msg) throws XAException {
318: xaCon.setConnectionError(new SQLException(
319: "XAException occured: " + msg));
320: throw new XAException(msg);
321: }
322: }
|