Project

General

Profile

Download (10.8 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.HibernateException;
13
import org.hibernate.Session;
14
import org.hibernate.engine.spi.SessionImplementor;
15
import org.springframework.transaction.TransactionStatus;
16
import org.springframework.transaction.support.DefaultTransactionDefinition;
17

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

    
21
import eu.etaxonomy.cdm.api.application.CdmRepository;
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;
28
import eu.etaxonomy.vaadin.mvp.AbstractView;
29

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

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

    
41
    private CdmRepository repo;
42

    
43
    private S service;
44

    
45
    TransactionStatus txStatus = null;
46

    
47
//    ConversationHolder conversationHolder = null;
48
//
49
//    /**
50
//     * @return the conversationHolder
51
//     */
52
//    public ConversationHolder getConversationHolder() {
53
//        return conversationHolder;
54
//    }
55

    
56
    protected DefaultTransactionDefinition txDefinition = null;
57

    
58
    /**
59
     *
60
     * @param repo
61
     * @param service
62
     *            may be <code>null</code>, but delete operations will fail with
63
     *            a NullPointerException in this case.
64
     */
65
    public CdmStore(CdmRepository repo, S service) {
66

    
67
        this.repo = repo;
68
        this.service = service;
69

    
70
    }
71

    
72
//    /**
73
//     * constructor which takes a ConversationHolder. The supplying class of the conversationHolder needs
74
//     * to care for <code>bind()</code>, <code>unbind()</code> and <code>close()</code> since the store is
75
//     * only responsible for starting and committing of transactions.
76
//     *
77
//     * @param repo
78
//     * @param service
79
//     * @param conversationHolder
80
//     */
81
//    public CdmStore(CdmRepository repo, S service, ConversationHolder conversationHolder) {
82
//
83
//        this.repo = repo;
84
//        this.service = service;
85
//        this.conversationHolder = conversationHolder;
86
//
87
//    }
88

    
89
    /**
90
     * @return
91
     *
92
     */
93
    public TransactionStatus startTransaction() {
94
//        if(conversationHolder != null && !conversationHolder.isTransactionActive()){
95
//            //conversationHolder.setDefinition(getTransactionDefinition());
96
//            return conversationHolder.startTransaction();
97
//        } else {
98
            checkExistingTransaction();
99
            txStatus = repo.startTransaction();
100
            return txStatus;
101
//        }
102
    }
103

    
104
    /**
105
     *
106
     */
107
    protected void checkExistingTransaction() {
108
        if (txStatus != null) {
109
            // @formatter:off
110
            // holding the TransactionStatus as state is not good design. we
111
            // should change the save operation
112
            // in the EditorView so that the presenter can process the save in
113
            // one method call.
114
            // Problems:
115
            // 1. the fieldGroup needs a open session and read transaction
116
            // during the validation, otherwise
117
            // LazyInitialisationExceptions occur.
118
            // 2. passing the TransactionState to the view also doesn't seem
119
            // like a good idea.
120
            // @formatter:on
121
            throw new RuntimeException("Opening a second transaction in the same" + this.getClass().getSimpleName() + " is not supported");
122
        }
123
    }
124

    
125
    /**
126
     * If the bean is contained in the session it is being updated by doing an
127
     * evict and merge. The fieldGroup is updated with the merged bean.
128
     *
129
     *
130
     * @param bean
131
     * @return The bean merged to the session or original bean in case a merge
132
     *         was not necessary.
133
     */
134
    public T mergedBean(T bean) throws IllegalStateException {
135

    
136
        Session session = getSession();
137

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

    
145
        logger.trace(this._toString() + ".mergedBean() - doing merge of" + bean.toString());
146
        // to avoid merge problems as described in
147
        // https://dev.e-taxonomy.eu/redmine/issues/6687
148
        // we are set the hibernate property
149
        // hibernate.event.merge.entity_copy_observer=allow
150
        @SuppressWarnings("unchecked")
151
        T mergedBean = (T) session.merge(bean);
152
        logger.trace(this._toString() + ".mergedBean() - bean after merge " + bean.toString());
153
        return mergedBean;
154

    
155
    }
156

    
157
    /**
158
     * @return
159
     */
160
    private Session getSession() {
161

    
162
        Session session;
163
//        if(conversationHolder != null){
164
//            session = conversationHolder.getSession();
165
//        } else {
166
            session = repo.getSession();
167
//        }
168
        logger.trace(this._toString() + ".getSession() - session:" + session.hashCode() + ", persistenceContext: "
169
                + ((SessionImplementor) session).getPersistenceContext() + " - " + session.toString());
170

    
171
        return session;
172
    }
173

    
174
    protected String _toString() {
175
        return this.getClass().getSimpleName() + "@" + this.hashCode();
176
    }
177

    
178
    /**
179
     *
180
     * @param bean
181
     *
182
     * @return the merged bean, this bean is <b>not reloaded</b> from the
183
     *         persistent storage.
184
     */
185
    public EntityChangeEvent saveBean(T bean, AbstractView view) {
186

    
187
        Type changeEventType;
188
        if(bean.getId() > 1){
189
            changeEventType = Type.MODIFIED;
190
        } else {
191
            changeEventType = Type.CREATED;
192
        }
193

    
194
        Session session = getSession();
195
        try {
196
            logger.trace(this._toString() + ".onEditorSaveEvent - session: " + session.hashCode());
197

    
198
            if(txStatus == null
199
    //                || (conversationHolder != null && !conversationHolder.isTransactionActive())
200
                    ){
201
                // no running transaction, start one ...
202
                startTransaction();
203
            }
204

    
205
            logger.trace(this._toString() + ".onEditorSaveEvent - merging bean into session");
206
            // merge the changes into the session, ...
207

    
208
            T mergedBean = mergedBean(bean);
209

    
210
            // NOTE: saveOrUpdate is really needed here even if we to a merge before
211
            // repo.getCommonService().saveOrUpdate(mergedBean);
212
            session.flush();
213
            commitTransaction();
214
            return new EntityChangeEvent(mergedBean, changeEventType, view);
215
        } catch (HibernateException | IllegalStateException e){
216
            session.clear(); // #7559
217
            throw e;
218
        }
219
//        finally {
220
//            try {
221
////                session.close(); // #7559
222
//                session.clear();
223
//            } catch (HibernateException e2) {
224
//                /* IGNORE HERE */
225
//            }
226
//        }
227

    
228
    }
229

    
230
    /**
231
     *
232
     * @param bean
233
     * @return a EntityChangeEvent in case the deletion was successful otherwise <code>null</code>.
234
     */
235
    public final EntityChangeEvent deleteBean(T bean, AbstractView view) {
236

    
237
        logger.trace(this._toString() + ".onEditorPreSaveEvent - starting transaction");
238
        Session session = getSession();
239
        try {
240
            startTransaction();
241
            logger.trace(this._toString() + ".deleteBean - deleting" + bean.toString());
242
            DeleteResult result = service.delete(bean);
243
            if (result.isOk()) {
244
                session.flush();
245
                commitTransaction();
246
                logger.trace(this._toString() + ".deleteBean - transaction comitted");
247
                return new EntityChangeEvent(bean, Type.REMOVED, view);
248
            } else {
249
                handleDeleteresultInError(result, session);
250
                txStatus = null;
251
            }
252
        } catch (HibernateException e){
253
            session.clear(); // #7559
254
            throw e;
255
        }
256
//        finally {
257
//            try {
258
////                session.close(); // #7559
259
//                session.clear(); // #7559
260
//            } catch (HibernateException e2) {
261
//                /* IGNORE HERE */
262
//            }
263
//        }
264
        return null;
265
    }
266

    
267
    /**
268
     * @param result
269
     */
270
    public static void handleDeleteresultInError(DeleteResult result, Session session) {
271
        String notificationTitle;
272
        StringBuffer messageBody = new StringBuffer();
273
        if (result.isAbort()) {
274
            notificationTitle = "The delete operation as abborded by the system.";
275
        } else {
276
            notificationTitle = "An error occured during the delete operation.";
277
        }
278
        if (!result.getExceptions().isEmpty()) {
279
            messageBody.append("<h3>").append("Exceptions:").append("</h3>").append("<ul>");
280
            result.getExceptions().forEach(e -> messageBody.append("<li>").append(e.getMessage()).append("</li>"));
281
            messageBody.append("</ul>");
282
            if(result.getExceptions().stream().anyMatch(e -> HibernateException.class.isAssignableFrom(e.getClass()))){
283
                session.clear(); // #7559
284
            }
285
        }
286
        if (!result.getRelatedObjects().isEmpty()) {
287
            messageBody.append("<h3>").append("Related objects exist:").append("</h3>").append("<ul>");
288
            result.getRelatedObjects().forEach(e -> {
289
                messageBody.append("<li>");
290
                if (IdentifiableEntity.class.isAssignableFrom(e.getClass())) {
291
                    messageBody.append(((IdentifiableEntity) e).getTitleCache());
292
                } else {
293
                    messageBody.append(e.toString());
294
                }
295
                messageBody.append("</li>");
296
            });
297

    
298
            messageBody.append("</ul>");
299
        }
300
        Notification notification = new Notification(notificationTitle, messageBody.toString(),
301
                com.vaadin.ui.Notification.Type.ERROR_MESSAGE, true);
302
        notification.show(UI.getCurrent().getPage());
303
    }
304

    
305

    
306
    protected void commitTransaction() {
307

    
308
//        if(conversationHolder != null){
309
//            conversationHolder.commit();
310
//        } else {
311
            repo.commitTransaction(txStatus);
312
            txStatus = null;
313
//        }
314
    }
315

    
316
    /**
317
     * @param entityId
318
     */
319
    public T loadBean(int entityId) {
320
//        conversationHolder.startTransaction();
321
        return service.find(entityId);
322
    }
323

    
324
    public S getService() {
325
        return service;
326
    }
327

    
328

    
329
}
(3-3/7)