Project

General

Profile

Download (46.3 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2007 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

    
10
package eu.etaxonomy.cdm.persistence.dao.hibernate.common;
11

    
12
import java.lang.reflect.Field;
13
import java.util.ArrayList;
14
import java.util.Collection;
15
import java.util.EnumSet;
16
import java.util.HashMap;
17
import java.util.HashSet;
18
import java.util.Iterator;
19
import java.util.List;
20
import java.util.Map;
21
import java.util.Set;
22
import java.util.UUID;
23

    
24
import org.apache.logging.log4j.LogManager;
25
import org.apache.logging.log4j.Logger;
26
import org.hibernate.Criteria;
27
import org.hibernate.FlushMode;
28
import org.hibernate.HibernateException;
29
import org.hibernate.LockOptions;
30
import org.hibernate.NonUniqueObjectException;
31
import org.hibernate.Session;
32
import org.hibernate.criterion.Criterion;
33
import org.hibernate.criterion.DetachedCriteria;
34
import org.hibernate.criterion.Disjunction;
35
import org.hibernate.criterion.Example;
36
import org.hibernate.criterion.Example.PropertySelector;
37
import org.hibernate.criterion.LogicalExpression;
38
import org.hibernate.criterion.Order;
39
import org.hibernate.criterion.ProjectionList;
40
import org.hibernate.criterion.Projections;
41
import org.hibernate.criterion.Restrictions;
42
import org.hibernate.criterion.Subqueries;
43
import org.hibernate.envers.AuditReader;
44
import org.hibernate.envers.AuditReaderFactory;
45
import org.hibernate.envers.query.AuditQuery;
46
import org.hibernate.metadata.ClassMetadata;
47
import org.hibernate.sql.JoinType;
48
import org.hibernate.type.Type;
49
import org.joda.time.DateTime;
50
import org.springframework.beans.factory.annotation.Autowired;
51
import org.springframework.dao.DataAccessException;
52
import org.springframework.dao.InvalidDataAccessApiUsageException;
53
import org.springframework.security.core.Authentication;
54
import org.springframework.security.core.context.SecurityContextHolder;
55
import org.springframework.util.ReflectionUtils;
56

    
57
import eu.etaxonomy.cdm.model.common.CdmBase;
58
import eu.etaxonomy.cdm.model.common.IPublishable;
59
import eu.etaxonomy.cdm.model.common.VersionableEntity;
60
import eu.etaxonomy.cdm.model.permission.User;
61
import eu.etaxonomy.cdm.model.view.AuditEvent;
62
import eu.etaxonomy.cdm.persistence.dao.common.ICdmEntityDao;
63
import eu.etaxonomy.cdm.persistence.dao.common.Restriction;
64
import eu.etaxonomy.cdm.persistence.dao.common.Restriction.Operator;
65
import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;
66
import eu.etaxonomy.cdm.persistence.dto.MergeResult;
67
import eu.etaxonomy.cdm.persistence.hibernate.PostMergeEntityListener;
68
import eu.etaxonomy.cdm.persistence.hibernate.replace.ReferringObjectMetadata;
69
import eu.etaxonomy.cdm.persistence.hibernate.replace.ReferringObjectMetadataFactory;
70
import eu.etaxonomy.cdm.persistence.query.Grouping;
71
import eu.etaxonomy.cdm.persistence.query.MatchMode;
72
import eu.etaxonomy.cdm.persistence.query.OrderHint;
73

    
74
/**
75
 * Hibernate implementation for {@link ICdmEntityDao}.
76
 */
77
public abstract class CdmEntityDaoBase<T extends CdmBase>
78
        extends DaoBase
79
        implements ICdmEntityDao<T> {
80

    
81
    private static final Logger logger = LogManager.getLogger(CdmEntityDaoBase.class);
82

    
83
    protected int flushAfterNo = 1000; // large numbers may cause synchronisation errors
84
                                        // when commiting the session
85
    protected Class<T> type;
86

    
87
    @Autowired
88
    // @Qualifier("defaultBeanInitializer")
89
    protected IBeanInitializer defaultBeanInitializer;
90

    
91
    public void setDefaultBeanInitializer(IBeanInitializer defaultBeanInitializer) {
92
        this.defaultBeanInitializer = defaultBeanInitializer;
93
    }
94

    
95
    @Autowired
96
    private ReferringObjectMetadataFactory referringObjectMetadataFactory;
97

    
98
    protected static final EnumSet<Operator> LEFTOUTER_OPS = EnumSet.of(Operator.AND_NOT, Operator.OR, Operator.OR_NOT);
99

    
100
    public CdmEntityDaoBase(Class<T> type) {
101
        this.type = type;
102
        assert type != null;
103
        logger.debug("Creating DAO of type [" + type.getSimpleName() + "]");
104
    }
105

    
106
    @Override
107
    public void lock(T t, LockOptions lockOptions) {
108
        getSession().buildLockRequest(lockOptions).lock(t);
109
    }
110

    
111
    @Override
112
    public void refresh(T t, LockOptions lockOptions, List<String> propertyPaths) {
113
        getSession().refresh(t, lockOptions);
114
        defaultBeanInitializer.initialize(t, propertyPaths);
115
    }
116

    
117
    // TODO this method should be moved to a concrete class (not typed)
118
    public UUID saveCdmObj(CdmBase cdmObj) throws DataAccessException {
119
        getSession().saveOrUpdate(cdmObj);
120
        return cdmObj.getUuid();
121
    }
122

    
123
    // TODO: Replace saveCdmObj() by saveCdmObject_
124
    private UUID saveCdmObject_(T cdmObj) {
125
        getSession().saveOrUpdate(cdmObj);
126
        return cdmObj.getUuid();
127
    }
128

    
129
    // TODO: Use everywhere CdmEntityDaoBase.saveAll() instead of
130
    // ServiceBase.saveCdmObjectAll()?
131
    // TODO: why does this use saveCdmObject_ which actually savesOrUpdateds
132
    // data ?
133
    @Override
134
    public Map<UUID, T> saveAll(Collection<? extends T> cdmObjCollection) {
135
        int types = cdmObjCollection.getClass().getTypeParameters().length;
136
        if (types > 0) {
137
            if (logger.isDebugEnabled()) {
138
                logger.debug("ClassType: + " + cdmObjCollection.getClass().getTypeParameters()[0]);
139
            }
140
        }
141

    
142
        Map<UUID, T> resultMap = new HashMap<>();
143
        Iterator<? extends T> iterator = cdmObjCollection.iterator();
144
        int i = 0;
145
        while (iterator.hasNext()) {
146
            if (((i % 2000) == 0) && (i > 0)) {
147
                logger.debug("Saved " + i + " objects");
148
            }
149
            T cdmObj = iterator.next();
150
            UUID uuid = saveCdmObject_(cdmObj);
151
            if (logger.isDebugEnabled()) {
152
                logger.debug("Save cdmObj: " + (cdmObj == null ? null : cdmObj.toString()));
153
            }
154
            resultMap.put(uuid, cdmObj);
155
            i++;
156
            if ((i % flushAfterNo) == 0) {
157
                try {
158
                    if (logger.isDebugEnabled()) {
159
                        logger.debug("flush");
160
                    }
161
                    flush();
162
                } catch (Exception e) {
163
                    logger.error("An exception occurred when trying to flush data");
164
                    e.printStackTrace();
165
                    throw new RuntimeException(e);
166
                }
167
            }
168
        }
169

    
170
        if (logger.isInfoEnabled()) {
171
            logger.info("Saved " + i + " objects");
172
        }
173
        return resultMap;
174
    }
175

    
176
    private UUID saveOrUpdateCdmObject(T cdmObj) {
177
        getSession().saveOrUpdate(cdmObj);
178
        return cdmObj.getUuid();
179
    }
180

    
181
    @Override
182
    public Map<UUID, T> saveOrUpdateAll(Collection<T> cdmObjCollection) {
183
        int types = cdmObjCollection.getClass().getTypeParameters().length;
184
        if (types > 0) {
185
            if (logger.isDebugEnabled()) {
186
                logger.debug("ClassType: + " + cdmObjCollection.getClass().getTypeParameters()[0]);
187
            }
188
        }
189

    
190
        Map<UUID, T> resultMap = new HashMap<>();
191
        Iterator<T> iterator = cdmObjCollection.iterator();
192
        int i = 0;
193
        while (iterator.hasNext()) {
194
            if (((i % 2000) == 0) && (i > 0)) {
195
                logger.debug("Saved " + i + " objects");
196
            }
197
            T cdmObj = iterator.next();
198
            UUID uuid = saveOrUpdateCdmObject(cdmObj);
199
            if (logger.isDebugEnabled()) {
200
                logger.debug("Save cdmObj: " + (cdmObj == null ? null : cdmObj.toString()));
201
            }
202
            resultMap.put(uuid, cdmObj);
203
            i++;
204
            if ((i % flushAfterNo) == 0) {
205
                try {
206
                    if (logger.isDebugEnabled()) {
207
                        logger.debug("flush");
208
                    }
209
                    flush();
210
                } catch (Exception e) {
211
                    logger.error("An exception occurred when trying to flush data");
212
                    e.printStackTrace();
213
                    throw new RuntimeException(e);
214
                }
215
            }
216
        }
217

    
218
        if (logger.isInfoEnabled()) {
219
            logger.info("Saved " + i + " objects");
220
        }
221
        return resultMap;
222
    }
223

    
224
    @Override
225
    public T replace(T x, T y) {
226
        if (x.equals(y)) {
227
            return y;
228
        }
229

    
230
        Class<?> commonClass = x.getClass();
231
        if (y != null) {
232
            while (!commonClass.isAssignableFrom(y.getClass())) {
233
                if (commonClass.equals(type)) {
234
                    throw new RuntimeException();
235
                }
236
                commonClass = commonClass.getSuperclass();
237
            }
238
        }
239

    
240
        getSession().merge(x);
241

    
242
        Set<ReferringObjectMetadata> referringObjectMetas = referringObjectMetadataFactory.get(x.getClass());
243

    
244
        for (ReferringObjectMetadata referringObjectMetadata : referringObjectMetas) {
245

    
246
            List<CdmBase> referringObjects = referringObjectMetadata.getReferringObjects(x, getSession());
247

    
248
            for (CdmBase referringObject : referringObjects) {
249
                try {
250
                    referringObjectMetadata.replace(referringObject, x, y);
251
                    getSession().update(referringObject);
252

    
253
                } catch (IllegalArgumentException e) {
254
                    throw new RuntimeException(e.getMessage(), e);
255
                } catch (IllegalAccessException e) {
256
                    throw new RuntimeException(e.getMessage(), e);
257
                }
258
            }
259
        }
260
        return y;
261
    }
262

    
263
    @Override
264
    public Session getSession() throws DataAccessException {
265
        return super.getSession();
266
    }
267

    
268
    @Override
269
    public void clear() throws DataAccessException {
270
        Session session = getSession();
271
        session.clear();
272
        if (logger.isDebugEnabled()) {
273
            logger.debug("dao clear end");
274
        }
275
    }
276

    
277
    @Override
278
    public MergeResult<T> merge(T transientObject, boolean returnTransientEntity) throws DataAccessException {
279
        Session session = getSession();
280
        PostMergeEntityListener.addSession(session);
281
        MergeResult<T> result = null;
282
        try {
283
            @SuppressWarnings("unchecked")
284
            T persistentObject = (T) session.merge(transientObject);
285
            if (logger.isDebugEnabled()) {
286
                logger.debug("dao merge end");
287
            }
288

    
289
            if (returnTransientEntity) {
290
                if (transientObject != null && persistentObject != null) {
291
                    transientObject.setId(persistentObject.getId());
292
                }
293
                result = new MergeResult(transientObject, PostMergeEntityListener.getNewEntities(session));
294
            } else {
295
                result = new MergeResult(persistentObject, null);
296
            }
297
            return result;
298
        } finally {
299
            PostMergeEntityListener.removeSession(session);
300
        }
301
    }
302

    
303
    @Override
304
    public T merge(T transientObject) throws DataAccessException {
305
        Session session = getSession();
306
        @SuppressWarnings("unchecked")
307
        T persistentObject = (T) session.merge(transientObject);
308
        if (logger.isDebugEnabled()) {
309
            logger.debug("dao merge end");
310
        }
311
        return persistentObject;
312
    }
313

    
314
    @Override
315
    public UUID saveOrUpdate(T transientObject) throws DataAccessException {
316
        if (transientObject == null) {
317
            logger.warn("Object to save should not be null. NOP");
318
            return null;
319
        }
320
        try {
321
            if (logger.isDebugEnabled()) {
322
                logger.debug("dao saveOrUpdate start...");
323
            }
324
            if (logger.isDebugEnabled()) {
325
                logger.debug("transientObject(" + transientObject.getClass().getSimpleName() + ") ID:"
326
                        + transientObject.getId() + ", UUID: " + transientObject.getUuid());
327
            }
328
            Session session = getSession();
329
            if (transientObject.getId() != 0 && VersionableEntity.class.isAssignableFrom(transientObject.getClass())) {
330
                VersionableEntity versionableEntity = (VersionableEntity) transientObject;
331
                versionableEntity.setUpdated(new DateTime());
332
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
333
                if (authentication != null && authentication.getPrincipal() != null
334
                        && authentication.getPrincipal() instanceof User) {
335
                    User user = (User) authentication.getPrincipal();
336
                    versionableEntity.setUpdatedBy(user);
337
                }
338
            }
339
            session.saveOrUpdate(transientObject);
340
            if (logger.isDebugEnabled()) {
341
                logger.debug("dao saveOrUpdate end");
342
            }
343
            return transientObject.getUuid();
344
        } catch (NonUniqueObjectException e) {
345
            logger.error("Error in CdmEntityDaoBase.saveOrUpdate(obj). ID=" + e.getIdentifier() + ". Class="
346
                    + e.getEntityName());
347
            logger.error(e.getMessage());
348

    
349
            e.printStackTrace();
350
            throw e;
351
        } catch (HibernateException e) {
352

    
353
            e.printStackTrace();
354
            throw e;
355
        }
356
    }
357

    
358
    @Override
359
    public <S extends T> S save(S newInstance) throws DataAccessException {
360
        if (newInstance == null) {
361
            logger.warn("Object to save should not be null. NOP");
362
            return null;
363
        }
364
        getSession().save(newInstance);
365
        return newInstance;
366
    }
367

    
368
    @Override
369
    public UUID update(T transientObject) throws DataAccessException {
370
        if (transientObject == null) {
371
            logger.warn("Object to update should not be null. NOP");
372
            return null;
373
        }
374
        getSession().update(transientObject);
375
        return transientObject.getUuid();
376
    }
377

    
378
    @Override
379
    public UUID refresh(T persistentObject) throws DataAccessException {
380
        getSession().refresh(persistentObject);
381
        return persistentObject.getUuid();
382
    }
383

    
384
    @Override
385
    public UUID delete(T persistentObject) throws DataAccessException {
386
        if (persistentObject == null) {
387
            logger.warn(type.getName() + " was 'null'");
388
            return null;
389
        }
390

    
391
        // Merge the object in if it is detached
392
        //
393
        // I think this is preferable to catching lazy initialization errors
394
        // as that solution only swallows and hides the exception, but doesn't
395
        // actually solve it.
396
        persistentObject = (T) getSession().merge(persistentObject);
397
        getSession().delete(persistentObject);
398
        return persistentObject.getUuid();
399
    }
400

    
401
    @Override
402
    public T findById(int id) throws DataAccessException {
403
        return getSession().get(type, id);
404
    }
405

    
406
    @Override
407
    public T findByUuid(UUID uuid) throws DataAccessException {
408
        return this.findByUuid(uuid, INCLUDE_UNPUBLISHED);
409
    }
410

    
411
    protected T findByUuid(UUID uuid, boolean includeUnpublished) throws DataAccessException {
412
        Session session = getSession();
413
        Criteria crit = session.createCriteria(type);
414
        crit.add(Restrictions.eq("uuid", uuid));
415
        crit.addOrder(Order.desc("created"));
416
        if (IPublishable.class.isAssignableFrom(type) && !includeUnpublished) {
417
            crit.add(Restrictions.eq("publish", Boolean.TRUE));
418
        }
419

    
420
        @SuppressWarnings("unchecked")
421
        List<T> results = crit.list();
422
        Set<T> resultSet = new HashSet<>();
423
        resultSet.addAll(results);
424
        if (resultSet.isEmpty()) {
425
            return null;
426
        } else {
427
            if (resultSet.size() > 1) {
428
                logger.error("findByUuid() delivers more than one result for UUID: " + uuid);
429
            }
430
            return results.get(0);
431
        }
432
    }
433

    
434
    @Override
435
    public T findByUuidWithoutFlush(UUID uuid) throws DataAccessException {
436
        Session session = getSession();
437
        FlushMode currentFlushMode = session.getHibernateFlushMode();
438
        try {
439
            // set flush mode to manual so that the session does not flush
440
            // when before performing the query
441
            session.setHibernateFlushMode(FlushMode.MANUAL);
442
            Criteria crit = session.createCriteria(type);
443
            crit.add(Restrictions.eq("uuid", uuid));
444
            crit.addOrder(Order.desc("created"));
445
            @SuppressWarnings("unchecked")
446
            List<T> results = crit.list();
447
            if (results.isEmpty()) {
448
                return null;
449
            } else {
450
                if (results.size() > 1) {
451
                    logger.error("findByUuid() delivers more than one result for UUID: " + uuid);
452
                }
453
                return results.get(0);
454
            }
455
        } finally {
456
            // set back the session flush mode
457
            if (currentFlushMode != null) {
458
                session.setHibernateFlushMode(currentFlushMode);
459
            }
460
        }
461
    }
462

    
463
    @Override
464
    public List<T> loadList(Collection<Integer> ids, List<OrderHint> orderHints, List<String> propertyPaths) throws DataAccessException {
465

    
466
        if (ids.isEmpty()) {
467
            return new ArrayList<>(0);
468
        }
469

    
470
        Criteria criteria = prepareList(null, ids, null, null, orderHints, "id");
471

    
472
        if (logger.isDebugEnabled()) {
473
            logger.debug(criteria.toString());
474
        }
475

    
476
        @SuppressWarnings("unchecked")
477
        List<T> result = criteria.list();
478
        defaultBeanInitializer.initializeAll(result, propertyPaths);
479
        return result;
480
    }
481

    
482
    @Override
483
    public List<T> list(Collection<UUID> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
484
            List<String> propertyPaths) throws DataAccessException {
485

    
486
        if (uuids == null || uuids.isEmpty()) {
487
            return new ArrayList<>();
488
        }
489

    
490
        Criteria criteria = prepareList(null, uuids, pageSize, pageNumber, orderHints, "uuid");
491
        @SuppressWarnings("unchecked")
492
        List<T> result = criteria.list();
493
        defaultBeanInitializer.initializeAll(result, propertyPaths);
494
        return result;
495
    }
496

    
497
    @Override
498
    public <S extends T> List<S> list(Class<S> clazz, Collection<UUID> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
499
            List<String> propertyPaths) throws DataAccessException {
500

    
501
        if (uuids == null || uuids.isEmpty()) {
502
            return new ArrayList<>();
503
        }
504

    
505
        Criteria criteria = prepareList(clazz, uuids, pageSize, pageNumber, orderHints, "uuid");
506
        @SuppressWarnings("unchecked")
507
        List<S> result = criteria.list();
508
        defaultBeanInitializer.initializeAll(result, propertyPaths);
509
        return result;
510
    }
511

    
512
    @Override
513
    public <S extends T> List<S> list(Class<S> type, List<Restriction<?>> restrictions, Integer limit, Integer start,
514
            List<OrderHint> orderHints, List<String> propertyPaths) {
515

    
516
        Criteria criteria = createCriteria(type, restrictions, false);
517

    
518
        addLimitAndStart(criteria, limit, start);
519
        addOrder(criteria, orderHints);
520

    
521
        @SuppressWarnings("unchecked")
522
        List<S> result = criteria.list();
523
        defaultBeanInitializer.initializeAll(result, propertyPaths);
524
        return result;
525
    }
526

    
527
    /**
528
     * @param restrictions
529
     * @param criteria
530
     */
531
    private void addRestrictions(List<Restriction<?>> restrictions, DetachedCriteria criteria) {
532

    
533
        if(restrictions == null || restrictions.isEmpty()){
534
            return ;
535
        }
536

    
537
        List<CriterionWithOperator> perProperty = new ArrayList<>(restrictions.size());
538
        Map<String, String> aliases = new HashMap<>();
539

    
540

    
541

    
542
        for(Restriction<?> restriction : restrictions){
543
            Collection<? extends Object> values = restriction.getValues();
544
            JoinType jointype = LEFTOUTER_OPS.contains(restriction.getOperator()) ? JoinType.LEFT_OUTER_JOIN : JoinType.INNER_JOIN;
545
            if(values != null && !values.isEmpty()){
546
                // ---
547
                String propertyPath = restriction.getPropertyName();
548
                String[] props =  propertyPath.split("\\.");
549
                String propertyName;
550
                if(props.length == 1){
551
                    // direct property of the base type of the criteria
552
                    propertyName = propertyPath;
553
                } else {
554
                    // create aliases if the propertyName is a dot separated property path
555
                    String aĺiasKey = jointype.name() + "_";
556
                    String aliasedProperty = null;
557
                    String alias = "";
558
                    for(int p = 0; p < props.length -1; p++){
559
                        aĺiasKey = aĺiasKey + (aĺiasKey.isEmpty() ? "" : ".") + props[p];
560
                        aliasedProperty = alias + (alias.isEmpty() ? "" : ".") + props[p];
561
                        if(!aliases.containsKey(aliasedProperty)){
562
                            alias = alias + (alias.isEmpty() ? "" : "_" ) + props[p];
563
                            aliases.put(aĺiasKey, alias);
564
                            criteria.createAlias(aliasedProperty, alias, jointype);
565
                            if(logger.isDebugEnabled()){
566
                                logger.debug("addRestrictions() alias created with aliasKey " + aĺiasKey + " => " + aliasedProperty + " as " + alias);
567
                            }
568
                        }
569
                    }
570
                    propertyName = alias + "." + props[props.length -1];
571
                }
572
                // ---
573
                Criterion[] predicates = new Criterion[values.size()];
574
                int i = 0;
575
                for(Object v : values){
576
                    Criterion criterion = createRestriction(propertyName, v, restriction.getMatchMode());
577
                    if(restriction.isNot()){
578
                        if(props.length > 1){
579
                            criterion = Restrictions.or(Restrictions.not(criterion), Restrictions.isNull(propertyName));
580
                        } else {
581
                            criterion = Restrictions.not(criterion);
582
                        }
583
                    }
584
                    predicates[i++] = criterion;
585
                    if(logger.isDebugEnabled()){
586
                        logger.debug("addRestrictions() predicate with " + propertyName + " " + (restriction.getMatchMode() == null ? "=" : restriction.getMatchMode().name()) + " " + v.toString());
587
                    }
588
                }
589
                if(restriction.getOperator() == Operator.AND_NOT){
590
                    perProperty.add(new CriterionWithOperator(restriction.getOperator(), Restrictions.and(predicates)));
591
                } else {
592
                    perProperty.add(new CriterionWithOperator(restriction.getOperator(), Restrictions.or(predicates)));
593
                }
594
            } // check has values
595
        } // loop over restrictions
596

    
597
        Restriction.Operator firstOperator = null;
598
        if(!perProperty.isEmpty()){
599
            LogicalExpression logicalExpression = null;
600
            for(CriterionWithOperator cwo : perProperty){
601
                if(logicalExpression == null){
602
                    firstOperator = cwo.operator;
603
                    logicalExpression = Restrictions.and(Restrictions.sqlRestriction("1=1"), cwo.criterion);
604
                } else {
605
                    switch(cwo.operator){
606
                        case AND:
607
                        case AND_NOT:
608
                            logicalExpression = Restrictions.and(logicalExpression, cwo.criterion);
609
                            break;
610
                        case OR:
611
                        case OR_NOT:
612
                            logicalExpression = Restrictions.or(logicalExpression, cwo.criterion);
613
                            break;
614
                        default:
615
                            throw new RuntimeException("Unsupported Operator");
616
                    }
617
                }
618

    
619
            }
620

    
621

    
622
            criteria.add(logicalExpression);
623
//            if(firstOperator == Operator.OR){
624
//                // OR
625
//            } else {
626
//                // AND
627
//                criteria.add(Restrictions.and(queryStringCriterion, logicalExpression));
628
//            }
629
        }
630
        if(logger.isDebugEnabled()){
631
            logger.debug("addRestrictions() final criteria: " + criteria.toString());
632
        }
633
    }
634

    
635
    /**
636
     * @param propertyName
637
     * @param value
638
     * @param matchMode
639
     *            is only applied if the <code>value</code> is a
640
     *            <code>String</code> object
641
     * @param criteria
642
     * @return
643
     */
644
    private Criterion createRestriction(String propertyName, Object value, MatchMode matchMode) {
645

    
646
        Criterion restriction;
647
        if (value == null) {
648
            if (logger.isDebugEnabled()) {
649
                logger.debug("createRestriction() " + propertyName + " is null ");
650
            }
651
            restriction = Restrictions.isNull(propertyName);
652
        } else if (matchMode == null || !(value instanceof String)) {
653
            if (logger.isDebugEnabled()) {
654
                logger.debug("createRestriction() " + propertyName + " = " + value.toString());
655
            }
656
            restriction = Restrictions.eq(propertyName, value);
657
        } else {
658
            String queryString = (String) value;
659
            if (logger.isDebugEnabled()) {
660
                logger.debug("createRestriction() " + propertyName + " " + matchMode.getMatchOperator() + " "
661
                        + matchMode.queryStringFrom(queryString));
662
            }
663
            switch(matchMode){
664
            case BEGINNING:
665
                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.START);
666
                break;
667
            case END:
668
                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.END);
669
                break;
670
            case LIKE:
671
                restriction = Restrictions.ilike(propertyName, matchMode.queryStringFrom(queryString), org.hibernate.criterion.MatchMode.ANYWHERE);
672
                break;
673
            case EXACT:
674
                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.EXACT);
675
                break;
676
            case ANYWHERE:
677
                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.ANYWHERE);
678
                break;
679
            default:
680
                throw new RuntimeException("Unknown MatchMode: " + matchMode.name());
681
            }
