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
|
}
|