Project

General

Profile

« Previous | Next » 

Revision 262c713c

Added by Andreas Kohlbecker almost 7 years ago

ref #6687 moving all update and delete operations into a dedicated class: CdmStore

View differences:

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
}
src/main/java/eu/etaxonomy/cdm/vaadin/view/name/SpecimenTypeDesignationWorkingsetEditorPresenter.java
175 175
     */
176 176
    @Override
177 177
    protected void saveBean(SpecimenTypeDesignationWorkingSetDTO bean) {
178
        // TODO Auto-generated method stub
178
        Session session = getSession();
179
        getRepo().startTransaction();
179 180

  
180 181
    }
181 182

  
src/main/java/eu/etaxonomy/cdm/vaadin/view/name/SpecimenTypeDesignationWorkingsetPopupEditor.java
56 56

  
57 57
    private ElementCollectionField<SpecimenTypeDesignationDTO> typeDesignationsCollectionField;
58 58

  
59
    private static int windowPixelWidth = 900;
60

  
61 59
    /**
62 60
     * @return the countrySelectField
63 61
     */
src/main/java/eu/etaxonomy/cdm/vaadin/view/name/TaxonNameEditorPresenter.java
14 14

  
15 15
import org.apache.log4j.Logger;
16 16

  
17
import eu.etaxonomy.cdm.api.service.DeleteResult;
18
import eu.etaxonomy.cdm.api.service.config.NameDeletionConfigurator;
17
import eu.etaxonomy.cdm.api.service.INameService;
19 18
import eu.etaxonomy.cdm.model.name.TaxonName;
20 19
import eu.etaxonomy.cdm.model.reference.Reference;
21 20
import eu.etaxonomy.cdm.service.CdmFilterablePagingProvider;
......
63 62
    }
64 63

  
65 64

  
66
    /**
67
     * {@inheritDoc}
68
     */
69
    @Override
70
    protected DeleteResult executeServiceDeleteOperation(TaxonName bean) {
71
        NameDeletionConfigurator config = new NameDeletionConfigurator();
72
        return getRepo().getNameService().delete(bean.getUuid(), config);
73
    }
74

  
75 65
    @Override
76 66
    protected TaxonName handleTransientProperties(TaxonName bean) {
77 67
        logger.trace(this._toString() + ".onEditorSaveEvent - handling transient properties");
......
92 82
            }
93 83
        }
94 84
        return bean;
85
    }
95 86

  
87
    /**
88
     * {@inheritDoc}
89
     */
90
    @Override
91
    protected INameService getService() {
92
        return getRepo().getNameService();
96 93
    }
97 94

  
98 95

  
96

  
99 97
}
src/main/java/eu/etaxonomy/cdm/vaadin/view/reference/ReferenceEditorPresenter.java
18 18
import org.vaadin.viritin.fields.LazyComboBox.FilterableCountProvider;
19 19
import org.vaadin.viritin.fields.LazyComboBox.FilterablePagingProvider;
20 20

  
21
import eu.etaxonomy.cdm.api.service.DeleteResult;
21
import eu.etaxonomy.cdm.api.service.IService;
22 22
import eu.etaxonomy.cdm.api.service.pager.Pager;
23 23
import eu.etaxonomy.cdm.model.reference.Reference;
24 24
import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
......
143 143
     * {@inheritDoc}
144 144
     */
145 145
    @Override
146
    protected DeleteResult executeServiceDeleteOperation(Reference bean) {
147
        return getRepo().getReferenceService().delete(bean);
146
    protected IService<Reference> getService() {
147
        return getRepo().getReferenceService();
148 148
    }
149 149

  
150

  
151

  
150 152
}
src/main/java/eu/etaxonomy/cdm/vaadin/view/registration/RegistrationEditorPresenter.java
8 8
*/
9 9
package eu.etaxonomy.cdm.vaadin.view.registration;
10 10

  
11
import eu.etaxonomy.cdm.api.service.DeleteResult;
11
import eu.etaxonomy.cdm.api.service.IRegistrationService;
12 12
import eu.etaxonomy.cdm.model.name.Registration;
13 13
import eu.etaxonomy.vaadin.mvp.AbstractCdmEditorPresenter;
14 14

  
......
25 25
     * {@inheritDoc}
26 26
     */
27 27
    @Override