682
        }
683
        return restriction;
684
    }
685

    
686
    /**
687
     * {@inheritDoc}
688
     */
689
    @Override
690
    public long count(Class<? extends T> type, List<Restriction<?>> restrictions) {
691

    
692
        Criteria criteria = createCriteria(type, restrictions, false);
693

    
694
        criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
695

    
696
        return (Long) criteria.uniqueResult();
697

    
698
    }
699

    
700
    /**
701
     * @param uuids
702
     * @param pageSize
703
     * @param pageNumber
704
     * @param orderHints
705
     * @param propertyName
706
     * @return
707
     */
708
    private Criteria prepareList(Class<? extends T> clazz, Collection<?> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
709
            String propertyName) {
710
        if (clazz == null){
711
            clazz = type;
712
        }
713
        Criteria criteria = getSession().createCriteria(clazz);
714
        criteria.add(Restrictions.in(propertyName, uuids));
715

    
716
        if (pageSize != null) {
717
            criteria.setMaxResults(pageSize);
718
            if (pageNumber != null) {
719
                criteria.setFirstResult(pageNumber * pageSize);
720
            } else {
721
                criteria.setFirstResult(0);
722
            }
723
        }
724

    
725
        if (orderHints == null) {
726
            orderHints = OrderHint.defaultOrderHintsFor(type);
727
        }
728
        addOrder(criteria, orderHints);
729
        return criteria;
730
    }
