Project

General

Profile

Download (47.2 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.log4j.Logger;
25
import org.hibernate.Criteria;
26
import org.hibernate.FlushMode;
27
import org.hibernate.HibernateException;
28
import org.hibernate.LockOptions;
29
import org.hibernate.NonUniqueObjectException;
30
import org.hibernate.Query;
31
import org.hibernate.Session;
32
import org.hibernate.criterion.Criterion;
33
import org.hibernate.criterion.DetachedCriteria;
34
import org.hibernate.criterion.Example;
35
import org.hibernate.criterion.Example.PropertySelector;
36
import org.hibernate.criterion.LogicalExpression;
37
import org.hibernate.criterion.Order;
38
import org.hibernate.criterion.ProjectionList;
39
import org.hibernate.criterion.Projections;
40
import org.hibernate.criterion.Restrictions;
41
import org.hibernate.criterion.Subqueries;
42
import org.hibernate.envers.AuditReader;
43
import org.hibernate.envers.AuditReaderFactory;
44
import org.hibernate.envers.query.AuditQuery;
45
import org.hibernate.metadata.ClassMetadata;
46
import org.hibernate.sql.JoinType;
47
import org.hibernate.type.Type;
48
import org.joda.time.DateTime;
49
import org.springframework.beans.factory.annotation.Autowired;
50
import org.springframework.dao.DataAccessException;
51
import org.springframework.dao.InvalidDataAccessApiUsageException;
52
import org.springframework.security.core.Authentication;
53
import org.springframework.security.core.context.SecurityContextHolder;
54
import org.springframework.stereotype.Repository;
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.User;
60
import eu.etaxonomy.cdm.model.common.VersionableEntity;
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
 * @author a.mueller FIXME CdmEntityDaoBase is abstract, can it be annotated
76
 *         with @Repository?
77
 */
78
@Repository
79
public abstract class CdmEntityDaoBase<T extends CdmBase> extends DaoBase implements ICdmEntityDao<T> {
80

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

    
83
    protected int flushAfterNo = 1000; // large numbers may cause
84
                                       // synchronisation errors when commiting
85
                                       // the session !!
86

    
87
    protected Class<T> type;
88

    
89
    @Autowired
90
    // @Qualifier("defaultBeanInitializer")
91
    protected IBeanInitializer defaultBeanInitializer;
92

    
93
    public void setDefaultBeanInitializer(IBeanInitializer defaultBeanInitializer) {
94
        this.defaultBeanInitializer = defaultBeanInitializer;
95
    }
96

    
97
    @Autowired
98
    private ReferringObjectMetadataFactory referringObjectMetadataFactory;
99

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
242
        getSession().merge(x);
243

    
244
        Set<ReferringObjectMetadata> referringObjectMetas = referringObjectMetadataFactory.get(x.getClass());
245

    
246
        for (ReferringObjectMetadata referringObjectMetadata : referringObjectMetas) {
247

    
248
            List<CdmBase> referringObjects = referringObjectMetadata.getReferringObjects(x, getSession());
249

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

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

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

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

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

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

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

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

    
351
            e.printStackTrace();
352
            throw e;
353
        } catch (HibernateException e) {
354

    
355
            e.printStackTrace();
356
            throw e;
357
        }
358
    }
359

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

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

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

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

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

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

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

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

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

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

    
465
    @Override
466
    public List<T> loadList(Collection<Integer> ids, List<String> propertyPaths) throws DataAccessException {
467

    
468
        if (ids.isEmpty()) {
469
            return new ArrayList<T>(0);
470
        }
471

    
472
        Criteria criteria = prepareList(null, ids, null, null, null, "id");
473

    
474
        if (logger.isDebugEnabled()) {
475
            logger.debug(criteria.toString());
476
        }
477

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

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

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

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

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

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

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

    
514
    /**
515
     * {@inheritDoc}
516
     */
517
    @Override
518
    public <S extends T> List<S> list(Class<S> type, List<Restriction<?>> restrictions, Integer limit, Integer start,
519
            List<OrderHint> orderHints, List<String> propertyPaths) {
520

    
521
        Criteria criteria = createCriteria(type, restrictions, false);
522

    
523
        addLimitAndStart(criteria, limit, start);
524
        addOrder(criteria, orderHints);
525

    
526
        @SuppressWarnings("unchecked")
527
        List<S> result = criteria.list();
528
        defaultBeanInitializer.initializeAll(result, propertyPaths);
529
        return result;
530
    }
531

    
532
    /**
533
     * @param restrictions
534
     * @param criteria
535
     */
536
    private void addRestrictions(List<Restriction<?>> restrictions, DetachedCriteria criteria) {
537

    
538
        if(restrictions == null || restrictions.isEmpty()){
539
            return ;
540
        }
541

    
542
        List<CriterionWithOperator> perProperty = new ArrayList<>(restrictions.size());
543
        Map<String, String> aliases = new HashMap<>();
544

    
545

    
546

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

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

    
624
            }
625

    
626

    
627
            criteria.add(logicalExpression);
628
//            if(firstOperator == Operator.OR){
629
//                // OR
630
//            } else {
631
//                // AND
632
//                criteria.add(Restrictions.and(queryStringCriterion, logicalExpression));
633
//            }
634
        }
635
        if(logger.isDebugEnabled()){
636
            logger.debug("addRestrictions() final criteria: " + criteria.toString());
637
        }
638
    }
