001: /**********************************************************************
002: Copyright (c) 2006 Jorg von Frantzius and others. All rights reserved.
003: Licensed under the Apache License, Version 2.0 (the "License");
004: you may not use this file except in compliance with the License.
005: You may obtain a copy of the License at
006:
007: http://www.apache.org/licenses/LICENSE-2.0
008:
009: Unless required by applicable law or agreed to in writing, software
010: distributed under the License is distributed on an "AS IS" BASIS,
011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: See the License for the specific language governing permissions and
013: limitations under the License.
014:
015: Contributors:
016: 2006 Andy Jefferson - localised, adapted to latest CVS
017: 2007 GUido Anzuoni - move TX Manager lookup to OMFContext
018: ...
019: **********************************************************************/package org.jpox;
020:
021: import javax.transaction.RollbackException;
022: import javax.transaction.Status;
023: import javax.transaction.Synchronization;
024: import javax.transaction.SystemException;
025: import javax.transaction.TransactionManager;
026:
027: import org.jpox.transaction.JPOXTransactionException;
028: import org.jpox.util.JPOXLogger;
029:
030: /**
031: * A transaction that is synchronized with a Java Transaction Service (JTA) transaction.
032: * Works only in a J2EE environments where a TranactionManager is present
033: * <p>
034: * When this feature is turned on, transactions must be controlled using javax.transaction.UserTransaction,
035: * not e.g. using PM.currentTransaction().begin(). Should also work for SessionBeans, as
036: * per spec UserTransaction reflects SessionBean-based tx demarcation.
037: *
038: * {@link org.jpox.Transaction}
039: *
040: * @version $Revision: 1.12 $
041: */
042: public class JTATransactionImpl extends TransactionImpl implements
043: Synchronization {
044: /** TransactionManager. **/
045: private TransactionManager tm;
046:
047: /** JTA transaction we currently are synchronized with. Null when there is no JTA transaction active or not yet detected. */
048: private javax.transaction.Transaction jtaTx;
049:
050: private boolean markedForRollback = false;
051:
052: /**
053: * Constructor.
054: * @param om Object Manager
055: */
056: JTATransactionImpl(ObjectManager om) {
057: super (om);
058: joinTransaction();
059: }
060:
061: /**
062: * Accessor for whether the transaction is active.
063: * @return Whether the transaction is active.
064: **/
065: public boolean isActive() {
066: boolean isActive = super .isActive();
067: if (isActive) {
068: //do not join transaction if org.jpox.Transaction already started
069: return true;
070: }
071: joinTransaction();
072: return active;
073: }
074:
075: // ------------------- Methods to get the JTA transaction for synchronising --------------------------
076:
077: /**
078: * Synchronize our active state with that of the JTA tx, if it exists.
079: * Look for an active JTA transaction. if there is one, begin() ourselves
080: * and register synchronization. We must poll because there is no
081: * way of getting notified of a newly begun transaction.<p>
082: */
083: private synchronized void joinTransaction() {
084: if (active) {
085: return;
086: }
087:
088: // try to registerSynchronization()
089: try {
090: if (tm == null) {
091: tm = obtainTransactionManager();
092: }
093: jtaTx = tm.getTransaction();
094: if (jtaTx != null
095: && jtaTx.getStatus() == Status.STATUS_ACTIVE) {
096: if (!om.getOMFContext().getPersistenceConfiguration()
097: .isJcaMode()) {
098: //in JCA mode, we do not register Synchronization
099: jtaTx.registerSynchronization(this );
100: }
101:
102: //the transaction is active here
103: begin();
104: } else {
105: // jtaTx can be null when there is no active transaction.
106: // There is no app-server agnostic way of getting notified
107: // when a global transaction has started. Instead, we
108: // poll for jtaTx' status in getConnection() and isActive()
109:
110: // If a transaction was marked for rollback before we could
111: // register synchronization, we won't be called back when it
112: // is rolled back
113: if (markedForRollback) {
114: // as jtaTx is null there is no active transaction, meaning
115: // that the jtaTx was actually rolled back after it had
116: // been marked for rollback: catch up
117: rollback();
118: markedForRollback = false;
119: }
120: }
121: } catch (SystemException se) {
122: throw new JPOXTransactionException(LOCALISER.msg("015026"),
123: se);
124: } catch (RollbackException e) {
125: // tx is marked for rollback: leave registeredSynchronizationOnJtaTx==false
126: // so that we try to register again next time we're called
127: }
128: }
129:
130: /**
131: * Accessor for the JTA TransactionManager. Unfortunately, before J2EE 5 there is no specified way to do it,
132: * only appserver-specific ways. Taken from http://www.onjava.com/pub/a/onjava/2005/07/20/transactions.html.
133: * <P>
134: * In J2EE 5, we'll be able to use the following
135: * https://glassfish.dev.java.net/nonav/javaee5/api/s1as-javadocs/javax/transaction/TransactionSynchronizationRegistry.html
136: * @return The TransactionManager
137: * @throws JPOXTransactionException if an error occurs obtaining the transaction manager
138: */
139: private TransactionManager obtainTransactionManager() {
140: TransactionManager tm = om.getOMFContext()
141: .getJtaTransactionManager();
142: if (tm == null) {
143: throw new JPOXTransactionException(LOCALISER.msg("015030"));
144: } else {
145: return tm;
146: }
147: }
148:
149: // --------------------------- methods for javax.transaction.Synchronization -----------------------------
150:
151: /**
152: * The beforeCompletion method is called by the transaction manager prior to the start of the two-phase
153: * transaction commit process.
154: */
155: public void beforeCompletion() {
156: try {
157: internalPreCommit();
158: } catch (Throwable th) {
159: // TODO Localise these messages
160: JPOXLogger.TRANSACTION
161: .error(
162: "Exception flushing work in JTA transaction. Mark for rollback",
163: th);
164: try {
165: jtaTx.setRollbackOnly();
166: } catch (Exception e) {
167: JPOXLogger.TRANSACTION
168: .fatal(
169: "Cannot mark transaction for rollback after exception in beforeCompletion. PersistenceManager might be in inconsistent state",
170: e);
171: }
172: }
173: }
174:
175: /**
176: * This method is called by the transaction manager after the transaction is committed or rolled back.
177: * Must be synchronized because some callees expect to be owner of this object's monitor (internalPostCommit()
178: * calls closeSQLConnection() which calls notifyAll()).
179: * @param status The status
180: */
181: public synchronized void afterCompletion(int status) {
182: try {
183: if (status == Status.STATUS_ROLLEDBACK) {
184: rollback();
185: } else if (status == Status.STATUS_COMMITTED) {
186: internalPostCommit();
187: } else {
188: // this method is called after*Completion*(), so we can expect not to be confronted with intermediate status codes
189: // TODO Localise this
190: JPOXLogger.TRANSACTION
191: .fatal("Received unexpected transaction status + "
192: + status);
193: }
194: } catch (Throwable th) {
195: // TODO Localise this
196: JPOXLogger.TRANSACTION
197: .error("Exception during afterCompletion in JTA transaction. PersistenceManager might be in inconsistent state");
198: } finally {
199: // done with this jtaTx. Make us synchronizeWithJta() again,
200: // as there there is no callback for a newly begun tx
201: jtaTx = null;
202: }
203: }
204: }
|