Project

General

Profile

Download (8.6 KB) Statistics
| Branch: | Tag: | Revision:
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
import org.springframework.transaction.support.DefaultTransactionDefinition;
16

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

    
20
import eu.etaxonomy.cdm.api.application.CdmRepository;
21
import eu.etaxonomy.cdm.api.service.DeleteResult;
22
import eu.etaxonomy.cdm.api.service.IService;
23
import eu.etaxonomy.cdm.model.common.CdmBase;
24
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
25
import eu.etaxonomy.cdm.vaadin.event.EntityChangeEvent;
26
import eu.etaxonomy.cdm.vaadin.event.EntityChangeEvent.Type;
27
import eu.etaxonomy.vaadin.mvp.AbstractView;
28

    
29
/**
30
 * @author a.kohlbecker
31
 * @since Jun 26, 2017
32
 *
33
 * TODO better naming of this class, ServiceWrapper, ServiceOperator, ...?
34
 *
35
 */
36
public class CdmStore<T extends CdmBase, S extends IService<T>> {
37

    
38
    private static final Logger logger = Logger.getLogger(CdmStore.class);
39

    
40
    private CdmRepository repo;
41

    
42
    private S service;
43

    
44
    TransactionStatus txStatus = null;
45

    
46
    protected DefaultTransactionDefinition txDefinition = null;
47

    
48
    /**
49
     *
50
     * @param repo
51
     * @param service
52
     *            may be <code>null</code>, but delete operations will fail with
53
     *            a NullPointerException in this case.
54
     */
55
    public CdmStore(CdmRepository repo, S service) {
56
        this.repo = repo;
57
        this.service = service;
58
    }
59

    
60

    
61
    /**
62
     * @return
63
     *
64
     */
65
    public TransactionStatus startTransaction() {
66
        checkExistingTransaction();
67
        txStatus = repo.startTransaction();
68
        return txStatus;
69
    }
70

    
71
    /**
72
     *
73
     */
74
    protected void checkExistingTransaction() {
75
        if (txStatus != null) {
76
            // @formatter:off
77
            // holding the TransactionStatus as state is not good design. we
78
            // should change the save operation
79
            // in the EditorView so that the presenter can process the save in
80
            // one method call.
81
            // Problems:
82
            // 1. the fieldGroup needs a open session and read transaction
83
            // during the validation, otherwise
84
            // LazyInitialisationExceptions occur.
85
            // 2. passing the TransactionState to the view also doesn't seem
86
            // like a good idea.
87
            // @formatter:on
88
            throw new RuntimeException("Opening a second transaction in the same" + this.getClass().getSimpleName() + " is not supported");
89
        }
90
    }
91

    
92
    /**
93
     * If the bean is contained in the session it is being updated by doing an
94
     * evict and merge. The fieldGroup is updated with the merged bean.
95
     *
96
     *
97
     * @param bean
98
     * @return The bean merged to the session or original bean in case a merge
99
     *         was not necessary.
100
     */
101
    public T mergedBean(T bean) throws IllegalStateException {
102

    
103
        Session session = getSession();
104

    
105
        if (session.contains(bean)) {
106
            // evict bean before merge to avoid duplicate beans in same session
107
            logger.trace(this._toString() + ".mergedBean() - evict " + bean.toString());
108
            session.evict(bean);
109
        }
110

    
111
        logger.trace(this._toString() + ".mergedBean() - doing merge of" + bean.toString());
112
        // to avoid merge problems as described in https://dev.e-taxonomy.eu/redmine/issues/6687
113
        // we are set the hibernate property hibernate.event.merge.entity_copy_observer=allow
114
        @SuppressWarnings("unchecked")
115
        T mergedBean = (T) session.merge(bean);
116
        logger.trace(this._toString() + ".mergedBean() - bean after merge " + bean.toString());
117
        return mergedBean;
118

    
119
    }
120

    
121
    /**
122
     * @return
123
     */
124
    private Session getSession() {
125

    
126
        Session session = repo.getSession();
127

    
128
        logger.trace(this._toString() + ".getSession() - session:" + session.hashCode() + ", persistenceContext: "
129
                + ((SessionImplementor) session).getPersistenceContext() + " - " + session.toString());
130

    
131
        return session;
132
    }
133

    
134
    protected String _toString() {
135
        return this.getClass().getSimpleName() + "@" + this.hashCode();
136
    }
137

    
138
    /**
139
     *
140
     * @param bean
141
     *
142
     * @return the merged bean, this bean is <b>not reloaded</b> from the
143
     *         persistent storage.
144
     */
145
    public EntityChangeEvent saveBean(T bean, AbstractView view) {
146

    
147
        Type changeEventType;
148
        if(bean.isPersited()){
149
            changeEventType = Type.MODIFIED;
150
        } else {
151
            changeEventType = Type.CREATED;
152
        }
153

    
154
        Session session = getSession();
155
        try {
156
            logger.trace(this._toString() + ".onEditorSaveEvent - session: " + session.hashCode());
157

    
158
            if(txStatus == null){
159
                // no running transaction, start one ...
160
                startTransaction();
161
            }
162

    
163
            logger.trace(this._toString() + ".onEditorSaveEvent - merging bean into session");
164
            // merge the changes into the session, ...
165
            T mergedBean = mergedBean(bean);
166
            session.flush();
167
            commitTransaction();
168
            return new EntityChangeEvent(mergedBean, changeEventType, view);
169
        } finally {
170
            session.clear(); // #7559
171
        }
172
    }
173

    
174
    /**
175
     *
176
     * @param bean
177
     * @return a EntityChangeEvent in case the deletion was successful otherwise <code>null</code>.
178
     */
179
    public final EntityChangeEvent deleteBean(T bean, AbstractView view) {
180

    
181
        logger.trace(this._toString() + ".onEditorPreSaveEvent - starting transaction");
182
        Session session = getSession();
183
        try {
184
            startTransaction();
185
            logger.trace(this._toString() + ".deleteBean - deleting" + bean.toString());
186
            DeleteResult result = service.delete(bean);
187
            if (result.isOk()) {
188
                session.flush();
189
                commitTransaction();
190
                logger.trace(this._toString() + ".deleteBean - transaction comitted");
191
                return new EntityChangeEvent(bean, Type.REMOVED, view);
192
            } else {
193
                handleDeleteresultInError(result, session);
194
                txStatus = null;
195
            }
196
        } finally {
197
            session.clear(); // #7559
198
        }
199
        return null;
200
    }
201

    
202
    /**
203
     * @param result
204
     */
205
    public static void handleDeleteresultInError(DeleteResult result, Session session) {
206
        String notificationTitle;
207
        StringBuffer messageBody = new StringBuffer();
208
        if (result.isAbort()) {
209
            notificationTitle = "The delete operation as abborded by the system.";
210
        } else {
211
            notificationTitle = "An error occured during the delete operation.";
212
        }
213
        if (!result.getExceptions().isEmpty()) {
214
            messageBody.append("<h3>").append("Exceptions:").append("</h3>").append("<ul>");
215
            result.getExceptions().forEach(e -> messageBody.append("<li>").append(e.getMessage()).append("</li>"));
216
            messageBody.append("</ul>");
217
            /*
218
             * not needed since covered by clear() in finally clause
219
            if(result.getExceptions().stream().anyMatch(e -> HibernateException.class.isAssignableFrom(e.getClass()))){
220
                session.clear(); // #7559
221
            }
222
            */
223
        }
224
        if (!result.getRelatedObjects().isEmpty()) {
225
            messageBody.append("<h3>").append("Related objects exist:").append("</h3>").append("<ul>");
226
            result.getRelatedObjects().forEach(e -> {
227
                messageBody.append("<li>");
228
                if (IdentifiableEntity.class.isAssignableFrom(e.getClass())) {
229
                    messageBody.append(((IdentifiableEntity) e).getTitleCache());
230
                } else {
231
                    messageBody.append(e.toString());
232
                }
233
                messageBody.append("</li>");
234
            });
235

    
236
            messageBody.append("</ul>");
237
        }
238
        Notification notification = new Notification(notificationTitle, messageBody.toString(),
239
                com.vaadin.ui.Notification.Type.ERROR_MESSAGE, true);
240
        notification.show(UI.getCurrent().getPage());
241
    }
242

    
243

    
244
    protected void commitTransaction() {
245
        repo.commitTransaction(txStatus);
246
        txStatus = null;
247
    }
248

    
249
    /**
250
     * @param entityId
251
     */
252
    public T loadBean(int entityId) {
253
        return service.find(entityId);
254
    }
255

    
256
    public S getService() {
257
        return service;
258
    }
259

    
260

    
261
}
(3-3/8)