639

    
640
    /**
641
     * @param propertyName
642
     * @param value
643
     * @param matchMode
644
     *            is only applied if the <code>value</code> is a
645
     *            <code>String</code> object
646
     * @param criteria
647
     * @return
648
     */
649
    private Criterion createRestriction(String propertyName, Object value, MatchMode matchMode) {
650

    
651
        Criterion restriction;
652
        if (value == null) {
653
            if (logger.isDebugEnabled()) {
654
                logger.debug("createRestriction() " + propertyName + " is null ");
655
            }
656
            restriction = Restrictions.isNull(propertyName);
657
        } else if (matchMode == null || !(value instanceof String)) {
658
            if (logger.isDebugEnabled()) {
659
                logger.debug("createRestriction() " + propertyName + " = " + value.toString());
660
            }
661
            restriction = Restrictions.eq(propertyName, value);
662
        } else {
663
            String queryString = (String) value;
664
            if (logger.isDebugEnabled()) {
665
                logger.debug("createRestriction() " + propertyName + " " + matchMode.getMatchOperator() + " "
666
                        + matchMode.queryStringFrom(queryString));
667
            }
668
            if (matchMode == MatchMode.BEGINNING) {
669
                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.START);
670
            } else if (matchMode == MatchMode.END) {
671
                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.END);
672
            } else if (matchMode == MatchMode.EXACT) {
673
                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.EXACT);
674
            } else {
675
                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.ANYWHERE);
676
            }
677
        }
678
        return restriction;
679
    }
680

    
681
    /**
682
     * {@inheritDoc}
683
     */
684
    @Override
685
    public long count(Class<? extends T> type, List<Restriction<?>> restrictions) {
686

    
687
        Criteria criteria = createCriteria(type, restrictions, false);
688

    
689
        criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
690

    
691
        return (Long) criteria.uniqueResult();
692

    
693
    }
694

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

    
711
        if (pageSize != null) {
712
            criteria.setMaxResults(pageSize);
713
            if (pageNumber != null) {
714
                criteria.setFirstResult(pageNumber * pageSize);
715
            } else {
716
                criteria.setFirstResult(0);
717
            }
718
        }
719

    
720
        if (orderHints == null) {
721
            orderHints = OrderHint.defaultOrderHintsFor(type);
722
        }
723
        addOrder(criteria, orderHints);
724
        return criteria;
725
    }
726

    
727
    /**
728
     * @param clazz
729
     * @return
730
     */
731
    private Criteria criterionForType(Class<? extends T> clazz) {
732
        return  getSession().createCriteria(entityType(clazz));
733
    }
734

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

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

    
751
        return bean;
752
    }
753

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

    
762
        return bean;
763
    }
764

    
765
    @Override
766
    public T load(UUID uuid, List<String> propertyPaths) {
767
        return this.load(uuid, INCLUDE_UNPUBLISHED, propertyPaths);
768
    }
