001: // $Id: JDBCContext.java 10477 2006-09-08 21:01:51Z epbernard $
002: package org.hibernate.jdbc;
003:
004: import java.io.Serializable;
005: import java.io.ObjectOutputStream;
006: import java.io.IOException;
007: import java.io.ObjectInputStream;
008: import java.sql.Connection;
009: import java.sql.SQLException;
010:
011: import javax.transaction.TransactionManager;
012:
013: import org.apache.commons.logging.Log;
014: import org.apache.commons.logging.LogFactory;
015: import org.hibernate.ConnectionReleaseMode;
016: import org.hibernate.HibernateException;
017: import org.hibernate.Interceptor;
018: import org.hibernate.SessionException;
019: import org.hibernate.Transaction;
020: import org.hibernate.TransactionException;
021: import org.hibernate.util.JTAHelper;
022: import org.hibernate.engine.SessionFactoryImplementor;
023: import org.hibernate.exception.JDBCExceptionHelper;
024: import org.hibernate.transaction.CacheSynchronization;
025: import org.hibernate.transaction.TransactionFactory;
026:
027: /**
028: * Acts as the mediary between "entity-mode related" sessions in terms of
029: * their interaction with the JDBC data store.
030: *
031: * @author Steve Ebersole
032: */
033: public class JDBCContext implements Serializable,
034: ConnectionManager.Callback {
035:
036: // TODO : make this the factory for "entity mode related" sessions;
037: // also means making this the target of transaction-synch and the
038: // thing that knows how to cascade things between related sessions
039: //
040: // At that point, perhaps this thing is a "SessionContext", and
041: // ConnectionManager is a "JDBCContext"? A "SessionContext" should
042: // live in the impl package...
043:
044: private static final Log log = LogFactory.getLog(JDBCContext.class);
045:
046: public static interface Context extends TransactionFactory.Context {
047: /**
048: * We cannot rely upon this method being called! It is only
049: * called if we are using Hibernate Transaction API.
050: */
051: public void afterTransactionBegin(Transaction tx);
052:
053: public void beforeTransactionCompletion(Transaction tx);
054:
055: public void afterTransactionCompletion(boolean success,
056: Transaction tx);
057:
058: public ConnectionReleaseMode getConnectionReleaseMode();
059:
060: public boolean isAutoCloseSessionEnabled();
061: }
062:
063: private Context owner;
064: private ConnectionManager connectionManager;
065: private transient boolean isTransactionCallbackRegistered;
066: private transient Transaction hibernateTransaction;
067:
068: public JDBCContext(Context owner, Connection connection,
069: Interceptor interceptor) {
070: this .owner = owner;
071: this .connectionManager = new ConnectionManager(owner
072: .getFactory(), this , owner.getConnectionReleaseMode(),
073: connection, interceptor);
074:
075: final boolean registerSynchronization = owner
076: .isAutoCloseSessionEnabled()
077: || owner.isFlushBeforeCompletionEnabled()
078: || owner.getConnectionReleaseMode() == ConnectionReleaseMode.AFTER_TRANSACTION;
079: if (registerSynchronization) {
080: registerSynchronizationIfPossible();
081: }
082: }
083:
084: /**
085: * Private constructor used exclusively for custom serialization...
086: *
087: */
088: private JDBCContext() {
089: }
090:
091: // ConnectionManager.Callback implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
092:
093: public void connectionOpened() {
094: if (owner.getFactory().getStatistics().isStatisticsEnabled()) {
095: owner.getFactory().getStatisticsImplementor().connect();
096: }
097: }
098:
099: public void connectionCleanedUp() {
100: if (!isTransactionCallbackRegistered) {
101: afterTransactionCompletion(false, null);
102: // Note : success = false, because we don't know the outcome of the transaction
103: }
104: }
105:
106: public SessionFactoryImplementor getFactory() {
107: return owner.getFactory();
108: }
109:
110: public ConnectionManager getConnectionManager() {
111: return connectionManager;
112: }
113:
114: public Connection borrowConnection() {
115: return connectionManager.borrowConnection();
116: }
117:
118: public Connection connection() throws HibernateException {
119: if (owner.isClosed()) {
120: throw new SessionException("Session is closed");
121: }
122:
123: return connectionManager.getConnection();
124: }
125:
126: public boolean registerCallbackIfNecessary() {
127: if (isTransactionCallbackRegistered) {
128: return false;
129: } else {
130: isTransactionCallbackRegistered = true;
131: return true;
132: }
133:
134: }
135:
136: public boolean registerSynchronizationIfPossible() {
137: if (isTransactionCallbackRegistered) {
138: // we already have a callback registered; either a local
139: // (org.hibernate.Transaction) transaction has accepted
140: // callback responsibilities, or we have previously
141: // registered a transaction synch.
142: return true;
143: }
144: boolean localCallbacksOnly = owner.getFactory().getSettings()
145: .getTransactionFactory()
146: .areCallbacksLocalToHibernateTransactions();
147: if (localCallbacksOnly) {
148: // the configured transaction-factory says it only supports
149: // local callback mode, so no sense attempting to register a
150: // JTA Synchronization
151: return false;
152: }
153: TransactionManager tm = owner.getFactory()
154: .getTransactionManager();
155: if (tm == null) {
156: // if there is no TM configured, we will not be able to access
157: // the javax.transaction.Transaction object in order to
158: // register a synch anyway.
159: return false;
160: } else {
161: try {
162: if (!isTransactionInProgress()) {
163: log
164: .trace("TransactionFactory reported no active transaction; Synchronization not registered");
165: return false;
166: } else {
167: javax.transaction.Transaction tx = tm
168: .getTransaction();
169: if (JTAHelper.isMarkedForRollback(tx)) {
170: log
171: .debug("Transaction is marked for rollback; skipping Synchronization registration");
172: return false;
173: } else {
174: tx
175: .registerSynchronization(new CacheSynchronization(
176: owner, this , tx, null));
177: isTransactionCallbackRegistered = true;
178: log
179: .debug("successfully registered Synchronization");
180: return true;
181: }
182: }
183: } catch (HibernateException e) {
184: throw e;
185: } catch (Exception e) {
186: throw new TransactionException(
187: "could not register synchronization with JTA TransactionManager",
188: e);
189: }
190: }
191: }
192:
193: public boolean isTransactionInProgress() {
194: return owner.getFactory().getSettings().getTransactionFactory()
195: .isTransactionInProgress(this , owner,
196: hibernateTransaction);
197: }
198:
199: public Transaction getTransaction() throws HibernateException {
200: if (hibernateTransaction == null) {
201: hibernateTransaction = owner.getFactory().getSettings()
202: .getTransactionFactory().createTransaction(this ,
203: owner);
204: }
205: return hibernateTransaction;
206: }
207:
208: public void beforeTransactionCompletion(Transaction tx) {
209: log.trace("before transaction completion");
210: owner.beforeTransactionCompletion(tx);
211: }
212:
213: /**
214: * We cannot rely upon this method being called! It is only
215: * called if we are using Hibernate Transaction API.
216: */
217: public void afterTransactionBegin(Transaction tx) {
218: log.trace("after transaction begin");
219: owner.afterTransactionBegin(tx);
220: }
221:
222: public void afterTransactionCompletion(boolean success,
223: Transaction tx) {
224: log.trace("after transaction completion");
225:
226: if (getFactory().getStatistics().isStatisticsEnabled()) {
227: getFactory().getStatisticsImplementor().endTransaction(
228: success);
229: }
230:
231: connectionManager.afterTransaction();
232:
233: isTransactionCallbackRegistered = false;
234: hibernateTransaction = null;
235: owner.afterTransactionCompletion(success, tx);
236: }
237:
238: /**
239: * Called after executing a query outside the scope of
240: * a Hibernate or JTA transaction
241: */
242: public void afterNontransactionalQuery(boolean success) {
243: log.trace("after autocommit");
244: try {
245: // check to see if the connection is in auto-commit
246: // mode (no connection means aggressive connection
247: // release outside a JTA transaction context, so MUST
248: // be autocommit mode)
249: boolean isAutocommit = connectionManager.isAutoCommit();
250:
251: connectionManager.afterTransaction();
252:
253: if (isAutocommit) {
254: owner.afterTransactionCompletion(success, null);
255: }
256: } catch (SQLException sqle) {
257: throw JDBCExceptionHelper.convert(owner.getFactory()
258: .getSQLExceptionConverter(), sqle,
259: "could not inspect JDBC autocommit mode");
260: }
261: }
262:
263: // serialization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
264:
265: private void writeObject(ObjectOutputStream oos) throws IOException {
266: // isTransactionCallbackRegistered denotes whether any Hibernate
267: // Transaction has registered as a callback against this
268: // JDBCContext; only one such callback is allowed. Directly
269: // serializing this value causes problems with JDBCTransaction,
270: // or really any Transaction impl where the callback is local
271: // to the Transaction instance itself, since that Transaction
272: // is not serialized along with the JDBCContext. Thus we
273: // handle that fact here explicitly...
274: oos.defaultWriteObject();
275: boolean deserHasCallbackRegistered = isTransactionCallbackRegistered
276: && !owner.getFactory().getSettings()
277: .getTransactionFactory()
278: .areCallbacksLocalToHibernateTransactions();
279: oos.writeBoolean(deserHasCallbackRegistered);
280: }
281:
282: private void readObject(ObjectInputStream ois) throws IOException,
283: ClassNotFoundException {
284: ois.defaultReadObject();
285: isTransactionCallbackRegistered = ois.readBoolean();
286: }
287:
288: /**
289: * Custom serialization routine used during serialization of a
290: * Session/PersistenceContext for increased performance.
291: *
292: * @param oos The stream to which we should write the serial data.
293: * @throws IOException
294: */
295: public void serialize(ObjectOutputStream oos) throws IOException {
296: connectionManager.serialize(oos);
297: }
298:
299: /**
300: * Custom deserialization routine used during deserialization of a
301: * Session/PersistenceContext for increased performance.
302: *
303: * @param ois The stream from which to read the entry.
304: * @throws IOException
305: */
306: public static JDBCContext deserialize(ObjectInputStream ois,
307: Context context, Interceptor interceptor)
308: throws IOException {
309: JDBCContext jdbcContext = new JDBCContext();
310: jdbcContext.owner = context;
311: jdbcContext.connectionManager = ConnectionManager.deserialize(
312: ois, context.getFactory(), interceptor, context
313: .getConnectionReleaseMode(), jdbcContext);
314: return jdbcContext;
315: }
316: }
|