731

    
732
    private Criteria criterionForType(Class<? extends T> clazz) {
733
        return  getSession().createCriteria(entityType(clazz));
734
    }
735

    
736
    protected Class<? extends T> entityType(Class<? extends T> clazz){
737
        if (clazz != null) {
738
            return clazz;
739
        } else {
740
            return type;
741
        }
742
    }
743

    
744
    @Override
745
    public T load(UUID uuid) {
746
        T bean = findByUuid(uuid);
747
        if (bean == null) {
748
            return null;
749
        }
750
        defaultBeanInitializer.load(bean);
751

    
752
        return bean;
753
    }
754

    
755
    @Override
756
    public T load(int id, List<String> propertyPaths) {
757
        T bean = findById(id);
758
        if (bean == null) {
759
            return bean;
760
        }
761
        defaultBeanInitializer.initialize(bean, propertyPaths);
762

    
763
        return bean;
764
    }
765

    
766
    @Override
767
    public T loadWithoutInitializing(int id){
768
        return this.getSession().load(type, id);
769
    }
770

    
771
    @Override
772
    public T load(UUID uuid, List<String> propertyPaths) {
773
        return this.load(uuid, INCLUDE_UNPUBLISHED, propertyPaths);
774
    }
775

    
776
    protected T load(UUID uuid, boolean includeUnpublished, List<String> propertyPaths) {
777
        T bean = findByUuid(uuid, includeUnpublished);
778
        if (bean == null) {
779
            return bean;
780
        }
781
        defaultBeanInitializer.initialize(bean, propertyPaths);
782

    
783
        return bean;
784
    }
