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.engine.transaction;
26
27 import java.sql.Connection;
28 import java.sql.SQLException;
29 import javax.transaction.Transaction;
30 import javax.transaction.TransactionManager;
31
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34 import org.hibernate.HibernateException;
35 import org.hibernate.engine.SessionImplementor;
36 import org.hibernate.exception.JDBCExceptionHelper;
37
38 /**
39 * Class which provides the isolation semantics required by
40 * an {@link IsolatedWork}. Processing comes in two flavors:<ul>
41 * <li>{@link #doIsolatedWork} : makes sure the work to be done is
42 * performed in a seperate, distinct transaction</li>
43 * <li>{@link #doNonTransactedWork} : makes sure the work to be
44 * done is performed outside the scope of any transaction</li>
45 * </ul>
46 *
47 * @author Steve Ebersole
48 */
49 public class Isolater {
50
51 private static final Logger log = LoggerFactory.getLogger( Isolater.class );
52
53 /**
54 * Ensures that all processing actually performed by the given work will
55 * occur on a seperate transaction.
56 *
57 * @param work The work to be performed.
58 * @param session The session from which this request is originating.
59 * @throws HibernateException
60 */
61 public static void doIsolatedWork(IsolatedWork work, SessionImplementor session) throws HibernateException {
62 boolean isJta = session.getFactory().getTransactionManager() != null;
63 if ( isJta ) {
64 new JtaDelegate( session ).delegateWork( work, true );
65 }
66 else {
67 new JdbcDelegate( session ).delegateWork( work, true );
68 }
69 }
70
71 /**
72 * Ensures that all processing actually performed by the given work will
73 * occur outside of a transaction.
74 *
75 * @param work The work to be performed.
76 * @param session The session from which this request is originating.
77 * @throws HibernateException
78 */
79 public static void doNonTransactedWork(IsolatedWork work, SessionImplementor session) throws HibernateException {
80 boolean isJta = session.getFactory().getTransactionManager() != null;
81 if ( isJta ) {
82 new JtaDelegate( session ).delegateWork( work, false );
83 }
84 else {
85 new JdbcDelegate( session ).delegateWork( work, false );
86 }
87 }
88
89 // should be ok performance-wise to generate new delegate instances for each
90 // request since these are locally stack-scoped. Besides, it makes the code
91 // much easier to read than the old TransactionHelper stuff...
92
93 private static interface Delegate {
94 public void delegateWork(IsolatedWork work, boolean transacted) throws HibernateException;
95 }
96
97 /**
98 * An isolation delegate for JTA-based transactions. Essentially susepnds
99 * any current transaction, does the work in a new transaction, and then
100 * resumes the initial transaction (if there was one).
101 */
102 public static class JtaDelegate implements Delegate {
103 private final SessionImplementor session;
104
105 public JtaDelegate(SessionImplementor session) {
106 this.session = session;
107 }
108
109 public void delegateWork(IsolatedWork work, boolean transacted) throws HibernateException {
110 TransactionManager transactionManager = session.getFactory().getTransactionManager();
111 Transaction surroundingTransaction = null;
112 Connection connection = null;
113 boolean caughtException = false;
114
115 try {
116 // First we need to suspend any current JTA transaction and obtain
117 // a JDBC connection
118 surroundingTransaction = transactionManager.suspend();
119 if ( log.isDebugEnabled() ) {
120 log.debug( "surrounding JTA transaction suspended [" + surroundingTransaction + "]" );
121 }
122
123 if ( transacted ) {
124 transactionManager.begin();
125 }
126
127 connection = session.getBatcher().openConnection();
128
129 // perform the actual work
130 work.doWork( connection );
131
132 // if everything went ok, commit the transaction and close the obtained
133 // connection handle...
134 session.getBatcher().closeConnection( connection );
135
136 if ( transacted ) {
137 transactionManager.commit();
138 }
139 }
140 catch( Throwable t ) {
141 // at some point the processing went bad, so we need to:
142 // 1) make sure the connection handle gets released
143 // 2) try to cleanup the JTA context as much as possible
144 caughtException = true;
145 try {
146 if ( connection != null && !connection.isClosed() ) {
147 session.getBatcher().closeConnection( connection );
148 }
149 }
150 catch( Throwable ignore ) {
151 log.trace( "unable to release connection on exception [" + ignore + "]" );
152 }
153 if ( transacted ) {
154 try {
155 transactionManager.rollback();
156 }
157 catch( Throwable ignore ) {
158 log.trace( "unable to rollback new transaction on exception [" + ignore + "]" );
159 }
160 }
161 // finally handle the exception
162 if ( t instanceof HibernateException ) {
163 throw ( HibernateException ) t;
164 }
165 else {
166 throw new HibernateException( "error performing isolated work", t );
167 }
168 }
169 finally {
170 if ( surroundingTransaction != null ) {
171 try {
172 transactionManager.resume( surroundingTransaction );
173 if ( log.isDebugEnabled() ) {
174 log.debug( "surrounding JTA transaction resumed [" + surroundingTransaction + "]" );
175 }
176 }
177 catch( Throwable t ) {
178 if ( !caughtException ) {
179 throw new HibernateException( "unable to resume previously suspended transaction", t );
180 }
181 }
182 }
183 }
184 }
185 }
186
187 /**
188 * An isolation delegate for JDBC-based transactions. Basically just
189 * grabs a new connection and does the work on that.
190 */
191 public static class JdbcDelegate implements Delegate {
192 private final SessionImplementor session;
193
194 public JdbcDelegate(SessionImplementor session) {
195 this.session = session;
196 }
197
198 public void delegateWork(IsolatedWork work, boolean transacted) throws HibernateException {
199 Connection connection = null;
200 boolean wasAutoCommit = false;
201 try {
202 connection = session.getBatcher().openConnection();
203
204 if ( transacted ) {
205 if ( connection.getAutoCommit() ) {
206 wasAutoCommit = true;
207 connection.setAutoCommit( false );
208 }
209 }
210
211 work.doWork( connection );
212
213 if ( transacted ) {
214 connection.commit();
215 }
216 }
217 catch( Throwable t ) {
218 try {
219 if ( transacted && connection != null && !connection.isClosed() ) {
220 connection.rollback();
221 }
222 }
223 catch( Throwable ignore ) {
224 log.trace( "unable to release connection on exception [" + ignore + "]" );
225 }
226
227 if ( t instanceof HibernateException ) {
228 throw ( HibernateException ) t;
229 }
230 else if ( t instanceof SQLException ) {
231 throw JDBCExceptionHelper.convert(
232 session.getFactory().getSQLExceptionConverter(),
233 ( SQLException ) t,
234 "error performing isolated work"
235 );
236 }
237 else {
238 throw new HibernateException( "error performing isolated work", t );
239 }
240 }
241 finally {
242 if ( connection != null ) {
243 if ( transacted && wasAutoCommit ) {
244 try {
245 connection.setAutoCommit( true );
246 }
247 catch( Throwable ignore ) {
248 log.trace( "was unable to reset connection back to auto-commit" );
249 }
250 }
251 session.getBatcher().closeConnection( connection );
252 }
253 }
254 }
255 }
256 }