cba2f9c437b3f0a97d190397ee8d4e27db7981be
[cdm-vaadin.git] / src / main / java / eu / etaxonomy / vaadin / mvp / CdmEditorPresenterBase.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.vaadin.mvp;
10
11 import java.util.Collection;
12 import java.util.EnumSet;
13 import java.util.HashSet;
14 import java.util.UUID;
15
16 import org.apache.logging.log4j.LogManager;
17 import org.apache.logging.log4j.Logger;
18 import org.hibernate.HibernateException;
19 import org.springframework.beans.factory.annotation.Autowired;
20
21 import eu.etaxonomy.cdm.api.service.IService;
22 import eu.etaxonomy.cdm.api.util.UserHelper;
23 import eu.etaxonomy.cdm.cache.CdmTransientEntityWithUuidCacher;
24 import eu.etaxonomy.cdm.debug.PersistentContextAnalyzer;
25 import eu.etaxonomy.cdm.model.ICdmEntityUuidCacher;
26 import eu.etaxonomy.cdm.model.common.CdmBase;
27 import eu.etaxonomy.cdm.model.permission.CRUD;
28 import eu.etaxonomy.cdm.persistence.permission.CdmAuthority;
29 import eu.etaxonomy.cdm.service.CdmBeanItemContainerFactory;
30 import eu.etaxonomy.cdm.service.CdmFilterablePagingProviderFactory;
31 import eu.etaxonomy.cdm.service.CdmStore;
32 import eu.etaxonomy.cdm.service.UserHelperAccess;
33 import eu.etaxonomy.cdm.vaadin.event.EntityChangeEvent;
34 import eu.etaxonomy.cdm.vaadin.view.name.CachingPresenter;
35 import eu.etaxonomy.vaadin.mvp.event.EditorPreSaveEvent;
36 import eu.etaxonomy.vaadin.mvp.event.EditorSaveEvent;
37
38 /**
39 * Provides generic save operations of modified cdm entities.
40 *
41 * @author a.kohlbecker
42 * @since Apr 5, 2017
43 */
44 public abstract class CdmEditorPresenterBase<DTO, CDM extends CdmBase, V extends ApplicationView<?>>
45 extends AbstractEditorPresenter<DTO, V>
46 implements CachingPresenter {
47
48 private static final long serialVersionUID = 2218185546277084261L;
49
50 private final static Logger logger = LogManager.getLogger();
51
52 protected BeanInstantiator<DTO> beanInstantiator = null;
53
54 @Autowired
55 protected CdmBeanItemContainerFactory cdmBeanItemContainerFactory;
56
57 @Autowired
58 protected CdmFilterablePagingProviderFactory pagingProviderFactory;
59
60 /**
61 * @param beanInstantiator the beanInstantiator to set
62 */
63 public void setBeanInstantiator(BeanInstantiator<DTO> beanInstantiator) {
64 this.beanInstantiator = beanInstantiator;
65 }
66
67
68 protected DTO createNewBean() {
69 if(this.beanInstantiator != null){
70 return beanInstantiator.createNewBean();
71 }
72 return defaultBeanInstantiator().createNewBean();
73 }
74
75 /**
76 * @return
77 */
78 protected abstract BeanInstantiator<DTO> defaultBeanInstantiator();
79
80 /**
81 * if not null, this CRUD set is to be used to create a CdmAuthoritiy for the base entity which will be
82 * granted to the current use as long this grant is not assigned yet.
83 */
84 protected EnumSet<CRUD> crud = null;
85
86
87 private ICdmEntityUuidCacher cache;
88
89 private java.util.Collection<CdmBase> rootEntities = new HashSet<>();
90
91 public CdmEditorPresenterBase() {
92 super();
93 logger.trace(this._toString() + " constructor");
94 }
95
96 @Autowired
97 protected CdmStore cdmStore;
98
99 protected CdmAuthority newAuthorityCreated;
100
101 @Override
102 protected DTO loadBeanById(Object identifier) {
103
104 CDM cdmEntitiy;
105 if(identifier != null) {
106 UUID uuidIdentifier = (UUID)identifier;
107 // CdmAuthority is needed before the bean is loaded into the session.
108 // otherwise adding the authority to the user would cause a flush
109 cdmEntitiy = loadCdmEntity(uuidIdentifier);
110 guaranteePerEntityCRUDPermissions(cdmEntitiy);
111 } else {
112 cdmEntitiy = loadCdmEntity(null);
113 if(cdmEntitiy != null){
114 guaranteePerEntityCRUDPermissions(cdmEntitiy);
115 }
116 }
117 DTO dto = initializeCache(cdmEntitiy);
118
119 return dto;
120 }
121
122
123 /**
124 * @param cdmEntitiy the CDM entity to initialize the cache with.
125 */
126 protected final DTO initializeCache(CDM cdmEntitiy) {
127 cache = new CdmTransientEntityWithUuidCacher(this);
128 // need to use load but put see #7214
129 cdmEntitiy = cache.load(cdmEntitiy);
130 addRootEntity(cdmEntitiy);
131
132 DTO dto = createDTODecorator(cdmEntitiy);
133 return dto;
134 }
135
136 /**
137 * @param cdmEntitiy
138 * @return
139 */
140 protected abstract DTO createDTODecorator(CDM cdmEntitiy);
141
142 /**
143 * @param cdmEntitiy
144 */
145 @Override
146 protected void adaptToUserPermission(DTO dto) {
147
148 CDM cdmEntitiy = cdmEntity(dto);
149
150 UserHelper userHelper = UserHelperAccess.userHelper();
151 boolean canDelete = userHelper.userHasPermission(cdmEntitiy, CRUD.DELETE);
152 boolean canEdit = userHelper.userHasPermission(cdmEntitiy, CRUD.UPDATE);
153
154 if(AbstractPopupEditor.class.isAssignableFrom(getView().getClass())){
155 AbstractPopupEditor<?,?> popupView = ((AbstractPopupEditor<?,?>)getView());
156
157 if(cdmEntitiy.isPersited() && !canEdit){
158 popupView.setReadOnly(true); // never reset true to false here!
159 logger.debug("setting editor to readonly");
160 }
161 if(!cdmEntitiy.isPersited() || !canDelete){
162 popupView.withDeleteButton(false);
163 logger.debug("removing delete button");
164 }
165 }
166
167 }
168
169 /**
170 * @param dto
171 * @return
172 */
173 protected abstract CDM cdmEntity(DTO dto);
174
175 /**
176 * @param identifier
177 * @return
178 */
179 protected abstract CDM loadCdmEntity(UUID uuid);
180
181 /**
182 * Grant per entity CdmAuthority to the current user <b>for the bean which is not yet loaded</b>
183 * into the editor. The <code>CRUD</code> to be granted are stored in the <code>crud</code> field.
184 */
185 protected abstract void guaranteePerEntityCRUDPermissions(UUID identifier);
186
187 /**
188 * Grant per entity CdmAuthority to the current user for the bean which is loaded
189 * into the editor. The <code>CRUD</code> to be granted are stored in the <code>crud</code> field.
190 */
191 protected abstract void guaranteePerEntityCRUDPermissions(CDM bean);
192
193 /**
194 * @return
195 */
196 protected abstract IService<CDM> getService();
197
198 @Override
199 // @EventBusListenerMethod // already annotated at super class
200 public void onEditorPreSaveEvent(EditorPreSaveEvent<DTO> preSaveEvent){
201 if(!isFromOwnView(preSaveEvent)){
202 return;
203 }
204 super.onEditorPreSaveEvent(preSaveEvent);
205 }
206
207 @Override
208 // @EventBusListenerMethod // already annotated at super class
209 public void onEditorSaveEvent(EditorSaveEvent<DTO> saveEvent){
210
211 if(!isFromOwnView(saveEvent)){
212 return;
213 }
214
215 // the bean is now updated with the changes made by the user
216 DTO dto = saveEvent.getBean();
217 CDM cdmEntity = cdmEntity(dto);
218
219 if(logger.isTraceEnabled()){
220 PersistentContextAnalyzer pca = new PersistentContextAnalyzer(cdmEntity);
221 pca.printEntityGraph(System.err);
222 pca.printCopyEntities(System.err);
223 }
224
225 if(logger.isTraceEnabled()){
226 PersistentContextAnalyzer pca = new PersistentContextAnalyzer(cdmEntity);
227 pca.printEntityGraph(System.err);
228 pca.printCopyEntities(System.err);
229 }
230 EntityChangeEvent<?> changeEvent = null;
231 try {
232 dto = preSaveBean(dto);
233 changeEvent = cdmStore.saveBean(cdmEntity, (AbstractView<?>) getView());
234
235 if(changeEvent != null){
236 viewEventBus.publish(this, changeEvent);
237 }
238 } catch (HibernateException e){
239 if(newAuthorityCreated != null){
240 UserHelperAccess.userHelper().removeAuthorityForCurrentUser(newAuthorityCreated);
241 }
242 throw e;
243 } finally {
244 postSaveBean(changeEvent);
245 }
246 }
247
248 /**
249 * This method is intended to be used for the following purposes:
250 * <ol>
251 * <li>
252 * EditorPresenters for beans with transient properties can overwrite this method to
253 * update the beanItem with the changes made to the transient properties.
254 * This can be necessary because Vaadin MethodProperties are readonly when no setter is
255 * available. This can be the case with transient properties. Automatic updating
256 * of the property during the fieldGroup commit does not work in this case.
257 * Presenters, however, should <b>operate on DTOs instead, which can implement the missing setter</b>.</li>
258 *
259 * <li>When modifying a bi-directional relation between two instances the user would
260 * need to have GrantedAuthorities for both sides of the relationship. This, however is not
261 * always possible. As a temporary solution the user can be granted the missing authority just
262 * for the time of saving the new relationship. You may also want to implement
263 * {@link #postSaveBean(EntityChangeEvent)} in this case.
264 * See {@link https://dev.e-taxonomy.eu/redmine/issues/7390 #7390}
265 * </li>
266 * </ol>
267 *
268 */
269 protected DTO preSaveBean(DTO bean) {
270 // blank implementation, to be implemented by sub classes if needed
271 return bean;
272 }
273
274 @Override
275 protected
276 final void saveBean(DTO bean){
277 // blank implementation, since this is not needed in this or any sub class
278 // see onEditorSaveEvent() instead
279 }
280
281 /**
282 * Called after saving the DTO to the persistent storage.
283 * This method is called in any case even if the save operation failed.
284 * See {@link #postSaveBean(EntityChangeEvent)}.
285 *
286 * @param changeEvent may be null in case of errors during the save operation
287 */
288 protected void postSaveBean(EntityChangeEvent<?> changeEvent) {
289 // blank implementation, to be implemented by sub classes if needed
290 }
291
292 @Override
293 protected void deleteBean(DTO bean){
294 CDM cdmEntity = cdmEntity(bean);
295 EntityChangeEvent<?> changeEvent = cdmStore.deleteBean(cdmEntity, (AbstractView<?>) getView());
296 if(changeEvent != null){
297 viewEventBus.publish(this, changeEvent);
298 }
299 }
300
301 public void setGrantsForCurrentUser(EnumSet<CRUD> crud) {
302 this.crud = crud;
303
304 }
305
306 @Override
307 public ICdmEntityUuidCacher getCache() {
308 return cache;
309 }
310
311 @Override
312 public void addRootEntity(CdmBase entity) {
313 rootEntities.add(entity);
314 }
315
316 @Override
317 public Collection<CdmBase> getRootEntities() {
318 return rootEntities;
319 }
320
321 @Override
322 public void destroy() throws Exception {
323 super.destroy();
324 disposeCache();
325 }
326
327 @Override
328 public void disposeCache() {
329 cache.dispose();
330 }
331 }