ref #6687 moving all update and delete operations into a dedicated class: CdmStore
[cdm-vaadin.git] / src / main / java / eu / etaxonomy / cdm / service / CdmStore.java
1 /**
2 * Copyright (C) 2017 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 package eu.etaxonomy.cdm.service;
10
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
16 import com.vaadin.ui.Notification;
17 import com.vaadin.ui.UI;
18
19 import eu.etaxonomy.cdm.api.application.CdmRepository;
20 import eu.etaxonomy.cdm.api.service.DeleteResult;
21 import eu.etaxonomy.cdm.api.service.IService;
22 import eu.etaxonomy.cdm.model.common.CdmBase;
23 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
24 import eu.etaxonomy.cdm.vaadin.event.EntityChangeEvent;
25 import eu.etaxonomy.cdm.vaadin.event.EntityChangeEvent.Type;
26
27 /**
28 * @author a.kohlbecker
29 * @since Jun 26, 2017
30 *
31 * TODO better naming of this class, ServiceWrapper, ServiceOperator, ...?
32 *
33 */
34 public class CdmStore<T extends CdmBase, S extends IService<T>> {
35
36 private static final Logger logger = Logger.getLogger(CdmStore.class);
37
38 private CdmRepository repo;
39
40 private S service;
41
42 TransactionStatus tx = null;
43
44 Session session = null;
45
46 /**
47 *
48 * @param repo
49 * @param service
50 * may be <code>null</code>, but delete operations will fail with
51 * a NullPointerException in this case.
52 */
53 public CdmStore(CdmRepository repo, S service) {
54
55 this.repo = repo;
56 this.service = service;
57 }
58
59 /**
60 * @return
61 *
62 */
63 public TransactionStatus startTransaction() {
64 if (tx != null) {
65 // @formatter:off
66 // holding the TransactionStatus as state is not good design. we
67 // should change the save operation
68 // in the EditorView so that the presenter can process the save in
69 // one method call.
70 // Problems:
71 // 1. the fieldGroup needs a open session and read transaction
72 // during the validation, otherwise
73 // LazyInitialisationExceptions occur.
74 // 2. passing the TransactionState to the view also doesn't seem
75 // like a good idea.
76 // @formatter:on
77 throw new RuntimeException("Can't process a second save operation while another one is in progress.");
78 }
79 return repo.startTransaction(true);
80 }
81
82 /**
83 * If the bean is contained in the session it is being updated by doing an
84 * evict and merge. The fieldGroup is updated with the merged bean.
85 *
86 *
87 * @param bean
88 * @return The bean merged to the session or original bean in case a merge
89 * was not necessary.
90 */
91 public T mergedBean(T bean) {
92 Session session = getSession();
93
94 // session.clear();
95 if (session.contains(bean)) {
96 // evict bean before merge to avoid duplicate beans in same session
97 logger.trace(this._toString() + ".mergedBean() - evict " + bean.toString());
98 session.evict(bean);
99 }
100
101 logger.trace(this._toString() + ".mergedBean() - doing merge of" + bean.toString());
102 // to avoid merge problems as described in
103 // https://dev.e-taxonomy.eu/redmine/issues/6687
104 // we are set the hibernate property
105 // hibernate.event.merge.entity_copy_observer=allow
106 @SuppressWarnings("unchecked")
107 T mergedBean = (T) session.merge(bean);
108 logger.trace(this._toString() + ".mergedBean() - bean after merge " + bean.toString());
109 return mergedBean;
110
111 }
112
113 /**
114 * @return
115 */
116 protected Session getSession() {
117 Session session = repo.getSession();
118 logger.trace(this._toString() + ".getSession() - session:" + session.hashCode() + ", persistenceContext: "
119 + ((SessionImplementor) session).getPersistenceContext() + " - " + session.toString());
120 return session;
121 }
122
123 protected String _toString() {
124 return this.getClass().getSimpleName() + "@" + this.hashCode();
125 }
126
127 /**
128 *
129 * @param bean
130 * @return the merged bean, this bean is <b>not reloaded</b> from the
131 * persistent storage.
132 */
133 public EntityChangeEvent saveBean(T bean) {
134
135 Type changeEventType;
136 if(bean.getId() > 1){
137 changeEventType = Type.MODIFIED;
138 } else {
139 changeEventType = Type.CREATED;
140 }
141
142 // Session session = getSession();
143 logger.trace(this._toString() + ".onEditorSaveEvent - session: " + session);
144 logger.trace(this._toString() + ".onEditorSaveEvent - merging bean into session");
145 // merge the changes into the session, ...
146 T mergedBean = mergedBean(bean);
147 repo.getCommonService().saveOrUpdate(mergedBean);
148 session.flush();
149 logger.trace(this._toString() + ".onEditorSaveEvent - session flushed");
150 repo.commitTransaction(tx);
151 tx = null;
152 if (session.isOpen()) {
153 session.close();
154 }
155 logger.trace(this._toString() + ".onEditorSaveEvent - transaction comitted");
156 return new EntityChangeEvent(mergedBean.getClass(), mergedBean.getId(), changeEventType);
157 }
158
159 /**
160 *
161 * @param bean
162 * @return a EntityChangeEvent in case the deletion was successful otherwise <code>null</code>.
163 */
164 public final EntityChangeEvent deleteBean(T bean) {
165 logger.trace(this._toString() + ".onEditorPreSaveEvent - starting transaction");
166 tx = startTransaction();
167 logger.trace(this._toString() + ".deleteBean - deleting" + bean.toString());
168 DeleteResult result = service.delete(bean);
169 if (result.isOk()) {
170 Session session = getSession();
171 session.flush();
172 logger.trace(this._toString() + ".deleteBean - session flushed");
173 repo.commitTransaction(tx);
174 tx = null;
175 session.close();
176 logger.trace(this._toString() + ".deleteBean - transaction comitted");
177 return new EntityChangeEvent(bean.getClass(), bean.getId(), Type.REMOVED);
178 } else {
179 String notificationTitle;
180 StringBuffer messageBody = new StringBuffer();
181 if (result.isAbort()) {
182 notificationTitle = "The delete operation as abborded by the system.";
183 } else {
184 notificationTitle = "An error occured during the delete operation.";
185 }
186 if (!result.getExceptions().isEmpty()) {
187 messageBody.append("<h3>").append("Exceptions:").append("</h3>").append("<ul>");
188 result.getExceptions().forEach(e -> messageBody.append("<li>").append(e.getMessage()).append("</li>"));
189 messageBody.append("</ul>");
190 }
191 if (!result.getRelatedObjects().isEmpty()) {
192 messageBody.append("<h3>").append("Related objects exist:").append("</h3>").append("<ul>");
193 result.getRelatedObjects().forEach(e -> {
194 messageBody.append("<li>");
195 if (IdentifiableEntity.class.isAssignableFrom(e.getClass())) {
196 messageBody.append(((IdentifiableEntity) e).getTitleCache());
197 } else {
198 messageBody.append(e.toString());
199 }
200 messageBody.append("</li>");
201 });
202
203 messageBody.append("</ul>");
204 }
205 Notification notification = new Notification(notificationTitle, messageBody.toString(),
206 com.vaadin.ui.Notification.Type.ERROR_MESSAGE, true);
207 notification.show(UI.getCurrent().getPage());
208 tx = null;
209 }
210 return null;
211 }
212
213 }