1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
4 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5 * indicated by the @author tags or express copyright attribution
6 * statements applied by the authors. All third-party contributions are
7 * distributed under license by Red Hat Middleware LLC.
8 *
9 * This copyrighted material is made available to anyone wishing to use, modify,
10 * copy, or redistribute it subject to the terms and conditions of the GNU
11 * Lesser General Public License, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this distribution; if not, write to:
20 * Free Software Foundation, Inc.
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301 USA
23 *
24 */
25 package org.hibernate.transaction;
26
27 import javax.transaction.Status;
28 import javax.transaction.Synchronization;
29 import javax.transaction.SystemException;
30 import javax.transaction.TransactionManager;
31 import javax.transaction.UserTransaction;
32
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 import org.hibernate.HibernateException;
37 import org.hibernate.Transaction;
38 import org.hibernate.TransactionException;
39 import org.hibernate.jdbc.JDBCContext;
40 import org.hibernate.util.JTAHelper;
41
42 /**
43 * {@link Transaction} implementation based on transaction management through
44 * a JTA {@link UserTransaction}. Similar to {@link CMTTransaction}, except
45 * here we are actually managing the transactions through the Hibernate
46 * transaction mechanism.
47 *
48 * @author Gavin King
49 * @author Steve Ebersole
50 * @author Les Hazlewood
51 */
52 public class JTATransaction implements Transaction {
53
54 private static final Logger log = LoggerFactory.getLogger( JTATransaction.class );
55
56 private final JDBCContext jdbcContext;
57 private final TransactionFactory.Context transactionContext;
58
59 private UserTransaction userTransaction;
60 private boolean newTransaction;
61 private boolean begun;
62 private boolean commitFailed;
63 private boolean commitSucceeded;
64 private boolean callback;
65
66 public JTATransaction(
67 UserTransaction userTransaction,
68 JDBCContext jdbcContext,
69 TransactionFactory.Context transactionContext) {
70 this.jdbcContext = jdbcContext;
71 this.transactionContext = transactionContext;
72 this.userTransaction = userTransaction;
73 }
74
75 /**
76 * {@inheritDoc}
77 */
78 public void begin() throws HibernateException {
79 if ( begun ) {
80 return;
81 }
82 if ( commitFailed ) {
83 throw new TransactionException( "cannot re-start transaction after failed commit" );
84 }
85
86 log.debug( "begin" );
87
88 try {
89 newTransaction = userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION;
90 if ( newTransaction ) {
91 userTransaction.begin();
92 log.debug( "Began a new JTA transaction" );
93 }
94 }
95 catch ( Exception e ) {
96 log.error( "JTA transaction begin failed", e );
97 throw new TransactionException( "JTA transaction begin failed", e );
98 }
99
100 /*if (newTransaction) {
101 // don't need a synchronization since we are committing
102 // or rolling back the transaction ourselves - assuming
103 // that we do no work in beforeTransactionCompletion()
104 synchronization = false;
105 }*/
106
107 boolean synchronization = jdbcContext.registerSynchronizationIfPossible();
108
109 if ( !newTransaction && !synchronization ) {
110 log.warn( "You should set hibernate.transaction.manager_lookup_class if cache is enabled" );
111 }
112
113 if ( !synchronization ) {
114 //if we could not register a synchronization,
115 //do the before/after completion callbacks
116 //ourself (but we need to let jdbcContext
117 //know that this is what we are going to
118 //do, so it doesn't keep trying to register
119 //synchronizations)
120 callback = jdbcContext.registerCallbackIfNecessary();
121 }
122
123 begun = true;
124 commitSucceeded = false;
125
126 jdbcContext.afterTransactionBegin( this );
127 }
128
129 /**
130 * {@inheritDoc}
131 */
132 public void commit() throws HibernateException {
133 if ( !begun ) {
134 throw new TransactionException( "Transaction not successfully started" );
135 }
136
137 log.debug( "commit" );
138
139 boolean flush = !transactionContext.isFlushModeNever()
140 && ( callback || !transactionContext.isFlushBeforeCompletionEnabled() );
141
142 if ( flush ) {
143 transactionContext.managedFlush(); //if an exception occurs during flush, user must call rollback()
144 }
145
146 if ( callback && newTransaction ) {
147 jdbcContext.beforeTransactionCompletion( this );
148 }
149
150 closeIfRequired();
151
152 if ( newTransaction ) {
153 try {
154 userTransaction.commit();
155 commitSucceeded = true;
156 log.debug( "Committed JTA UserTransaction" );
157 }
158 catch ( Exception e ) {
159 commitFailed = true; // so the transaction is already rolled back, by JTA spec
160 log.error( "JTA commit failed", e );
161 throw new TransactionException( "JTA commit failed: ", e );
162 }
163 finally {
164 afterCommitRollback();
165 }
166 }
167 else {
168 // this one only really needed for badly-behaved applications!
169 // (if the TransactionManager has a Sychronization registered,
170 // its a noop)
171 // (actually we do need it for downgrading locks)
172 afterCommitRollback();
173 }
174
175 }
176
177 /**
178 * {@inheritDoc}
179 */
180 public void rollback() throws HibernateException {
181 if ( !begun && !commitFailed ) {
182 throw new TransactionException( "Transaction not successfully started" );
183 }
184
185 log.debug( "rollback" );
186
187 try {
188 closeIfRequired();
189 }
190 catch ( Exception e ) {
191 // swallow it, and continue to roll back JTA transaction
192 log.error( "could not close session during rollback", e );
193 }
194
195 try {
196 if ( newTransaction ) {
197 if ( !commitFailed ) {
198 userTransaction.rollback();
199 log.debug( "Rolled back JTA UserTransaction" );
200 }
201 }
202 else {
203 userTransaction.setRollbackOnly();
204 log.debug( "set JTA UserTransaction to rollback only" );
205 }
206 }
207 catch ( Exception e ) {
208 log.error( "JTA rollback failed", e );
209 throw new TransactionException( "JTA rollback failed", e );
210 }
211 finally {
212 afterCommitRollback();
213 }
214 }
215
216 private static final int NULL = Integer.MIN_VALUE;
217
218 private void afterCommitRollback() throws TransactionException {
219
220 begun = false;
221 // this method is a noop if there is a Synchronization!
222 if ( callback ) {
223 if ( !newTransaction ) {
224 log.warn( "You should set hibernate.transaction.manager_lookup_class if cache is enabled" );
225 }
226 int status = NULL;
227 try {
228 status = userTransaction.getStatus();
229 }
230 catch ( Exception e ) {
231 log.error( "Could not determine transaction status after commit", e );
232 throw new TransactionException( "Could not determine transaction status after commit", e );
233 }
234 finally {
235 jdbcContext.afterTransactionCompletion( status == Status.STATUS_COMMITTED, this );
236 }
237 }
238 }
239
240 /**
241 * {@inheritDoc}
242 */
243 public boolean wasRolledBack() throws TransactionException {
244 final int status;
245 try {
246 status = userTransaction.getStatus();
247 }
248 catch ( SystemException se ) {
249 log.error( "Could not determine transaction status", se );
250 throw new TransactionException( "Could not determine transaction status", se );
251 }
252 if ( status == Status.STATUS_UNKNOWN ) {
253 throw new TransactionException( "Could not determine transaction status" );
254 }
255 else {
256 return JTAHelper.isRollback( status );
257 }
258 }
259
260 /**
261 * {@inheritDoc}
262 */
263 public boolean wasCommitted() throws TransactionException {
264 final int status;
265 try {
266 status = userTransaction.getStatus();
267 }
268 catch ( SystemException se ) {
269 log.error( "Could not determine transaction status", se );
270 throw new TransactionException( "Could not determine transaction status: ", se );
271 }
272 if ( status == Status.STATUS_UNKNOWN ) {
273 throw new TransactionException( "Could not determine transaction status" );
274 }
275 else {
276 return status == Status.STATUS_COMMITTED;
277 }
278 }
279
280 /**
281 * {@inheritDoc}
282 */
283 public boolean isActive() throws TransactionException {
284 if ( !begun || commitFailed || commitSucceeded ) {
285 return false;
286 }
287
288 final int status;
289 try {
290 status = userTransaction.getStatus();
291 }
292 catch ( SystemException se ) {
293 log.error( "Could not determine transaction status", se );
294 throw new TransactionException( "Could not determine transaction status: ", se );
295 }
296 if ( status == Status.STATUS_UNKNOWN ) {
297 throw new TransactionException( "Could not determine transaction status" );
298 }
299 else {
300 return status == Status.STATUS_ACTIVE;
301 }
302 }
303
304 /**
305 * {@inheritDoc}
306 */
307 public void registerSynchronization(Synchronization sync) throws HibernateException {
308 if ( getTransactionManager() == null ) {
309 throw new IllegalStateException( "JTA TransactionManager not available" );
310 }
311 else {
312 try {
313 getTransactionManager().getTransaction().registerSynchronization( sync );
314 }
315 catch ( Exception e ) {
316 throw new TransactionException( "could not register synchronization", e );
317 }
318 }
319 }
320
321 /**
322 * Getter for property 'transactionManager'.
323 *
324 * @return Value for property 'transactionManager'.
325 */
326 private TransactionManager getTransactionManager() {
327 return transactionContext.getFactory().getTransactionManager();
328 }
329
330 private void closeIfRequired() throws HibernateException {
331 boolean close = callback &&
332 transactionContext.shouldAutoClose() &&
333 !transactionContext.isClosed();
334 if ( close ) {
335 transactionContext.managedClose();
336 }
337 }
338
339 /**
340 * {@inheritDoc}
341 */
342 public void setTimeout(int seconds) {
343 try {
344 userTransaction.setTransactionTimeout( seconds );
345 }
346 catch ( SystemException se ) {
347 throw new TransactionException( "could not set transaction timeout", se );
348 }
349 }
350
351 /**
352 * Getter for property 'userTransaction'.
353 *
354 * @return Value for property 'userTransaction'.
355 */
356 protected UserTransaction getUserTransaction() {
357 return userTransaction;
358 }
359 }