769

    
770
    protected T load(UUID uuid, boolean includeUnpublished, List<String> propertyPaths) {
771
        T bean = findByUuid(uuid, includeUnpublished);
772
        if (bean == null) {
773
            return bean;
774
        }
775
        defaultBeanInitializer.initialize(bean, propertyPaths);
776

    
777
        return bean;
778
    }
779

    
780
    @Override
781
    public Boolean exists(UUID uuid) {
782
        if (findByUuid(uuid) == null) {
783
            return false;
784
        }
785
        return true;
786
    }
787

    
788
    @Override
789
    public long count() {
790
        return count(type);
791
    }
792

    
793
    @Override
794
    public long count(Class<? extends T> clazz) {
795
        Session session = getSession();
796
        Criteria criteria = null;
797
        if (clazz == null) {
798
            criteria = session.createCriteria(type);
799
        } else {
800
            criteria = session.createCriteria(clazz);
801
        }
802
        criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
803

    
804
        // since hibernate 4 (or so) uniqueResult returns Long, not Integer,
805
        // therefore needs
806
        // to be casted. Think about returning long rather then int!
807
        return (long) criteria.uniqueResult();
808
    }
809

    
810
    @Override
811
    public List<T> list(Integer limit, Integer start) {
812
        return list(limit, start, null);
813
    }
814

    
815
    @Override
816
    public List<Object[]> group(Class<? extends T> clazz, Integer limit, Integer start, List<Grouping> groups,
817
            List<String> propertyPaths) {
818

    
819
        Criteria criteria = null;
820
        criteria = criterionForType(clazz);
821

    
822
        addGroups(criteria, groups);
823

    
824
        if (limit != null) {
825
            criteria.setFirstResult(start);
826
            criteria.setMaxResults(limit);
827
        }
828

    
829
        @SuppressWarnings("unchecked")
830
        List<Object[]> result = criteria.list();
831

    
832
        if (propertyPaths != null && !propertyPaths.isEmpty()) {
833
            for (Object[] objects : result) {
834
                defaultBeanInitializer.initialize(objects[0], propertyPaths);
835
            }
836
        }
837

    
838
        return result;
839
    }
840

    
841
    protected void countGroups(DetachedCriteria criteria, List<Grouping> groups) {
842
        if (groups != null) {
843

    
844
            Map<String, String> aliases = new HashMap<String, String>();
845

    
846
            for (Grouping grouping : groups) {
847
                if (grouping.getAssociatedObj() != null) {
848
                    String alias = null;
849
                    if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
850
                        alias = grouping.getAssociatedObjectAlias();
851
                        aliases.put(grouping.getAssociatedObj(), alias);
852
                        criteria.createAlias(grouping.getAssociatedObj(), alias);
853
                    }
854
                }
855
            }
856

    
857
            ProjectionList projectionList = Projections.projectionList();
858

    
859
            for (Grouping grouping : groups) {
860
                grouping.addProjection(projectionList);
861
            }
862
            criteria.setProjection(projectionList);
863
        }
864
    }
865

    
866
    protected void addGroups(Criteria criteria, List<Grouping> groups) {
867
        if (groups != null) {
868

    
869
            Map<String, String> aliases = new HashMap<String, String>();
870

    
871
            for (Grouping grouping : groups) {
872
                if (grouping.getAssociatedObj() != null) {
873
                    String alias = null;
874
                    if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
875
                        alias = grouping.getAssociatedObjectAlias();
876
                        aliases.put(grouping.getAssociatedObj(), alias);
877
                        criteria.createAlias(grouping.getAssociatedObj(), alias);
878
                    }
879
                }
880
            }
881

    
882
            ProjectionList projectionList = Projections.projectionList();
883

    
884
            for (Grouping grouping : groups) {
885
                grouping.addProjection(projectionList);
886
            }
887
            criteria.setProjection(projectionList);
888

    
889
            for (Grouping grouping : groups) {
890
                grouping.addOrder(criteria);
891

    
892
            }
893
        }
894
    }
895

    
896
    @Override
897
    public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints) {
898
        return list(limit, start, orderHints, null);
899
    }
900

    
901
    @Override
902
    public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
903
        Criteria criteria = getSession().createCriteria(type);
904
        if (limit != null) {
905
            criteria.setFirstResult(start);
906
            criteria.setMaxResults(limit);
907
        }
