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.jdbc;
26
27 import java.io.Serializable;
28 import java.io.ObjectOutputStream;
29 import java.io.IOException;
30 import java.io.ObjectInputStream;
31 import java.sql.Connection;
32 import java.sql.SQLException;
33
34 import javax.transaction.TransactionManager;
35
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38 import org.hibernate.ConnectionReleaseMode;
39 import org.hibernate.HibernateException;
40 import org.hibernate.Interceptor;
41 import org.hibernate.SessionException;
42 import org.hibernate.Transaction;
43 import org.hibernate.TransactionException;
44 import org.hibernate.util.JTAHelper;
45 import org.hibernate.engine.SessionFactoryImplementor;
46 import org.hibernate.exception.JDBCExceptionHelper;
47 import org.hibernate.transaction.CacheSynchronization;
48 import org.hibernate.transaction.TransactionFactory;
49
50 /**
51 * Acts as the mediary between "entity-mode related" sessions in terms of
52 * their interaction with the JDBC data store.
53 *
54 * @author Steve Ebersole
55 */
56 public class JDBCContext implements Serializable, ConnectionManager.Callback {
57
58 // TODO : make this the factory for "entity mode related" sessions;
59 // also means making this the target of transaction-synch and the
60 // thing that knows how to cascade things between related sessions
61 //
62 // At that point, perhaps this thing is a "SessionContext", and
63 // ConnectionManager is a "JDBCContext"? A "SessionContext" should
64 // live in the impl package...
65
66 private static final Logger log = LoggerFactory.getLogger( JDBCContext.class );
67
68 public static interface Context extends TransactionFactory.Context {
69 /**
70 * We cannot rely upon this method being called! It is only
71 * called if we are using Hibernate Transaction API.
72 */
73 public void afterTransactionBegin(Transaction tx);
74 public void beforeTransactionCompletion(Transaction tx);
75 public void afterTransactionCompletion(boolean success, Transaction tx);
76 public ConnectionReleaseMode getConnectionReleaseMode();
77 public boolean isAutoCloseSessionEnabled();
78 }
79
80 private Context owner;
81 private ConnectionManager connectionManager;
82 private transient boolean isTransactionCallbackRegistered;
83 private transient Transaction hibernateTransaction;
84
85 public JDBCContext(Context owner, Connection connection, Interceptor interceptor) {
86 this.owner = owner;
87 this.connectionManager = new ConnectionManager(
88 owner.getFactory(),
89 this,
90 owner.getConnectionReleaseMode(),
91 connection,
92 interceptor
93 );
94
95 final boolean registerSynchronization = owner.isAutoCloseSessionEnabled()
96 || owner.isFlushBeforeCompletionEnabled()
97 || owner.getConnectionReleaseMode() == ConnectionReleaseMode.AFTER_TRANSACTION;
98 if ( registerSynchronization ) {
99 registerSynchronizationIfPossible();
100 }
101 }
102
103 /**
104 * Private constructor used exclusively for custom serialization...
105 *
106 */
107 private JDBCContext() {
108 }
109
110 // ConnectionManager.Callback implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
111
112 public void connectionOpened() {
113 if ( owner.getFactory().getStatistics().isStatisticsEnabled() ) {
114 owner.getFactory().getStatisticsImplementor().connect();
115 }
116 }
117
118 public void connectionCleanedUp() {
119 if ( !isTransactionCallbackRegistered ) {
120 afterTransactionCompletion( false, null );
121 // Note : success = false, because we don't know the outcome of the transaction
122 }
123 }
124
125 public SessionFactoryImplementor getFactory() {
126 return owner.getFactory();
127 }
128
129 public ConnectionManager getConnectionManager() {
130 return connectionManager;
131 }
132
133 public Connection borrowConnection() {
134 return connectionManager.borrowConnection();
135 }
136
137 public Connection connection() throws HibernateException {
138 if ( owner.isClosed() ) {
139 throw new SessionException( "Session is closed" );
140 }
141
142 return connectionManager.getConnection();
143 }
144
145 public boolean registerCallbackIfNecessary() {
146 if ( isTransactionCallbackRegistered ) {
147 return false;
148 }
149 else {
150 isTransactionCallbackRegistered = true;
151 return true;
152 }
153
154 }
155
156 public boolean registerSynchronizationIfPossible() {
157 if ( isTransactionCallbackRegistered ) {
158 // we already have a callback registered; either a local
159 // (org.hibernate.Transaction) transaction has accepted
160 // callback responsibilities, or we have previously
161 // registered a transaction synch.
162 return true;
163 }
164 boolean localCallbacksOnly = owner.getFactory().getSettings()
165 .getTransactionFactory()
166 .areCallbacksLocalToHibernateTransactions();
167 if ( localCallbacksOnly ) {
168 // the configured transaction-factory says it only supports
169 // local callback mode, so no sense attempting to register a
170 // JTA Synchronization
171 return false;
172 }
173 TransactionManager tm = owner.getFactory().getTransactionManager();
174 if ( tm == null ) {
175 // if there is no TM configured, we will not be able to access
176 // the javax.transaction.Transaction object in order to
177 // register a synch anyway.
178 return false;
179 }
180 else {
181 try {
182 if ( !isTransactionInProgress() ) {
183 log.trace( "TransactionFactory reported no active transaction; Synchronization not registered" );
184 return false;
185 }
186 else {
187 javax.transaction.Transaction tx = tm.getTransaction();
188 if ( JTAHelper.isMarkedForRollback( tx ) ) {
189 // transactions marked for rollback-only cause some TM impls to throw exceptions
190 log.debug( "Transaction is marked for rollback; skipping Synchronization registration" );
191 return false;
192 }
193 else {
194 if ( hibernateTransaction == null ) {
195 hibernateTransaction = owner.getFactory().getSettings().getTransactionFactory().createTransaction( this, owner );
196 }
197 tx.registerSynchronization( new CacheSynchronization(owner, this, tx, hibernateTransaction) );
198 isTransactionCallbackRegistered = true;
199 log.debug("successfully registered Synchronization");
200 return true;
201 }
202 }
203 }
204 catch( HibernateException e ) {
205 throw e;
206 }
207 catch (Exception e) {
208 throw new TransactionException( "could not register synchronization with JTA TransactionManager", e );
209 }
210 }
211 }
212
213 public boolean isTransactionInProgress() {
214 return owner.getFactory().getSettings().getTransactionFactory()
215 .isTransactionInProgress( this, owner, hibernateTransaction );
216 }
217
218 public Transaction getTransaction() throws HibernateException {
219 if (hibernateTransaction==null) {
220 hibernateTransaction = owner.getFactory().getSettings()
221 .getTransactionFactory()
222 .createTransaction( this, owner );
223 }
224 return hibernateTransaction;
225 }
226
227 public void beforeTransactionCompletion(Transaction tx) {
228 log.trace( "before transaction completion" );
229 owner.beforeTransactionCompletion(tx);
230 }
231
232 /**
233 * We cannot rely upon this method being called! It is only
234 * called if we are using Hibernate Transaction API.
235 */
236 public void afterTransactionBegin(Transaction tx) {
237 log.trace( "after transaction begin" );
238 owner.afterTransactionBegin(tx);
239 }
240
241 public void afterTransactionCompletion(boolean success, Transaction tx) {
242 log.trace( "after transaction completion" );
243
244 if ( getFactory().getStatistics().isStatisticsEnabled() ) {
245 getFactory().getStatisticsImplementor().endTransaction(success);
246 }
247
248 connectionManager.afterTransaction();
249
250 isTransactionCallbackRegistered = false;
251 hibernateTransaction = null;
252 owner.afterTransactionCompletion(success, tx);
253 }
254
255 /**
256 * Called after executing a query outside the scope of
257 * a Hibernate or JTA transaction
258 */
259 public void afterNontransactionalQuery(boolean success) {
260 log.trace( "after autocommit" );
261 try {
262 // check to see if the connection is in auto-commit
263 // mode (no connection means aggressive connection
264 // release outside a JTA transaction context, so MUST
265 // be autocommit mode)
266 boolean isAutocommit = connectionManager.isAutoCommit();
267
268 connectionManager.afterTransaction();
269
270 if ( isAutocommit ) {
271 owner.afterTransactionCompletion(success, null);
272 }
273 }
274 catch (SQLException sqle) {
275 throw JDBCExceptionHelper.convert(
276 owner.getFactory().getSQLExceptionConverter(),
277 sqle,
278 "could not inspect JDBC autocommit mode"
279 );
280 }
281 }
282
283
284 // serialization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
285
286 private void writeObject(ObjectOutputStream oos) throws IOException {
287 // isTransactionCallbackRegistered denotes whether any Hibernate
288 // Transaction has registered as a callback against this
289 // JDBCContext; only one such callback is allowed. Directly
290 // serializing this value causes problems with JDBCTransaction,
291 // or really any Transaction impl where the callback is local
292 // to the Transaction instance itself, since that Transaction
293 // is not serialized along with the JDBCContext. Thus we
294 // handle that fact here explicitly...
295 oos.defaultWriteObject();
296 boolean deserHasCallbackRegistered = isTransactionCallbackRegistered
297 && ! owner.getFactory().getSettings().getTransactionFactory()
298 .areCallbacksLocalToHibernateTransactions();
299 oos.writeBoolean( deserHasCallbackRegistered );
300 }
301
302 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
303 ois.defaultReadObject();
304 isTransactionCallbackRegistered = ois.readBoolean();
305 }
306
307 /**
308 * Custom serialization routine used during serialization of a
309 * Session/PersistenceContext for increased performance.
310 *
311 * @param oos The stream to which we should write the serial data.
312 * @throws IOException
313 */
314 public void serialize(ObjectOutputStream oos) throws IOException {
315 connectionManager.serialize( oos );
316 }
317
318 /**
319 * Custom deserialization routine used during deserialization of a
320 * Session/PersistenceContext for increased performance.
321 *
322 * @param ois The stream from which to read the entry.
323 * @throws IOException
324 */
325 public static JDBCContext deserialize(
326 ObjectInputStream ois,
327 Context context,
328 Interceptor interceptor) throws IOException {
329 JDBCContext jdbcContext = new JDBCContext();
330 jdbcContext.owner = context;
331 jdbcContext.connectionManager = ConnectionManager.deserialize(
332 ois,
333 context.getFactory(),
334 interceptor,
335 context.getConnectionReleaseMode(),
336 jdbcContext
337 );
338 return jdbcContext;
339 }
340 }