785

    
786
    @Override
787
    public Boolean exists(UUID uuid) {
788
        if (findByUuid(uuid) == null) {
789
            return false;
790
        }
791
        return true;
792
    }
793

    
794
    @Override
795
    public long count() {
796
        return count(type);
797
    }
798

    
799
    @Override
800
    public long count(Class<? extends T> clazz) {
801
        Session session = getSession();
802
        Criteria criteria = null;
803
        if (clazz == null) {
804
            criteria = session.createCriteria(type);
805
        } else {
806
            criteria = session.createCriteria(clazz);
807
        }
808
        criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
809

    
810
        // since hibernate 4 (or so) uniqueResult returns Long, not Integer,
811
        // therefore needs
812
        // to be casted. Think about returning long rather then int!
813
        return (long) criteria.uniqueResult();
814
    }
815

    
816
    @Override
817
    public List<T> list(Integer limit, Integer start) {
818
        return list(limit, start, null);
819
    }
820

    
821
    @Override
822
    public List<Object[]> group(Class<? extends T> clazz, Integer limit, Integer start, List<Grouping> groups,
823
            List<String> propertyPaths) {
824

    
825
        Criteria criteria = null;
826
        criteria = criterionForType(clazz);
827

    
828
        addGroups(criteria, groups);
829

    
830
        if (limit != null) {
831
            criteria.setFirstResult(start);
832
            criteria.setMaxResults(limit);
833
        }
834

    
835
        @SuppressWarnings("unchecked")
836
        List<Object[]> result = criteria.list();
837

    
838
        if (propertyPaths != null && !propertyPaths.isEmpty()) {
839
            for (Object[] objects : result) {
840
                defaultBeanInitializer.initialize(objects[0], propertyPaths);
841
            }
842
        }
843

    
844
        return result;
845
    }