28
    protected DeleteResult executeServiceDeleteOperation(Registration bean) {
29
        return getRepo().getRegistrationService().delete(bean);
28
    protected IRegistrationService getService() {
29
        return getRepo().getRegistrationService();
30 30
    }
31 31

  
32 32

  
src/main/java/eu/etaxonomy/vaadin/mvp/AbstractCdmEditorPresenter.java
13 13
import org.springframework.context.event.EventListener;
14 14
import org.springframework.transaction.TransactionStatus;
15 15

  
16
import com.vaadin.ui.Notification;
17
import com.vaadin.ui.UI;
18

  
19
import eu.etaxonomy.cdm.api.service.DeleteResult;
16
import eu.etaxonomy.cdm.api.service.IService;
20 17
import eu.etaxonomy.cdm.model.common.CdmBase;
21
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
18
import eu.etaxonomy.cdm.service.CdmStore;
22 19
import eu.etaxonomy.cdm.vaadin.event.EntityChangeEvent;
23
import eu.etaxonomy.cdm.vaadin.event.EntityChangeEvent.Type;
24 20
import eu.etaxonomy.vaadin.mvp.event.EditorPreSaveEvent;
25 21
import eu.etaxonomy.vaadin.mvp.event.EditorSaveEvent;
26 22

  
......
39 35

  
40 36
    TransactionStatus tx = null;
41 37

  
38
    Session session = null;
39

  
40
    CdmStore<DTO, IService<DTO>> store ;
41

  
42 42
    public AbstractCdmEditorPresenter() {
43 43
        super();
44 44
        logger.trace(this._toString() + " constructor");
45 45
    }
46 46

  
47
    protected CdmStore<DTO, IService<DTO>> getStore() {
48
        if(store == null){
49
            store = new CdmStore<>(getRepo(), getService());
50
        }
51
        return store;
52
    }
53

  
54

  
47 55
    /**
48 56
     * @return
49
     *
50 57
     */
51
    protected TransactionStatus startTransaction() {
52
        if(tx != null){
53
            // @formatter:off
54
            // holding the TransactionStatus as state is not good design. we should change the save operation
55
            // in the EditorView so that the presenter can process the save in one method call.
56
            // Problems:
57
            // 1. the fieldGroup needs a open session and read transaction during the validation, otherwise
58
            //    LazyInitialisationExceptions occur.
59
            // 2. passing the TransactionState to the view also doesn't seem like a good idea.
60
            // @formatter:on
61
            throw new RuntimeException("Can't process a second save operation while another one is in progress.");
62
        }
63
        return getRepo().startTransaction(true);
64
    }
58
    protected abstract IService<DTO> getService();
65 59

  
66 60
    @SuppressWarnings("unchecked")
67 61
    @Override
......
71 65
            return;
72 66
        }
73 67

  
68
        session = getSession();
69
        logger.trace(this._toString() + ".onEditorPreSaveEvent - session: " + session);
74 70
        logger.trace(this._toString() + ".onEditorPreSaveEvent - starting transaction");
75
        tx = startTransaction();
71
        tx = getStore().startTransaction();
76 72
        // merge the bean and update the fieldGroup with the merged bean, so that updating
77 73
        // of field values in turn of the commit are can not cause LazyInitializationExeptions
78 74
        // the bean still has the original values at this point
......
88 84
            return;
89 85
        }
90 86
        // the bean is now updated with the changes made by the user
91
        Session session = getSession();
92
        logger.trace(this._toString() + ".onEditorSaveEvent - merging bean into session");
93 87
        DTO bean = (DTO) saveEvent.getBean();
94
        Type changeEventType;
95
        if(bean.getId() > 1){
96
            changeEventType = Type.MODIFIED;
97
        } else {
98
            changeEventType = Type.CREATED;
99
        }
100 88
        bean = handleTransientProperties(bean);
101
        // merge the changes into the session, ...
102
        DTO mergedBean = mergedBean(bean);
103
        getRepo().getCommonService().saveOrUpdate(mergedBean);
104
        session.flush();
105
        logger.trace(this._toString() + ".onEditorSaveEvent - session flushed");
106
        getRepo().commitTransaction(tx);
107
        tx = null;