908

    
909
        addOrder(criteria, orderHints);
910
        @SuppressWarnings("unchecked")
911
        List<T> results = criteria.list();
912

    
913
        defaultBeanInitializer.initializeAll(results, propertyPaths);
914
        return results;
915
    }
916

    
917
    @Override
918
    public <S extends T> List<S> list(Class<S> clazz, Integer limit, Integer start, List<OrderHint> orderHints,
919
            List<String> propertyPaths) {
920
        Criteria criteria = null;
921
        if (clazz == null) {
922
            criteria = getSession().createCriteria(type);
923
        } else {
924
            criteria = getSession().createCriteria(clazz);
925
        }
926

    
927
        addLimitAndStart(criteria, limit, start);
928

    
929
        addOrder(criteria, orderHints);
930

    
931
        @SuppressWarnings("unchecked")
932
        List<S> results = criteria.list();
933

    
934
        defaultBeanInitializer.initializeAll(results, propertyPaths);
935
        return results;
936
    }
937

    
938
    public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start, List<OrderHint> orderHints) {
939
        return list(type, limit, start, orderHints, null);
940
    }
941

    
942
    @Override
943
    public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start) {
944
        return list(type, limit, start, null, null);
945
    }
946

    
947
    @Override
948
    public Class<T> getType() {
949
        return type;
950
    }
951

    
952
    protected void setPagingParameter(Query query, Integer pageSize, Integer pageIndex) {
953
        if (pageSize != null) {
954
            query.setMaxResults(pageSize);
955
            if (pageIndex != null) {
956
                query.setFirstResult(pageIndex * pageSize);
957
            } else {
958
                query.setFirstResult(0);
959
            }
960
        }
961
    }
962

    
963
    protected void setPagingParameter(AuditQuery query, Integer pageSize, Integer pageIndex) {
964
        if (pageSize != null) {
965
            query.setMaxResults(pageSize);
966
            if (pageIndex != null) {
967
                query.setFirstResult(pageIndex * pageSize);
968
            } else {
969
                query.setFirstResult(0);
970
            }
971
        }
972
    }
973

    
974
    @Override
975
    public long count(T example, Set<String> includeProperties) {
976
        Criteria criteria = getSession().createCriteria(example.getClass());
977
        addExample(criteria, example, includeProperties);
978

    
979
        criteria.setProjection(Projections.rowCount());
980
        return (Long) criteria.uniqueResult();
981
    }
982

    
983
    protected void addExample(Criteria criteria, T example, Set<String> includeProperties) {
984
        if (includeProperties != null && !includeProperties.isEmpty()) {
985
            criteria.add(Example.create(example).setPropertySelector(new PropertySelectorImpl(includeProperties)));
986
            ClassMetadata classMetadata = getSession().getSessionFactory().getClassMetadata(example.getClass());
987
            for (String property : includeProperties) {
988
                Type type = classMetadata.getPropertyType(property);
989
                if (type.isEntityType()) {
990
                    try {
991
                        Field field = ReflectionUtils.findField(example.getClass(), property);
992
                        field.setAccessible(true);
993
                        Object value = field.get(example);
994
                        if (value != null) {
995
                            criteria.add(Restrictions.eq(property, value));
996
                        } else {
997
                            criteria.add(Restrictions.isNull(property));
998
                        }
999
                    } catch (SecurityException se) {
1000
                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1001
                                se);
1002
                    } catch (HibernateException he) {
1003
                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1004
                                he);
1005
                    } catch (IllegalArgumentException iae) {
1006
                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1007
                                iae);
1008
                    } catch (IllegalAccessException ie) {
1009
                        throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1010
                                ie);
1011
                    }
1012

    
1013
                }
1014
            }
1015
        } else {
1016
            criteria.add(Example.create(example));
1017
        }
1018
    }
1019

    
1020
    /**
1021
     *
1022
     * NOTE: We can't reuse
1023
     * {@link #list(Class, String, Object, MatchMode, Integer, Integer, List, List)
1024
     * here due to different default behavior of the <code>matchmode</code>
1025
     * parameter.
1026
     *
1027
     * @param clazz
1028
     * @param param
1029
     * @param queryString
1030
     * @param matchmode
1031
     * @param criterion
1032
     * @param pageSize
1033
     * @param pageNumber
1034
     * @param orderHints
1035
     * @param propertyPaths
1036
     * @return
1037
     */