846

    
847
    protected void countGroups(DetachedCriteria criteria, List<Grouping> groups) {
848
        if (groups != null) {
849

    
850
            Map<String, String> aliases = new HashMap<String, String>();
851

    
852
            for (Grouping grouping : groups) {
853
                if (grouping.getAssociatedObj() != null) {
854
                    String alias = null;
855
                    if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
856
                        alias = grouping.getAssociatedObjectAlias();
857
                        aliases.put(grouping.getAssociatedObj(), alias);
858
                        criteria.createAlias(grouping.getAssociatedObj(), alias);
859
                    }
860
                }
861
            }
862

    
863
            ProjectionList projectionList = Projections.projectionList();
864

    
865
            for (Grouping grouping : groups) {
866
                grouping.addProjection(projectionList);
867
            }
868
            criteria.setProjection(projectionList);
869
        }
870
    }
871

    
872
    protected void addGroups(Criteria criteria, List<Grouping> groups) {
873
        if (groups != null) {
874

    
875
            Map<String, String> aliases = new HashMap<String, String>();
876

    
877
            for (Grouping grouping : groups) {
878
                if (grouping.getAssociatedObj() != null) {
879
                    String alias = null;
880
                    if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
881
                        alias = grouping.getAssociatedObjectAlias();
882
                        aliases.put(grouping.getAssociatedObj(), alias);
883
                        criteria.createAlias(grouping.getAssociatedObj(), alias);
884
                    }
885
                }
886
            }
887

    
888
            ProjectionList projectionList = Projections.projectionList();
889

    
890
            for (Grouping grouping : groups) {
891
                grouping.addProjection(projectionList);
892
            }
893
            criteria.setProjection(projectionList);
894

    
895
            for (Grouping grouping : groups) {
896
                grouping.addOrder(criteria);
897

    
898
            }
899
        }
