001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.geronimo.transaction.manager;
019
020 import java.util.ArrayList;
021 import java.util.HashMap;
022 import java.util.Iterator;
023 import java.util.List;
024 import java.util.Map;
025
026 import javax.transaction.*;
027 import javax.transaction.xa.XAException;
028 import javax.transaction.xa.Xid;
029
030 import java.util.concurrent.ConcurrentHashMap;
031 import java.util.concurrent.CopyOnWriteArrayList;
032 import org.apache.commons.logging.Log;
033 import org.apache.commons.logging.LogFactory;
034 import org.apache.geronimo.transaction.log.UnrecoverableLog;
035
036 /**
037 * Simple implementation of a transaction manager.
038 *
039 * @version $Rev: 552073 $ $Date: 2007-06-29 21:10:51 -0400 (Fri, 29 Jun 2007) $
040 */
041 public class TransactionManagerImpl implements TransactionManager, UserTransaction, TransactionSynchronizationRegistry, XidImporter, MonitorableTransactionManager, RecoverableTransactionManager {
042 private static final Log log = LogFactory.getLog(TransactionManagerImpl.class);
043 protected static final int DEFAULT_TIMEOUT = 600;
044 protected static final byte[] DEFAULT_TM_ID = new byte[] {71,84,77,73,68};
045
046 final TransactionLog transactionLog;
047 final XidFactory xidFactory;
048 private final int defaultTransactionTimeoutMilliseconds;
049 private final ThreadLocal transactionTimeoutMilliseconds = new ThreadLocal();
050 private final ThreadLocal threadTx = new ThreadLocal();
051 private final ConcurrentHashMap associatedTransactions = new ConcurrentHashMap();
052 private static final Log recoveryLog = LogFactory.getLog("RecoveryController");
053 final Recovery recovery;
054 private final CopyOnWriteArrayList transactionAssociationListeners = new CopyOnWriteArrayList();
055 private List recoveryErrors = new ArrayList();
056
057 public TransactionManagerImpl() throws XAException {
058 this(DEFAULT_TIMEOUT,
059 null,
060 null
061 );
062 }
063
064 public TransactionManagerImpl(int defaultTransactionTimeoutSeconds) throws XAException {
065 this(defaultTransactionTimeoutSeconds,
066 null,
067 null
068 );
069 }
070
071 public TransactionManagerImpl(int defaultTransactionTimeoutSeconds, TransactionLog transactionLog) throws XAException {
072 this(defaultTransactionTimeoutSeconds,
073 null,
074 transactionLog
075 );
076 }
077
078 public TransactionManagerImpl(int defaultTransactionTimeoutSeconds, XidFactory xidFactory, TransactionLog transactionLog) throws XAException {
079 if (defaultTransactionTimeoutSeconds <= 0) {
080 throw new IllegalArgumentException("defaultTransactionTimeoutSeconds must be positive: attempted value: " + defaultTransactionTimeoutSeconds);
081 }
082
083 this.defaultTransactionTimeoutMilliseconds = defaultTransactionTimeoutSeconds * 1000;
084
085 if (transactionLog == null) {
086 this.transactionLog = new UnrecoverableLog();
087 } else {
088 this.transactionLog = transactionLog;
089 }
090
091 if (xidFactory != null) {
092 this.xidFactory = xidFactory;
093 } else {
094 this.xidFactory = new XidFactoryImpl(DEFAULT_TM_ID);
095 }
096
097 recovery = new RecoveryImpl(this.transactionLog, this.xidFactory);
098 }
099
100 public Transaction getTransaction() {
101 return (Transaction) threadTx.get();
102 }
103
104 private void associate(TransactionImpl tx) throws InvalidTransactionException {
105 if (tx == null) throw new NullPointerException("tx is null");
106
107 Object existingAssociation = associatedTransactions.putIfAbsent(tx, Thread.currentThread());
108 if (existingAssociation != null) {
109 throw new InvalidTransactionException("Specified transaction is already associated with another thread");
110 }
111 threadTx.set(tx);
112 fireThreadAssociated(tx);
113 }
114
115 private void unassociate() {
116 Transaction tx = getTransaction();
117 if (tx != null) {
118 associatedTransactions.remove(tx);
119 threadTx.set(null);
120 fireThreadUnassociated(tx);
121 }
122 }
123
124 public void setTransactionTimeout(int seconds) throws SystemException {
125 if (seconds < 0) {
126 throw new SystemException("transaction timeout must be positive or 0 to reset to default");
127 }
128 if (seconds == 0) {
129 transactionTimeoutMilliseconds.set(null);
130 } else {
131 transactionTimeoutMilliseconds.set(new Long(seconds * 1000));
132 }
133 }
134
135 public int getStatus() throws SystemException {
136 Transaction tx = getTransaction();
137 return (tx != null) ? tx.getStatus() : Status.STATUS_NO_TRANSACTION;
138 }
139
140 public void begin() throws NotSupportedException, SystemException {
141 begin(getTransactionTimeoutMilliseconds(0L));
142 }
143
144 public Transaction begin(long transactionTimeoutMilliseconds) throws NotSupportedException, SystemException {
145 if (getStatus() != Status.STATUS_NO_TRANSACTION) {
146 throw new NotSupportedException("Nested Transactions are not supported");
147 }
148 TransactionImpl tx = new TransactionImpl(xidFactory, transactionLog, getTransactionTimeoutMilliseconds(transactionTimeoutMilliseconds));
149 // timeoutTimer.schedule(tx, getTransactionTimeoutMilliseconds(transactionTimeoutMilliseconds));
150 try {
151 associate(tx);
152 } catch (InvalidTransactionException e) {
153 // should not be possible since we just created that transaction and no one has a reference yet
154 throw (SystemException)new SystemException("Internal error: associate threw an InvalidTransactionException for a newly created transaction").initCause(e);
155 }
156 // Todo: Verify if this is correct thing to do. Use default timeout for next transaction.
157 this.transactionTimeoutMilliseconds.set(null);
158 return tx;
159 }
160
161 public Transaction suspend() throws SystemException {
162 Transaction tx = getTransaction();
163 if (tx != null) {
164 unassociate();
165 }
166 return tx;
167 }
168
169 public void resume(Transaction tx) throws IllegalStateException, InvalidTransactionException, SystemException {
170 if (getTransaction() != null) {
171 throw new IllegalStateException("Thread already associated with another transaction");
172 }
173 if (!(tx instanceof TransactionImpl)) {
174 throw new InvalidTransactionException("Cannot resume foreign transaction: " + tx);
175 }
176 associate((TransactionImpl) tx);
177 }
178
179 public Object getResource(Object key) {
180 TransactionImpl tx = getActiveTransactionImpl();
181 return tx.getResource(key);
182 }
183
184 private TransactionImpl getActiveTransactionImpl() {
185 TransactionImpl tx = (TransactionImpl)threadTx.get();
186 if (tx == null) {
187 throw new IllegalStateException("No tx on thread");
188 }
189 if (tx.getStatus() != Status.STATUS_ACTIVE && tx.getStatus() != Status.STATUS_MARKED_ROLLBACK) {
190 throw new IllegalStateException("Transaction " + tx + " is not active");
191 }
192 return tx;
193 }
194
195 public boolean getRollbackOnly() {
196 TransactionImpl tx = getActiveTransactionImpl();
197 return tx.getRollbackOnly();
198 }
199
200 public Object getTransactionKey() {
201 TransactionImpl tx = getActiveTransactionImpl();
202 return tx.getTransactionKey();
203 }
204
205 public int getTransactionStatus() {
206 TransactionImpl tx = (TransactionImpl) getTransaction();
207 return tx == null? Status.STATUS_NO_TRANSACTION: tx.getTransactionStatus();
208 }
209
210 public void putResource(Object key, Object value) {
211 TransactionImpl tx = getActiveTransactionImpl();
212 tx.putResource(key, value);
213 }
214
215 /**
216 * jta 1.1 method so the jpa implementations can be told to flush their caches.
217 * @param synchronization
218 */
219 public void registerInterposedSynchronization(Synchronization synchronization) {
220 TransactionImpl tx = getActiveTransactionImpl();
221 tx.registerInterposedSynchronization(synchronization);
222 }
223
224 public void setRollbackOnly() throws IllegalStateException {
225 TransactionImpl tx = (TransactionImpl) threadTx.get();
226 if (tx == null) {
227 throw new IllegalStateException("No transaction associated with current thread");
228 }
229 tx.setRollbackOnly();
230 }
231
232 public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException, RollbackException, SecurityException, SystemException {
233 Transaction tx = getTransaction();
234 if (tx == null) {
235 throw new IllegalStateException("No transaction associated with current thread");
236 }
237 try {
238 tx.commit();
239 } finally {
240 unassociate();
241 }
242 }
243
244 public void rollback() throws IllegalStateException, SecurityException, SystemException {
245 Transaction tx = getTransaction();
246 if (tx == null) {
247 throw new IllegalStateException("No transaction associated with current thread");
248 }
249 try {
250 tx.rollback();
251 } finally {
252 unassociate();
253 }
254 }
255
256 //XidImporter implementation
257 public Transaction importXid(Xid xid, long transactionTimeoutMilliseconds) throws XAException, SystemException {
258 if (transactionTimeoutMilliseconds < 0) {
259 throw new SystemException("transaction timeout must be positive or 0 to reset to default");
260 }
261 TransactionImpl tx = new TransactionImpl(xid, xidFactory, transactionLog, getTransactionTimeoutMilliseconds(transactionTimeoutMilliseconds));
262 return tx;
263 }
264
265 public void commit(Transaction tx, boolean onePhase) throws XAException {
266 if (onePhase) {
267 try {
268 tx.commit();
269 } catch (HeuristicMixedException e) {
270 throw (XAException) new XAException().initCause(e);
271 } catch (HeuristicRollbackException e) {
272 throw (XAException) new XAException().initCause(e);
273 } catch (RollbackException e) {
274 throw (XAException) new XAException().initCause(e);
275 } catch (SecurityException e) {
276 throw (XAException) new XAException().initCause(e);
277 } catch (SystemException e) {
278 throw (XAException) new XAException().initCause(e);
279 }
280 } else {
281 try {
282 ((TransactionImpl) tx).preparedCommit();
283 } catch (SystemException e) {
284 throw (XAException) new XAException().initCause(e);
285 }
286 }
287 }
288
289 public void forget(Transaction tx) throws XAException {
290 //TODO implement this!
291 }
292
293 public int prepare(Transaction tx) throws XAException {
294 try {
295 return ((TransactionImpl) tx).prepare();
296 } catch (SystemException e) {
297 throw (XAException) new XAException().initCause(e);
298 } catch (RollbackException e) {
299 throw (XAException) new XAException().initCause(e);
300 }
301 }
302
303 public void rollback(Transaction tx) throws XAException {
304 try {
305 tx.rollback();
306 } catch (IllegalStateException e) {
307 throw (XAException) new XAException().initCause(e);
308 } catch (SystemException e) {
309 throw (XAException) new XAException().initCause(e);
310 }
311 }
312
313 long getTransactionTimeoutMilliseconds(long transactionTimeoutMilliseconds) {
314 if (transactionTimeoutMilliseconds != 0) {
315 return transactionTimeoutMilliseconds;
316 }
317 Long timeout = (Long) this.transactionTimeoutMilliseconds.get();
318 if (timeout != null) {
319 return timeout.longValue();
320 }
321 return defaultTransactionTimeoutMilliseconds;
322 }
323
324 //Recovery
325 public void recoveryError(Exception e) {
326 recoveryLog.error(e);
327 recoveryErrors.add(e);
328 }
329
330 public void recoverResourceManager(NamedXAResource xaResource) {
331 try {
332 recovery.recoverResourceManager(xaResource);
333 } catch (XAException e) {
334 recoveryError(e);
335 }
336 }
337
338 public Map getExternalXids() {
339 return new HashMap(recovery.getExternalXids());
340 }
341
342 public void addTransactionAssociationListener(TransactionManagerMonitor listener) {
343 transactionAssociationListeners.addIfAbsent(listener);
344 }
345
346 public void removeTransactionAssociationListener(TransactionManagerMonitor listener) {
347 transactionAssociationListeners.remove(listener);
348 }
349
350 protected void fireThreadAssociated(Transaction tx) {
351 for (Iterator iterator = transactionAssociationListeners.iterator(); iterator.hasNext();) {
352 TransactionManagerMonitor listener = (TransactionManagerMonitor) iterator.next();
353 try {
354 listener.threadAssociated(tx);
355 } catch (Exception e) {
356 log.warn("Error calling transaction association listener", e);
357 }
358 }
359 }
360
361 protected void fireThreadUnassociated(Transaction tx) {
362 for (Iterator iterator = transactionAssociationListeners.iterator(); iterator.hasNext();) {
363 TransactionManagerMonitor listener = (TransactionManagerMonitor) iterator.next();
364 try {
365 listener.threadUnassociated(tx);
366 } catch (Exception e) {
367 log.warn("Error calling transaction association listener", e);
368 }
369 }
370 }
371 }