2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
9 package eu
.etaxonomy
.cdm
.service
;
11 import org
.apache
.log4j
.Logger
;
12 import org
.hibernate
.Session
;
13 import org
.hibernate
.engine
.spi
.SessionImplementor
;
14 import org
.springframework
.transaction
.TransactionStatus
;
15 import org
.springframework
.transaction
.support
.DefaultTransactionDefinition
;
17 import com
.vaadin
.ui
.Notification
;
18 import com
.vaadin
.ui
.UI
;
20 import eu
.etaxonomy
.cdm
.api
.application
.CdmRepository
;
21 import eu
.etaxonomy
.cdm
.api
.conversation
.ConversationHolder
;
22 import eu
.etaxonomy
.cdm
.api
.service
.DeleteResult
;
23 import eu
.etaxonomy
.cdm
.api
.service
.IService
;
24 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
25 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
26 import eu
.etaxonomy
.cdm
.vaadin
.event
.EntityChangeEvent
;
27 import eu
.etaxonomy
.cdm
.vaadin
.event
.EntityChangeEvent
.Type
;
30 * @author a.kohlbecker
33 * TODO better naming of this class, ServiceWrapper, ServiceOperator, ...?
36 public class CdmStore
<T
extends CdmBase
, S
extends IService
<T
>> {
38 private static final Logger logger
= Logger
.getLogger(CdmStore
.class);
40 private CdmRepository repo
;
44 TransactionStatus txNonConversational
= null;
46 ConversationHolder conversationHolder
= null;
49 * @return the conversationHolder
51 public ConversationHolder
getConversationHolder() {
52 return conversationHolder
;
55 protected DefaultTransactionDefinition txDefinition
= null;
61 * may be <code>null</code>, but delete operations will fail with
62 * a NullPointerException in this case.
64 public CdmStore(CdmRepository repo
, S service
) {
67 this.service
= service
;
72 * constructor which takes a ConversationHolder. The supplying class of the conversationHolder needs
73 * to care for <code>bind()</code>, <code>unbind()</code> and <code>close()</code> since the store is
74 * only responsible for starting and committing of transactions.
78 * @param conversationHolder
80 public CdmStore(CdmRepository repo
, S service
, ConversationHolder conversationHolder
) {
83 this.service
= service
;
84 this.conversationHolder
= conversationHolder
;
92 public TransactionStatus
startTransaction() {
93 if(conversationHolder
!= null && !conversationHolder
.isTransactionActive()){
94 //conversationHolder.setDefinition(getTransactionDefinition());
95 return conversationHolder
.startTransaction();
97 checkExistingTransaction();
98 txNonConversational
= repo
.startTransaction();
99 return txNonConversational
;
106 protected void checkExistingTransaction() {
107 if (txNonConversational
!= null) {
109 // holding the TransactionStatus as state is not good design. we
110 // should change the save operation
111 // in the EditorView so that the presenter can process the save in
114 // 1. the fieldGroup needs a open session and read transaction
115 // during the validation, otherwise
116 // LazyInitialisationExceptions occur.
117 // 2. passing the TransactionState to the view also doesn't seem
120 throw new RuntimeException("Opening a second transaction in the same" + this.getClass().getSimpleName() + " is not supported");
125 * If the bean is contained in the session it is being updated by doing an
126 * evict and merge. The fieldGroup is updated with the merged bean.
130 * @return The bean merged to the session or original bean in case a merge
133 public T
mergedBean(T bean
) {
135 Session session
= getSession();
138 if (session
.contains(bean
)) {
139 // evict bean before merge to avoid duplicate beans in same session
140 logger
.trace(this._toString() + ".mergedBean() - evict " + bean
.toString());
144 logger
.trace(this._toString() + ".mergedBean() - doing merge of" + bean
.toString());
145 // to avoid merge problems as described in
146 // https://dev.e-taxonomy.eu/redmine/issues/6687
147 // we are set the hibernate property
148 // hibernate.event.merge.entity_copy_observer=allow
149 @SuppressWarnings("unchecked")
150 T mergedBean
= (T
) session
.merge(bean
);
151 logger
.trace(this._toString() + ".mergedBean() - bean after merge " + bean
.toString());
159 private Session
getSession() {
162 if(conversationHolder
!= null){
163 session
= conversationHolder
.getSession();
165 session
= repo
.getSession();
167 logger
.trace(this._toString() + ".getSession() - session:" + session
.hashCode() + ", persistenceContext: "
168 + ((SessionImplementor
) session
).getPersistenceContext() + " - " + session
.toString());
173 protected String
_toString() {
174 return this.getClass().getSimpleName() + "@" + this.hashCode();
181 * @return the merged bean, this bean is <b>not reloaded</b> from the
182 * persistent storage.
184 public EntityChangeEvent
saveBean(T bean
) {
186 Type changeEventType
;
187 if(bean
.getId() > 1){
188 changeEventType
= Type
.MODIFIED
;
190 changeEventType
= Type
.CREATED
;
193 Session session
= getSession();
194 logger
.trace(this._toString() + ".onEditorSaveEvent - session: " + session
.hashCode());
196 if(txNonConversational
== null || (conversationHolder
!= null && !conversationHolder
.isTransactionActive())){
197 // no running transaction, start one ...
201 logger
.trace(this._toString() + ".onEditorSaveEvent - merging bean into session");
202 // merge the changes into the session, ...
204 T mergedBean
= mergedBean(bean
);
205 //T mergedBean = bean;
206 // NOTE: saveOrUpdate is really needed here even if we to a merge before
207 repo
.getCommonService().saveOrUpdate(mergedBean
);
211 return new EntityChangeEvent(mergedBean
.getClass(), mergedBean
.getId(), changeEventType
);
217 * @return a EntityChangeEvent in case the deletion was successful otherwise <code>null</code>.
219 public final EntityChangeEvent
deleteBean(T bean
) {
221 logger
.trace(this._toString() + ".onEditorPreSaveEvent - starting transaction");
224 logger
.trace(this._toString() + ".deleteBean - deleting" + bean
.toString());
225 DeleteResult result
= service
.delete(bean
);
228 getSession().flush();
230 logger
.trace(this._toString() + ".deleteBean - transaction comitted");
231 return new EntityChangeEvent(bean
.getClass(), bean
.getId(), Type
.REMOVED
);
233 String notificationTitle
;
234 StringBuffer messageBody
= new StringBuffer();
235 if (result
.isAbort()) {
236 notificationTitle
= "The delete operation as abborded by the system.";
238 notificationTitle
= "An error occured during the delete operation.";
240 if (!result
.getExceptions().isEmpty()) {
241 messageBody
.append("<h3>").append("Exceptions:").append("</h3>").append("<ul>");
242 result
.getExceptions().forEach(e
-> messageBody
.append("<li>").append(e
.getMessage()).append("</li>"));
243 messageBody
.append("</ul>");
245 if (!result
.getRelatedObjects().isEmpty()) {
246 messageBody
.append("<h3>").append("Related objects exist:").append("</h3>").append("<ul>");
247 result
.getRelatedObjects().forEach(e
-> {
248 messageBody
.append("<li>");
249 if (IdentifiableEntity
.class.isAssignableFrom(e
.getClass())) {
250 messageBody
.append(((IdentifiableEntity
) e
).getTitleCache());
252 messageBody
.append(e
.toString());
254 messageBody
.append("</li>");
257 messageBody
.append("</ul>");
259 Notification notification
= new Notification(notificationTitle
, messageBody
.toString(),
260 com
.vaadin
.ui
.Notification
.Type
.ERROR_MESSAGE
, true);
261 notification
.show(UI
.getCurrent().getPage());
262 txNonConversational
= null;
268 protected void commitTransction() {
270 if(conversationHolder
!= null){
271 conversationHolder
.commit();
273 repo
.commitTransaction(txNonConversational
);
274 txNonConversational
= null;
281 public T
loadBean(int entityId
) {
282 conversationHolder
.startTransaction();
283 return service
.find(entityId
);
286 public S
getService() {