900
    }
901

    
902
    @Override
903
    public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints) {
904
        return list(limit, start, orderHints, null);
905
    }
906

    
907
    @Override
908
    public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
909
        Criteria criteria = getSession().createCriteria(type);
910
        if (limit != null) {
911
            criteria.setFirstResult(start);
912
            criteria.setMaxResults(limit);
913
        }
914

    
915
        addOrder(criteria, orderHints);
916
        @SuppressWarnings("unchecked")
917
        List<T> results = criteria.list();
918

    
919
        defaultBeanInitializer.initializeAll(results, propertyPaths);
920
        return results;
921
    }
922

    
923
    @Override
924
    public <S extends T> List<S> list(Class<S> clazz, Integer limit, Integer start, List<OrderHint> orderHints,
925
            List<String> propertyPaths) {
926
        Criteria criteria = null;
927
        if (clazz == null) {
928
            criteria = getSession().createCriteria(type);
929
        } else {
930
            criteria = getSession().createCriteria(clazz);
931
        }
932

    
933
        addLimitAndStart(criteria, limit, start);
934

    
935
        addOrder(criteria, orderHints);
936

    
937
        @SuppressWarnings("unchecked")
938
        List<S> results = criteria.list();
939

    
940
        defaultBeanInitializer.initializeAll(results, propertyPaths);
941
        return results;
942
    }
943

    
944
    public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start, List<OrderHint> orderHints) {
945
        return list(type, limit, start, orderHints, null);
946
    }
947

    
948
    @Override
949
    public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start) {
950
        return list(type, limit, start, null, null);
951
    }
952

    
953
    @Override
954
    public Class<T> getType() {
955
        return type;
956
    }
957

    
958
    @Override
959
    public long count(T example, Set<String> includeProperties) {
960
        Criteria criteria = getSession().createCriteria(example.getClass());
961
        addExample(criteria, example, includeProperties);
962

    
963
        criteria.setProjection(Projections.rowCount());
964
        return (Long) criteria.uniqueResult();
965
    }
