change version to 3.3.0-SNAPSHOT in cdm-3.3 branch
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / conversation / ConversationHolder.java
1 /**
2 * Copyright (C) 2009 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9
10 package eu.etaxonomy.cdm.api.conversation;
11
12 import javax.sql.DataSource;
13
14 import org.apache.log4j.Logger;
15 import org.hibernate.FlushMode;
16 import org.hibernate.LockMode;
17 import org.hibernate.Session;
18 import org.hibernate.SessionFactory;
19 import org.springframework.beans.factory.annotation.Autowired;
20 import org.springframework.orm.hibernate3.SessionFactoryUtils;
21 import org.springframework.orm.hibernate3.SessionHolder;
22 import org.springframework.transaction.PlatformTransactionManager;
23 import org.springframework.transaction.TransactionDefinition;
24 import org.springframework.transaction.TransactionStatus;
25 import org.springframework.transaction.support.TransactionSynchronizationManager;
26
27 import eu.etaxonomy.cdm.persistence.hibernate.CdmPostDataChangeObservableListener;
28
29 /**
30 * This is an implementation of the session-per-conversation pattern for usage
31 * in a Spring context.
32 *
33 * @see http://www.hibernate.org/42.html
34 *
35 * @author n.hoffmann
36 * @created 12.03.2009
37 * @version 1.0
38 */
39 public class ConversationHolder{
40
41 private static final Logger logger = Logger.getLogger(ConversationHolder.class);
42
43 @Autowired
44 private SessionFactory sessionFactory;
45
46 @Autowired
47 private DataSource dataSource;
48
49 @Autowired
50 private PlatformTransactionManager transactionManager;
51
52
53
54 /**
55 * The persistence context for this conversation
56 */
57 private Session longSession = null;
58
59 /**
60 * Spring communicates with hibernate sessions via a SessionHolder object
61 */
62 private SessionHolder sessionHolder = null;
63
64 /**
65 * @see TransactionDefinition
66 */
67 private TransactionDefinition definition;
68
69 /**
70 * This conversations transaction
71 */
72 private TransactionStatus transactionStatus;
73
74 private boolean closed = false;
75
76 /**
77 * Simple constructor used by Spring only
78 */
79 private ConversationHolder(){
80 closed = false;
81 }
82
83 public ConversationHolder(DataSource dataSource, SessionFactory sessionFactory,
84 PlatformTransactionManager transactionManager) {
85 this();
86 this.dataSource = dataSource;
87 this.sessionFactory = sessionFactory;
88 this.transactionManager = transactionManager;
89
90 bind();
91
92 if(TransactionSynchronizationManager.hasResource(getDataSource())){
93 TransactionSynchronizationManager.unbindResource(getDataSource());
94 }
95 }
96
97 /**
98 * This method has to be called when starting a new unit-of-work. All required resources are
99 * bound so that SessionFactory.getCurrentSession() returns the right session for this conversation
100 */
101 public void bind() {
102
103 logger.info("Binding resources for ConversationHolder");
104
105 if(TransactionSynchronizationManager.isSynchronizationActive()){
106 TransactionSynchronizationManager.clearSynchronization();
107 }
108
109 try{
110
111 logger.info("Starting new Synchronization in TransactionSynchronizationManager");
112 TransactionSynchronizationManager.initSynchronization();
113
114 if(TransactionSynchronizationManager.hasResource(getSessionFactory())){
115 TransactionSynchronizationManager.unbindResource(getSessionFactory());
116 }
117
118 logger.info("Binding Session to TransactionSynchronizationManager: Session: " + getSessionHolder());
119 TransactionSynchronizationManager.bindResource(getSessionFactory(), getSessionHolder());
120
121 }catch(Exception e){
122 logger.error("Error binding resources for session", e);
123 }
124
125 }
126
127 public SessionHolder getSessionHolder(){
128 if(this.sessionHolder == null){
129 logger.info("Creating new SessionHolder");
130 this.sessionHolder = new SessionHolder(getSession());
131 }
132 return this.sessionHolder;
133 }
134
135 /**
136 * @return
137 */
138 private DataSource getDataSource() {
139 return this.dataSource;
140 }
141
142 /**
143 * @return true if this longSession is bound to the session factory.
144 */
145 public boolean isBound(){
146 //return sessionHolder != null && longSession != null && longSession.isConnected();
147 return longSession != null && getSessionFactory().getCurrentSession() == longSession;
148 }
149
150 /**
151 * Creates an instance of TransactionStatus and binds it to this conversation manager.
152 * At the moment we allow only on transaction per conversation holder.
153 *
154 * @return the transaction status bound to this conversation holder
155 */
156 public TransactionStatus startTransaction(){
157 if (isTransactionActive()){
158 logger.warn("We allow only one transaction at the moment but startTransaction " +
159 "was called a second time.\nReturning the transaction already associated with this " +
160 "ConversationManager");
161 }else{
162 transactionStatus = transactionManager.getTransaction(definition);
163
164 logger.info("Transaction started: " + transactionStatus);
165 }
166 return transactionStatus;
167 }
168
169 /**
170 * @return if there is a running transaction
171 */
172 public boolean isTransactionActive(){
173 return transactionStatus != null;
174 }
175
176 /* (non-Javadoc)
177 * @see org.hibernate.Session#evict(java.lang.Object object)
178 */
179 public void evict(Object object){
180 getSession().evict(object);
181 }
182
183 /* (non-Javadoc)
184 * @see org.hibernate.Session#refresh(java.lang.Object object)
185 */
186 public void refresh(Object object){
187 getSession().refresh(object);
188 }
189
190 /* (non-Javadoc)
191 * @see org.hibernate.Session#clear()
192 */
193 public void clear(){
194 getSession().clear();
195 }
196
197 /**
198 * Commit the running transaction.
199 */
200 public void commit(){
201 commit(true);
202 }
203
204 /**
205 * Commit the running transaction but optionally start a
206 * new one right away.
207 *
208 * @param restartTransaction whether to start a new transaction
209 */
210 public TransactionStatus commit(boolean restartTransaction){
211 if(isTransactionActive()){
212
213 if(getSessionHolder().isRollbackOnly()){
214 logger.error("Commiting this session will not work. It has been marked as rollback only.");
215 }
216
217 // commit the changes
218 transactionManager.commit(transactionStatus);
219
220 // propagate transaction end
221 CdmPostDataChangeObservableListener.getDefault().delayedNotify();
222
223 // Reset the transactionStatus.
224 transactionStatus = null;
225
226 // Committing a transaction frees all resources.
227 // Since we are in a conversation we directly rebind those resources and start a new transaction
228 bind();
229 if(restartTransaction){
230 return startTransaction();
231 }
232 }else{
233 logger.warn("No active transaction but commit was called");
234 }
235 return null;
236 }
237
238 /**
239 * @return the session associated with this conversation manager
240 */
241 private Session getSession() {
242 if(longSession == null){
243 logger.info("Creating Session: [" + longSession + "]");
244 longSession = SessionFactoryUtils.getNewSession(getSessionFactory());
245 longSession.setFlushMode(FlushMode.COMMIT);
246 }
247
248 return longSession;
249 }
250
251 /**
252 * @return the session factory that is bound to this conversation manager
253 */
254 private SessionFactory getSessionFactory() {
255 return sessionFactory;
256 }
257
258 public void delete(Object object){
259 this.getSession().delete(object);
260 }
261
262 /**
263 * Facades Session.lock()
264 */
265 public void lock(Object persistentObject, LockMode lockMode) {
266 getSession().lock(persistentObject, lockMode);
267 }
268
269 public void lock(String entityName, Object persistentObject, LockMode lockMode){
270 getSession().lock(entityName, persistentObject, lockMode);
271 }
272
273 /**
274 * @return the definition
275 */
276 public TransactionDefinition getDefinition() {
277 return definition;
278 }
279
280 /**
281 * @param definition the definition to set
282 */
283 public void setDefinition(TransactionDefinition definition) {
284 this.definition = definition;
285 }
286
287 /**
288 * Register to get updated after any interaction with the datastore
289 */
290 public void registerForDataStoreChanges(IConversationEnabled observer) {
291 CdmPostDataChangeObservableListener.getDefault().register(observer);
292 }
293
294 /**
295 * Register to get updated after any interaction with the datastore
296 */
297 public void unregisterForDataStoreChanges(IConversationEnabled observer) {
298 CdmPostDataChangeObservableListener.getDefault().unregister(observer);
299 }
300
301 /**
302 * Free resources bound to this conversationHolder
303 */
304 public void close(){
305 if(getSession().isOpen())
306 getSession().close();
307 closed = true;
308 }
309
310 public boolean isClosed(){
311 return closed;
312 }
313 }