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.context;
26
27 import org.hibernate.HibernateException;
28 import org.hibernate.ConnectionReleaseMode;
29 import org.hibernate.classic.Session;
30 import org.hibernate.engine.SessionFactoryImplementor;
31 import org.hibernate.util.JTAHelper;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import javax.transaction.Transaction;
36 import javax.transaction.TransactionManager;
37 import javax.transaction.Synchronization;
38 import java.util.Map;
39 import java.util.Hashtable;
40
41 /**
42 * An implementation of {@link CurrentSessionContext} which scopes the notion
43 * of a current session to a JTA transaction. Because JTA gives us a nice
44 * tie-in to clean up after ourselves, this implementation will generate
45 * Sessions as needed provided a JTA transaction is in effect. If a session
46 * is not already associated with the current JTA transaction at the time
47 * {@link #currentSession()} is called, a new session will be opened and it
48 * will be associated with that JTA transaction.
49 * <p/>
50 * Note that the sessions returned from this method are automatically configured with
51 * both the {@link org.hibernate.cfg.Environment#FLUSH_BEFORE_COMPLETION auto-flush} and
52 * {@link org.hibernate.cfg.Environment#AUTO_CLOSE_SESSION auto-close} attributes set to
53 * true, meaning that the Session will be automatically flushed and closed
54 * as part of the lifecycle for the JTA transaction to which it is associated.
55 * Additionally, it will also be configured to aggressively release JDBC
56 * connections after each statement is executed. These settings are governed
57 * by the {@link #isAutoFlushEnabled()}, {@link #isAutoCloseEnabled()}, and
58 * {@link #getConnectionReleaseMode()} methods; these are provided (along with
59 * the {@link #buildOrObtainSession()} method) for easier subclassing for custom
60 * JTA-based session tracking logic (like maybe long-session semantics).
61 *
62 * @author Steve Ebersole
63 */
64 public class JTASessionContext implements CurrentSessionContext {
65
66 private static final Logger log = LoggerFactory.getLogger( JTASessionContext.class );
67
68 protected final SessionFactoryImplementor factory;
69 private transient Map currentSessionMap = new Hashtable();
70
71 public JTASessionContext(SessionFactoryImplementor factory) {
72 this.factory = factory;
73 }
74
75 /**
76 * {@inheritDoc}
77 */
78 public Session currentSession() throws HibernateException {
79 TransactionManager transactionManager = factory.getTransactionManager();
80 if ( transactionManager == null ) {
81 throw new HibernateException( "No TransactionManagerLookup specified" );
82 }
83
84 Transaction txn;
85 try {
86 txn = transactionManager.getTransaction();
87 if ( txn == null ) {
88 throw new HibernateException( "Unable to locate current JTA transaction" );
89 }
90 if ( !JTAHelper.isInProgress( txn.getStatus() ) ) {
91 // We could register the session against the transaction even though it is
92 // not started, but we'd have no guarentee of ever getting the map
93 // entries cleaned up (aside from spawning threads).
94 throw new HibernateException( "Current transaction is not in progress" );
95 }
96 }
97 catch ( HibernateException e ) {
98 throw e;
99 }
100 catch ( Throwable t ) {
101 throw new HibernateException( "Problem locating/validating JTA transaction", t );
102 }
103
104 final Object txnIdentifier = factory.getSettings().getTransactionManagerLookup() == null
105 ? txn
106 : factory.getSettings().getTransactionManagerLookup().getTransactionIdentifier( txn );
107
108 Session currentSession = ( Session ) currentSessionMap.get( txnIdentifier );
109
110 if ( currentSession == null ) {
111 currentSession = buildOrObtainSession();
112
113 try {
114 txn.registerSynchronization( buildCleanupSynch( txnIdentifier ) );
115 }
116 catch ( Throwable t ) {
117 try {
118 currentSession.close();
119 }
120 catch ( Throwable ignore ) {
121 log.debug( "Unable to release generated current-session on failed synch registration", ignore );
122 }
123 throw new HibernateException( "Unable to register cleanup Synchronization with TransactionManager" );
124 }
125
126 currentSessionMap.put( txnIdentifier, currentSession );
127 }
128
129 return currentSession;
130 }
131
132 /**
133 * Builds a {@link CleanupSynch} capable of cleaning up the the current session map as an after transaction
134 * callback.
135 *
136 * @param transactionIdentifier The transaction identifier under which the current session is registered.
137 * @return The cleanup synch.
138 */
139 private CleanupSynch buildCleanupSynch(Object transactionIdentifier) {
140 return new CleanupSynch( transactionIdentifier, this );
141 }
142
143 /**
144 * Strictly provided for subclassing purposes; specifically to allow long-session
145 * support.
146 * <p/>
147 * This implementation always just opens a new session.
148 *
149 * @return the built or (re)obtained session.
150 */
151 protected Session buildOrObtainSession() {
152 return factory.openSession(
153 null,
154 isAutoFlushEnabled(),
155 isAutoCloseEnabled(),
156 getConnectionReleaseMode()
157 );
158 }
159
160 /**
161 * Mainly for subclass usage. This impl always returns true.
162 *
163 * @return Whether or not the the session should be closed by transaction completion.
164 */
165 protected boolean isAutoCloseEnabled() {
166 return true;
167 }
168
169 /**
170 * Mainly for subclass usage. This impl always returns true.
171 *
172 * @return Whether or not the the session should be flushed prior transaction completion.
173 */
174 protected boolean isAutoFlushEnabled() {
175 return true;
176 }
177
178 /**
179 * Mainly for subclass usage. This impl always returns after_statement.
180 *
181 * @return The connection release mode for any built sessions.
182 */
183 protected ConnectionReleaseMode getConnectionReleaseMode() {
184 return ConnectionReleaseMode.AFTER_STATEMENT;
185 }
186
187 /**
188 * JTA transaction synch used for cleanup of the internal session map.
189 */
190 protected static class CleanupSynch implements Synchronization {
191 private Object transactionIdentifier;
192 private JTASessionContext context;
193
194 public CleanupSynch(Object transactionIdentifier, JTASessionContext context) {
195 this.transactionIdentifier = transactionIdentifier;
196 this.context = context;
197 }
198
199 /**
200 * {@inheritDoc}
201 */
202 public void beforeCompletion() {
203 }
204
205 /**
206 * {@inheritDoc}
207 */
208 public void afterCompletion(int i) {
209 context.currentSessionMap.remove( transactionIdentifier );
210 }
211 }
212 }