966

    
967
    protected void addExample(Criteria criteria, T example, Set<String> includeProperties) {
968
        if (includeProperties != null && !includeProperties.isEmpty()) {
969
            criteria.add(Example.create(example).setPropertySelector(new PropertySelectorImpl(includeProperties)));
970
            ClassMetadata classMetadata = getSession().getSessionFactory().getClassMetadata(example.getClass());
971
            for (String property : includeProperties) {
972
                Type type = classMetadata.getPropertyType(property);
973
                if (type.isEntityType()) {
974
                    try {
975
                        Field field = ReflectionUtils.findField(example.getClass(), property);
976
                        field.setAccessible(true);
977
                        Object value = field.get(example);
978
                        if (value != null) {
979
                            criteria.add(Restrictions.eq(property, value));
980
                        } else {
981
                            criteria.add(Restrictions.isNull(property));
982
                        }
983
                    } catch (SecurityException se) {
984
                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
985
                                se);
986
                    } catch (HibernateException he) {
987
                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
988
                                he);
989
                    } catch (IllegalArgumentException iae) {
990
                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
991
                                iae);
992
                    } catch (IllegalAccessException ie) {
993
                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
994
                                ie);
995
                    }
996
                }
997
            }
998
        } else {
999
            criteria.add(Example.create(example));
1000
        }
1001
    }
1002

    
1003
    /**
1004
     *
1005
     * NOTE: We can't reuse
1006
     * {@link #list(Class, String, Object, MatchMode, Integer, Integer, List, List)
1007
     * here due to different default behavior of the <code>matchmode</code>
1008
     * parameter.
1009
     *
1010
     * @param clazz
1011
     * @param param
1012
     * @param queryString
1013
     * @param matchmode
1014
     * @param criterion
1015
     * @param pageSize
1016
     * @param pageNumber
1017
     * @param orderHints
1018
     * @param propertyPaths
1019
     * @return
1020
     */
1021
    @Override
1022
    public <S extends T> List<S> findByParam(Class<S> clazz, String param, String queryString, MatchMode matchmode,
1023
            List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
1024
            List<String> propertyPaths) {
1025
        Set<String> stringSet = new HashSet<>();
1026
        stringSet.add(param);
1027
        return this.findByParam(clazz, stringSet, queryString, matchmode,
1028
                criterion, pageSize, pageNumber, orderHints,
1029
                propertyPaths);
1030
    }
1031

    
1032
    @Override
1033
    public <S extends T> List<S> findByParam(Class<S> clazz, Set<String> params, String queryString, MatchMode matchmode,
1034
            List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
1035
            List<String> propertyPaths) {
1036

    
1037
        Criteria criteria = criterionForType(clazz);
1038

    
1039
        if (queryString != null) {
1040
            Set<Criterion> criterions = new HashSet<>();
1041
            for (String param: params){
1042
                Criterion crit;
1043
                if (matchmode == null) {
1044
                     crit = Restrictions.ilike(param, queryString);
1045
                } else if (matchmode == MatchMode.BEGINNING) {
1046
                     crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START);
1047
                } else if (matchmode == MatchMode.END) {
1048
                    crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END);
1049
                } else if (matchmode == MatchMode.EXACT) {
1050
                    crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT);
1051
                } else {
1052
                    crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE);
1053
                }
1054
                criterions.add(crit);
1055
            }
1056
            if (criterions.size()>1){
1057
                Iterator<Criterion> critIterator = criterions.iterator();
1058
                Disjunction disjunction = Restrictions.disjunction();
1059
                while (critIterator.hasNext()){
1060
                    disjunction.add(critIterator.next());
1061
                }
1062
                criteria.add(disjunction);
1063
            }else{
1064
                if (!criterions.isEmpty()){
1065
                    criteria.add(criterions.iterator().next());
1066
                }
1067
            }
1068
        }
1069

    
1070
        addCriteria(criteria, criterion);
1071

    
1072
        addPageSizeAndNumber(criteria, pageSize, pageNumber);
1073
        addOrder(criteria, orderHints);
1074

    
1075
        @SuppressWarnings("unchecked")
1076
        List<S> result = criteria.list();
1077
        defaultBeanInitializer.initializeAll(result, propertyPaths);
1078
        return result;
1079
    }
1080

    
1081
    @Override
1082
    public long countByParam(Class<? extends T> clazz, String param, String queryString, MatchMode matchmode,
1083
            List<Criterion> criterion) {
1084

    
1085
        Criteria criteria = null;
1086

    
1087
        criteria = criterionForType(clazz);
1088

    
1089
        if (queryString != null) {
1090
            if (matchmode == null) {
1091
                criteria.add(Restrictions.ilike(param, queryString));
1092
            } else if (matchmode == MatchMode.BEGINNING) {
1093
                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START));
1094
            } else if (matchmode == MatchMode.END) {
1095
                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END));
1096
            } else if (matchmode == MatchMode.EXACT) {
1097
                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT));
1098
            } else {
1099
                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE));
1100
            }
1101
        }
1102

    
1103
        addCriteria(criteria, criterion);
1104

    
1105
        criteria.setProjection(Projections.rowCount());
1106

    
1107
        return (Long) criteria.uniqueResult();
1108
    }
1109

    
1110
    /**
1111
     * Creates a criteria query for the CDM <code>type</code> either for counting or listing matching entities.
1112
     * <p>
1113
     * The set of matching entities can be restricted by passing a list  of {@link Restriction} objects.
1114
     * Restrictions can logically combined:
1115
     <pre>
1116
       Arrays.asList(
1117
           new Restriction<String>("titleCache", MatchMode.ANYWHERE, "foo"),
1118
           new Restriction<String>("institute.name", Operator.OR, MatchMode.BEGINNING, "Bar")
1119
       );
1120
     </pre>
1121
     * The first Restriction in the example above by default has the <code>Operator.AND</code> which will be
1122
     * ignored since this is the first restriction. The <code>Operator</code> of further restrictions in the
1123
     * list are used to combine with the previous restriction.
1124
     *
1125
     * @param type
1126
     * @param restrictions
1127
     * @param doCount
1128
     * @return
1129
     */