108
        if(session.isOpen()){
109
            session.close();
89
        EntityChangeEvent changeEvent = getStore().saveBean(bean);
90
        if(changeEvent != null){
91
            eventBus.publishEvent(changeEvent);
110 92
        }
111
        logger.trace(this._toString() + ".onEditorSaveEvent - transaction comitted");
112
        eventBus.publishEvent(new EntityChangeEvent(mergedBean.getClass(), mergedBean.getId(), changeEventType));
113 93
    }
114 94

  
115 95
    /**
......
119 99
     * This is necessary because Vaadin MethodProperties are readonly when no setter is
120 100
     * available. This can be the case with transient properties. Automatic updating
121 101
     * of the property during the fieldGroup commit does not work in this case.
102
     *
103
     * @deprecated editors should operate on DTOs instead, remove this method if unused.
122 104
     */
105
    @Deprecated
123 106
    protected DTO handleTransientProperties(DTO bean) {
124 107
        // no need to handle transient properties in the generic case
125 108
        return bean;
126 109
    }
127 110

  
128 111
    /**
129
     * Obtains the bean from the fieldGroup. If the bean is contained in the session is being updated by
112
     * If the bean is contained in the session it is being updated by
130 113
     * doing an evict and merge. The fieldGroup is updated with the merged bean.
131 114
     *
115
     * @param bean
132 116
     *
133
     * @param CommitEvent
134 117
     * @return The bean merged to the session or original bean in case a merge was not necessary.
135 118
     */
136 119
    private DTO mergedBean(DTO bean) {
137
        Session session = getSession();
138

  
139
        // session.clear();
140
        if(session.contains(bean)){
141
            // evict bean before merge to avoid duplicate beans in same session
142
            logger.trace(this._toString() + ".mergedBean() - evict " + bean.toString());
143
            session.evict(bean);
144
        }
145

  
146
        logger.trace(this._toString() + ".mergedBean() - doing merge of" + bean.toString());
147
        // to avoid merge problems as described in https://dev.e-taxonomy.eu/redmine/issues/6687
148
        // we are set the hibernate property hibernate.event.merge.entity_copy_observer=allow
149
        @SuppressWarnings("unchecked")
150
        DTO mergedBean = (DTO) session.merge(bean);
151
        logger.trace(this._toString() + ".mergedBean() - bean after merge " + bean.toString());
120
        DTO mergedBean = getStore().mergedBean(bean);
152 121
        ((AbstractPopupEditor<DTO, AbstractCdmEditorPresenter<DTO, V>>)getView()).updateItemDataSource(mergedBean);
153 122
        return mergedBean;
154 123

  
155 124
    }
156 125

  
157

  
158 126
    @Override
159 127
    protected final void saveBean(DTO bean){
160 128
        // blank implementation, since this is not needed in this or any sub class
129
        // see onEditorSaveEvent() instead
161 130
    }
162 131

  
163 132
    @Override
164 133
    protected final void deleteBean(DTO bean){
165
        logger.trace(this._toString() + ".onEditorPreSaveEvent - starting transaction");
166
        tx = startTransaction();
167
        logger.trace(this._toString() + ".deleteBean - deleting" + bean.toString());
168
        DeleteResult result = executeServiceDeleteOperation(bean);
169
        if(result.isOk()){
170
            Session session = getSession();
171
            session.flush();
172
            logger.trace(this._toString() + ".deleteBean - session flushed");
173
            getRepo().commitTransaction(tx);
174
            tx = null;
175
            session.close();
176
            logger.trace(this._toString() + ".deleteBean - transaction comitted");
177
            eventBus.publishEvent(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

  
204
                messageBody.append("</ul>");
205
            }
206
            Notification notification = new Notification(
207
                   notificationTitle,
208
                   messageBody.toString(),
209
                   com.vaadin.ui.Notification.Type.ERROR_MESSAGE,
210
                   true);
211
            notification.show(UI.getCurrent().getPage());
212
            tx = null;
134
        EntityChangeEvent changeEvent = getStore().deleteBean(bean);
135
        if(changeEvent != null){
136
            eventBus.publishEvent(changeEvent);
213 137
        }
214
    }
215 138

  
216
    /**
217
     * Implementations will execute the {@link
218
     *
219
     * @return
220
     */
221
    protected abstract DeleteResult executeServiceDeleteOperation(DTO bean);
139
    }
222 140

  
223 141
}

Also available in: Unified diff