1038
    @Override
1039
    public <S extends T> List<S> findByParam(Class<S> clazz, String param, String queryString, MatchMode matchmode,
1040
            List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
1041
            List<String> propertyPaths) {
1042

    
1043
        Criteria criteria = criterionForType(clazz);
1044

    
1045
        if (queryString != null) {
1046
            if (matchmode == null) {
1047
                criteria.add(Restrictions.ilike(param, queryString));
1048
            } else if (matchmode == MatchMode.BEGINNING) {
1049
                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START));
1050
            } else if (matchmode == MatchMode.END) {
1051
                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END));
1052
            } else if (matchmode == MatchMode.EXACT) {
1053
                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT));
1054
            } else {
1055
                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE));
1056
            }
1057
        }
1058

    
1059
        addCriteria(criteria, criterion);
1060

    
1061
        if (pageSize != null) {
1062
            criteria.setMaxResults(pageSize);
1063
            if (pageNumber != null) {
1064
                criteria.setFirstResult(pageNumber * pageSize);
1065
            } else {
1066
                criteria.setFirstResult(0);
1067
            }
1068
        }
1069

    
1070
        addOrder(criteria, orderHints);
1071

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

    
1078
    /**
1079
     *
1080
     * @param clazz
1081
     * @param param
1082
     * @param queryString
1083
     * @param matchmode
1084
     * @param criterion
1085
     * @return
1086
     */
1087
    @Override
1088
    public long countByParam(Class<? extends T> clazz, String param, String queryString, MatchMode matchmode,
1089
            List<Criterion> criterion) {
1090

    
1091
        Criteria criteria = null;
1092

    
1093
        criteria = criterionForType(clazz);
1094

    
1095
        if (queryString != null) {
1096
            if (matchmode == null) {
1097
                criteria.add(Restrictions.ilike(param, queryString));
1098
            } else if (matchmode == MatchMode.BEGINNING) {
1099
                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START));
1100
            } else if (matchmode == MatchMode.END) {
1101
                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END));
1102
            } else if (matchmode == MatchMode.EXACT) {
1103
                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT));
1104
            } else {
1105
                criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE));
1106
            }
1107
        }
1108

    
1109
        addCriteria(criteria, criterion);
1110

    
1111
        criteria.setProjection(Projections.rowCount());
1112

    
1113
        return (Long) criteria.uniqueResult();
1114
    }
1115

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

    
1138
        DetachedCriteria idsOnlyCriteria = DetachedCriteria.forClass(entityType(type));
1139
        idsOnlyCriteria.setProjection(Projections.distinct(Projections.id()));
1140

    
1141
        addRestrictions(restrictions, idsOnlyCriteria);
1142

    
1143
        Criteria criteria = criterionForType(type);
1144
        criteria.add(Subqueries.propertyIn("id", idsOnlyCriteria));
1145

    
1146
        if(doCount){
1147
            criteria.setProjection(Projections.rowCount());
1148
        } else {
1149
            idsOnlyCriteria.setProjection(Projections.distinct(Projections.property("id")));
1150
        }
1151

    
1152
        return criteria;
1153
    }
1154

    
1155

    
1156
    @Override
1157
    public <S extends T> List<S> findByParamWithRestrictions(Class<S> clazz, String param, String queryString,
1158
            MatchMode matchmode, List<Restriction<?>> restrictions, Integer pageSize, Integer pageNumber,
1159
            List<OrderHint> orderHints, List<String> propertyPaths) {
1160

    
1161
        List<Restriction<?>> allRestrictions = new ArrayList<>();
1162
        allRestrictions.add(new Restriction<String>(param, matchmode, queryString));
1163
        if(restrictions != null){
1164
            allRestrictions.addAll(restrictions);
1165
        }
1166
        Criteria criteria = createCriteria(clazz, allRestrictions, false);
1167

    
1168
        if (pageSize != null) {
1169
            criteria.setMaxResults(pageSize);
1170
            if (pageNumber != null) {
1171
                criteria.setFirstResult(pageNumber * pageSize);
1172
            } else {
1173
                criteria.setFirstResult(0);
1174
            }
1175
        }
1176

    
1177
        addOrder(criteria, orderHints);
1178

    
1179
        @SuppressWarnings("unchecked")
1180
        List<S> result = criteria.list();
1181
        defaultBeanInitializer.initializeAll(result, propertyPaths);
1182
        return result;
1183

    
1184
    }