1130
    protected Criteria createCriteria(Class<? extends T> type, List<Restriction<?>> restrictions, boolean doCount) {
1131

    
1132
        DetachedCriteria idsOnlyCriteria = DetachedCriteria.forClass(entityType(type));
1133
        idsOnlyCriteria.setProjection(Projections.distinct(Projections.id()));
1134

    
1135
        addRestrictions(restrictions, idsOnlyCriteria);
1136

    
1137
        Criteria criteria = criterionForType(type);
1138
        criteria.add(Subqueries.propertyIn("id", idsOnlyCriteria));
1139

    
1140
        if(doCount){
1141
            criteria.setProjection(Projections.rowCount());
1142
        } else {
1143
            idsOnlyCriteria.setProjection(Projections.distinct(Projections.property("id")));
1144
        }
1145

    
1146
        return criteria;
1147
    }
1148

    
1149

    
1150
    @Override
1151
    public <S extends T> List<S> findByParamWithRestrictions(Class<S> clazz, String param, String queryString,
1152
            MatchMode matchmode, List<Restriction<?>> restrictions, Integer pageSize, Integer pageNumber,
1153
            List<OrderHint> orderHints, List<String> propertyPaths) {
1154

    
1155
        List<Restriction<?>> allRestrictions = new ArrayList<>();
1156
        allRestrictions.add(new Restriction<>(param, matchmode, queryString));
1157
        if(restrictions != null){
1158
            allRestrictions.addAll(restrictions);
1159
        }
1160
        Criteria criteria = createCriteria(clazz, allRestrictions, false);
1161

    
1162
        addPageSizeAndNumber(criteria, pageSize, pageNumber);
1163

    
1164
        addOrder(criteria, orderHints);
1165

    
1166
        @SuppressWarnings("unchecked")
1167
        List<S> result = criteria.list();
1168
        defaultBeanInitializer.initializeAll(result, propertyPaths);
1169
        return result;
1170

    
1171
    }
1172

    
1173
    @Override
1174
    public long countByParamWithRestrictions(Class<? extends T> clazz, String param, String queryString,
1175
            MatchMode matchmode, List<Restriction<?>> restrictions) {
1176

    
1177
        List<Restriction<?>> allRestrictions = new ArrayList<>();
1178
        allRestrictions.add(new Restriction<>(param, matchmode, queryString));
1179
        if(restrictions != null){
1180
            allRestrictions.addAll(restrictions);
1181
        }
1182
        Criteria criteria = createCriteria(clazz, allRestrictions, true);
1183

    
1184
        return (Long) criteria.uniqueResult();
1185
    }
1186

    
1187
    @Override
1188
    public <S extends T> List<S> list(S example, Set<String> includeProperties, Integer limit, Integer start,
1189
            List<OrderHint> orderHints, List<String> propertyPaths) {
1190
        Criteria criteria = getSession().createCriteria(example.getClass());
1191
        addExample(criteria, example, includeProperties);
1192

    
1193
        addLimitAndStart(criteria, limit, start);
1194

    
1195
        addOrder(criteria, orderHints);
1196

    
1197
        @SuppressWarnings("unchecked")
1198
        List<S> results = criteria.list();
1199
        defaultBeanInitializer.initializeAll(results, propertyPaths);
1200
        return results;
1201
    }
1202

    
1203
    private class PropertySelectorImpl implements PropertySelector {
1204

    
1205
        private final Set<String> includeProperties;
1206
        /**
1207
         *
1208
         */
1209
        private static final long serialVersionUID = -3175311800911570546L;
1210

    
1211
        public PropertySelectorImpl(Set<String> includeProperties) {
1212
            this.includeProperties = includeProperties;
1213
        }
1214

    
1215
        @Override
1216
        public boolean include(Object propertyValue, String propertyName, Type type) {
1217
            if (includeProperties.contains(propertyName)) {
1218
                return true;
1219
            } else {
1220
                return false;
1221
            }
1222
        }
1223

    
1224
    }
1225

    
1226
    private class CriterionWithOperator {
1227

    
1228
        Restriction.Operator operator;
1229
        Criterion criterion;
1230

    
1231

    
1232
        public CriterionWithOperator(Operator operator, Criterion criterion) {
1233
            super();
1234
            this.operator = operator;
1235
            this.criterion = criterion;
1236
        }
1237

    
1238

    
1239
    }
1240

    
1241
    /**
1242
     * Returns a Criteria for the given {@link Class class} or, if
1243
     * <code>null</code>, for the base {@link Class class} of this DAO.
1244
     *
1245
     * @param clazz
1246
     * @return the Criteria
1247
     */
1248
    protected Criteria getCriteria(Class<? extends CdmBase> clazz) {
1249
        Criteria criteria = null;
1250
        if (clazz == null) {
1251
            criteria = getSession().createCriteria(type);
1252
        } else {
1253
            criteria = getSession().createCriteria(clazz);
1254
        }
1255
        return criteria;
1256
    }
1257

    
1258
    /**
1259
     * @param clazz
1260
     * @param auditEvent
1261
     * @return
1262
     */
1263
    protected AuditQuery makeAuditQuery(Class<? extends CdmBase> clazz, AuditEvent auditEvent) {
1264
        AuditQuery query = null;
1265

    
1266
        if (clazz == null) {
1267
            query = getAuditReader().createQuery().forEntitiesAtRevision(type, auditEvent.getRevisionNumber());
1268
        } else {
1269
            query = getAuditReader().createQuery().forEntitiesAtRevision(clazz, auditEvent.getRevisionNumber());
1270
        }
1271
        return query;
1272
    }
1273

    
1274
    protected AuditReader getAuditReader() {
1275
        return AuditReaderFactory.get(getSession());
1276
    }
1277
}
(3-3/18)