1185

    
1186
    @Override
1187
    public long countByParamWithRestrictions(Class<? extends T> clazz, String param, String queryString,
1188
            MatchMode matchmode, List<Restriction<?>> restrictions) {
1189

    
1190
        List<Restriction<?>> allRestrictions = new ArrayList<>();
1191
        allRestrictions.add(new Restriction<String>(param, matchmode, queryString));
1192
        if(restrictions != null){
1193
            allRestrictions.addAll(restrictions);
1194
        }
1195
        Criteria criteria = createCriteria(clazz, allRestrictions, true);
1196

    
1197
        return (Long) criteria.uniqueResult();
1198
    }
1199

    
1200
    @Override
1201
    public List<T> list(T example, Set<String> includeProperties, Integer limit, Integer start,
1202
            List<OrderHint> orderHints, List<String> propertyPaths) {
1203
        Criteria criteria = getSession().createCriteria(example.getClass());
1204
        addExample(criteria, example, includeProperties);
1205

    
1206
        addLimitAndStart(criteria, limit, start);
1207

    
1208
        addOrder(criteria, orderHints);
1209

    
1210
        @SuppressWarnings("unchecked")
1211
        List<T> results = criteria.list();
1212
        defaultBeanInitializer.initializeAll(results, propertyPaths);
1213
        return results;
1214
    }
1215

    
1216
    private class PropertySelectorImpl implements PropertySelector {
1217

    
1218
        private final Set<String> includeProperties;
1219
        /**
1220
         *
1221
         */
1222
        private static final long serialVersionUID = -3175311800911570546L;
1223

    
1224
        public PropertySelectorImpl(Set<String> includeProperties) {
1225
            this.includeProperties = includeProperties;
1226
        }
1227

    
1228
        @Override
1229
        public boolean include(Object propertyValue, String propertyName, Type type) {
1230
            if (includeProperties.contains(propertyName)) {
1231
                return true;
1232
            } else {
1233
                return false;
1234
            }
1235
        }
1236

    
1237
    }
1238

    
1239
    private class CriterionWithOperator {
1240

    
1241
        Restriction.Operator operator;
1242
        Criterion criterion;
1243

    
1244

    
1245
        public CriterionWithOperator(Operator operator, Criterion criterion) {
1246
            super();
1247
            this.operator = operator;
1248
            this.criterion = criterion;
1249
        }
1250

    
1251

    
1252
    }
1253

    
1254
    /**
1255
     * Returns a Criteria for the given {@link Class class} or, if
1256
     * <code>null</code>, for the base {@link Class class} of this DAO.
1257
     *
1258
     * @param clazz
1259
     * @return the Criteria
1260
     */
1261
    protected Criteria getCriteria(Class<? extends CdmBase> clazz) {
1262
        Criteria criteria = null;
1263
        if (clazz == null) {
1264
            criteria = getSession().createCriteria(type);
1265
        } else {
1266
            criteria = getSession().createCriteria(clazz);
1267
        }
1268
        return criteria;
1269
    }
1270

    
1271
    /**
1272
     * @param clazz
1273
     * @param auditEvent
1274
     * @return
1275
     */
1276
    protected AuditQuery makeAuditQuery(Class<? extends CdmBase> clazz, AuditEvent auditEvent) {
1277
        AuditQuery query = null;
1278

    
1279
        if (clazz == null) {
1280
            query = getAuditReader().createQuery().forEntitiesAtRevision(type, auditEvent.getRevisionNumber());
1281
        } else {
1282
            query = getAuditReader().createQuery().forEntitiesAtRevision(clazz, auditEvent.getRevisionNumber());
1283
        }
1284
        return query;
1285
    }
1286

    
1287
    protected AuditReader getAuditReader() {
1288
        return AuditReaderFactory.get(getSession());
1289
    }
1290
}
(3-3/25)