Merge branch 'release/5.8.0'
[cdmlib.git] / cdmlib-persistence / src / main / java / eu / etaxonomy / cdm / persistence / dao / hibernate / common / CdmEntityDaoBase.java
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 switch(matchMode){
669 case BEGINNING:
670 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.START);
671 break;
672 case END:
673 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.END);
674 break;
675 case LIKE:
676 restriction = Restrictions.ilike(propertyName, matchMode.queryStringFrom(queryString), org.hibernate.criterion.MatchMode.ANYWHERE);
677 break;
678 case EXACT:
679 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.EXACT);
680 break;
681 case ANYWHERE:
682 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.ANYWHERE);
683 break;
684 default:
685 throw new RuntimeException("Unknown MatchMode: " + matchMode.name());
686 }
687 }
688 return restriction;
689 }
690
691 /**
692 * {@inheritDoc}
693 */
694 @Override
695 public long count(Class<? extends T> type, List<Restriction<?>> restrictions) {
696
697 Criteria criteria = createCriteria(type, restrictions, false);
698
699 criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
700
701 return (Long) criteria.uniqueResult();
702
703 }
704
705 /**
706 * @param uuids
707 * @param pageSize
708 * @param pageNumber
709 * @param orderHints
710 * @param propertyName
711 * @return
712 */
713 private Criteria prepareList(Class<? extends T> clazz, Collection<?> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
714 String propertyName) {
715 if (clazz == null){
716 clazz = type;
717 }
718 Criteria criteria = getSession().createCriteria(clazz);
719 criteria.add(Restrictions.in(propertyName, uuids));
720
721 if (pageSize != null) {
722 criteria.setMaxResults(pageSize);
723 if (pageNumber != null) {
724 criteria.setFirstResult(pageNumber * pageSize);
725 } else {
726 criteria.setFirstResult(0);
727 }
728 }
729
730 if (orderHints == null) {
731 orderHints = OrderHint.defaultOrderHintsFor(type);
732 }
733 addOrder(criteria, orderHints);
734 return criteria;
735 }
736
737 /**
738 * @param clazz
739 * @return
740 */
741 private Criteria criterionForType(Class<? extends T> clazz) {
742 return getSession().createCriteria(entityType(clazz));
743 }
744
745 protected Class<? extends T> entityType(Class<? extends T> clazz){
746 if (clazz != null) {
747 return clazz;
748 } else {
749 return type;
750 }
751 }
752
753 @Override
754 public T load(UUID uuid) {
755 T bean = findByUuid(uuid);
756 if (bean == null) {
757 return null;
758 }
759 defaultBeanInitializer.load(bean);
760
761 return bean;
762 }
763
764 @Override
765 public T load(int id, List<String> propertyPaths) {
766 T bean = findById(id);
767 if (bean == null) {
768 return bean;
769 }
770 defaultBeanInitializer.initialize(bean, propertyPaths);
771
772 return bean;
773 }
774
775 @Override
776 public T load(UUID uuid, List<String> propertyPaths) {
777 return this.load(uuid, INCLUDE_UNPUBLISHED, propertyPaths);
778 }
779
780 protected T load(UUID uuid, boolean includeUnpublished, List<String> propertyPaths) {
781 T bean = findByUuid(uuid, includeUnpublished);
782 if (bean == null) {
783 return bean;
784 }
785 defaultBeanInitializer.initialize(bean, propertyPaths);
786
787 return bean;
788 }
789
790 @Override
791 public Boolean exists(UUID uuid) {
792 if (findByUuid(uuid) == null) {
793 return false;
794 }
795 return true;
796 }
797
798 @Override
799 public long count() {
800 return count(type);
801 }
802
803 @Override
804 public long count(Class<? extends T> clazz) {
805 Session session = getSession();
806 Criteria criteria = null;
807 if (clazz == null) {
808 criteria = session.createCriteria(type);
809 } else {
810 criteria = session.createCriteria(clazz);
811 }
812 criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
813
814 // since hibernate 4 (or so) uniqueResult returns Long, not Integer,
815 // therefore needs
816 // to be casted. Think about returning long rather then int!
817 return (long) criteria.uniqueResult();
818 }
819
820 @Override
821 public List<T> list(Integer limit, Integer start) {
822 return list(limit, start, null);
823 }
824
825 @Override
826 public List<Object[]> group(Class<? extends T> clazz, Integer limit, Integer start, List<Grouping> groups,
827 List<String> propertyPaths) {
828
829 Criteria criteria = null;
830 criteria = criterionForType(clazz);
831
832 addGroups(criteria, groups);
833
834 if (limit != null) {
835 criteria.setFirstResult(start);
836 criteria.setMaxResults(limit);
837 }
838
839 @SuppressWarnings("unchecked")
840 List<Object[]> result = criteria.list();
841
842 if (propertyPaths != null && !propertyPaths.isEmpty()) {
843 for (Object[] objects : result) {
844 defaultBeanInitializer.initialize(objects[0], propertyPaths);
845 }
846 }
847
848 return result;
849 }
850
851 protected void countGroups(DetachedCriteria criteria, List<Grouping> groups) {
852 if (groups != null) {
853
854 Map<String, String> aliases = new HashMap<String, String>();
855
856 for (Grouping grouping : groups) {
857 if (grouping.getAssociatedObj() != null) {
858 String alias = null;
859 if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
860 alias = grouping.getAssociatedObjectAlias();
861 aliases.put(grouping.getAssociatedObj(), alias);
862 criteria.createAlias(grouping.getAssociatedObj(), alias);
863 }
864 }
865 }
866
867 ProjectionList projectionList = Projections.projectionList();
868
869 for (Grouping grouping : groups) {
870 grouping.addProjection(projectionList);
871 }
872 criteria.setProjection(projectionList);
873 }
874 }
875
876 protected void addGroups(Criteria criteria, List<Grouping> groups) {
877 if (groups != null) {
878
879 Map<String, String> aliases = new HashMap<String, String>();
880
881 for (Grouping grouping : groups) {
882 if (grouping.getAssociatedObj() != null) {
883 String alias = null;
884 if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
885 alias = grouping.getAssociatedObjectAlias();
886 aliases.put(grouping.getAssociatedObj(), alias);
887 criteria.createAlias(grouping.getAssociatedObj(), alias);
888 }
889 }
890 }
891
892 ProjectionList projectionList = Projections.projectionList();
893
894 for (Grouping grouping : groups) {
895 grouping.addProjection(projectionList);
896 }
897 criteria.setProjection(projectionList);
898
899 for (Grouping grouping : groups) {
900 grouping.addOrder(criteria);
901
902 }
903 }
904 }
905
906 @Override
907 public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints) {
908 return list(limit, start, orderHints, null);
909 }
910
911 @Override
912 public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
913 Criteria criteria = getSession().createCriteria(type);
914 if (limit != null) {
915 criteria.setFirstResult(start);
916 criteria.setMaxResults(limit);
917 }
918
919 addOrder(criteria, orderHints);
920 @SuppressWarnings("unchecked")
921 List<T> results = criteria.list();
922
923 defaultBeanInitializer.initializeAll(results, propertyPaths);
924 return results;
925 }
926
927 @Override
928 public <S extends T> List<S> list(Class<S> clazz, Integer limit, Integer start, List<OrderHint> orderHints,
929 List<String> propertyPaths) {
930 Criteria criteria = null;
931 if (clazz == null) {
932 criteria = getSession().createCriteria(type);
933 } else {
934 criteria = getSession().createCriteria(clazz);
935 }
936
937 addLimitAndStart(criteria, limit, start);
938
939 addOrder(criteria, orderHints);
940
941 @SuppressWarnings("unchecked")
942 List<S> results = criteria.list();
943
944 defaultBeanInitializer.initializeAll(results, propertyPaths);
945 return results;
946 }
947
948 public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start, List<OrderHint> orderHints) {
949 return list(type, limit, start, orderHints, null);
950 }
951
952 @Override
953 public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start) {
954 return list(type, limit, start, null, null);
955 }
956
957 @Override
958 public Class<T> getType() {
959 return type;
960 }
961
962 protected void setPagingParameter(Query query, Integer pageSize, Integer pageIndex) {
963 if (pageSize != null) {
964 query.setMaxResults(pageSize);
965 if (pageIndex != null) {
966 query.setFirstResult(pageIndex * pageSize);
967 } else {
968 query.setFirstResult(0);
969 }
970 }
971 }
972
973 protected void setPagingParameter(AuditQuery query, Integer pageSize, Integer pageIndex) {
974 if (pageSize != null) {
975 query.setMaxResults(pageSize);
976 if (pageIndex != null) {
977 query.setFirstResult(pageIndex * pageSize);
978 } else {
979 query.setFirstResult(0);
980 }
981 }
982 }
983
984 @Override
985 public long count(T example, Set<String> includeProperties) {
986 Criteria criteria = getSession().createCriteria(example.getClass());
987 addExample(criteria, example, includeProperties);
988
989 criteria.setProjection(Projections.rowCount());
990 return (Long) criteria.uniqueResult();
991 }
992
993 protected void addExample(Criteria criteria, T example, Set<String> includeProperties) {
994 if (includeProperties != null && !includeProperties.isEmpty()) {
995 criteria.add(Example.create(example).setPropertySelector(new PropertySelectorImpl(includeProperties)));
996 ClassMetadata classMetadata = getSession().getSessionFactory().getClassMetadata(example.getClass());
997 for (String property : includeProperties) {
998 Type type = classMetadata.getPropertyType(property);
999 if (type.isEntityType()) {
1000 try {
1001 Field field = ReflectionUtils.findField(example.getClass(), property);
1002 field.setAccessible(true);
1003 Object value = field.get(example);
1004 if (value != null) {
1005 criteria.add(Restrictions.eq(property, value));
1006 } else {
1007 criteria.add(Restrictions.isNull(property));
1008 }
1009 } catch (SecurityException se) {
1010 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1011 se);
1012 } catch (HibernateException he) {
1013 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1014 he);
1015 } catch (IllegalArgumentException iae) {
1016 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1017 iae);
1018 } catch (IllegalAccessException ie) {
1019 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1020 ie);
1021 }
1022
1023 }
1024 }
1025 } else {
1026 criteria.add(Example.create(example));
1027 }
1028 }
1029
1030 /**
1031 *
1032 * NOTE: We can't reuse
1033 * {@link #list(Class, String, Object, MatchMode, Integer, Integer, List, List)
1034 * here due to different default behavior of the <code>matchmode</code>
1035 * parameter.
1036 *
1037 * @param clazz
1038 * @param param
1039 * @param queryString
1040 * @param matchmode
1041 * @param criterion
1042 * @param pageSize
1043 * @param pageNumber
1044 * @param orderHints
1045 * @param propertyPaths
1046 * @return
1047 */
1048 @Override
1049 public <S extends T> List<S> findByParam(Class<S> clazz, String param, String queryString, MatchMode matchmode,
1050 List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
1051 List<String> propertyPaths) {
1052
1053 Criteria criteria = criterionForType(clazz);
1054
1055 if (queryString != null) {
1056 if (matchmode == null) {
1057 criteria.add(Restrictions.ilike(param, queryString));
1058 } else if (matchmode == MatchMode.BEGINNING) {
1059 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START));
1060 } else if (matchmode == MatchMode.END) {
1061 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END));
1062 } else if (matchmode == MatchMode.EXACT) {
1063 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT));
1064 } else {
1065 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE));
1066 }
1067 }
1068
1069 addCriteria(criteria, criterion);
1070
1071 if (pageSize != null) {
1072 criteria.setMaxResults(pageSize);
1073 if (pageNumber != null) {
1074 criteria.setFirstResult(pageNumber * pageSize);
1075 } else {
1076 criteria.setFirstResult(0);
1077 }
1078 }
1079
1080 addOrder(criteria, orderHints);
1081
1082 @SuppressWarnings("unchecked")
1083 List<S> result = criteria.list();
1084 defaultBeanInitializer.initializeAll(result, propertyPaths);
1085 return result;
1086 }
1087
1088 /**
1089 *
1090 * @param clazz
1091 * @param param
1092 * @param queryString
1093 * @param matchmode
1094 * @param criterion
1095 * @return
1096 */
1097 @Override
1098 public long countByParam(Class<? extends T> clazz, String param, String queryString, MatchMode matchmode,
1099 List<Criterion> criterion) {
1100
1101 Criteria criteria = null;
1102
1103 criteria = criterionForType(clazz);
1104
1105 if (queryString != null) {
1106 if (matchmode == null) {
1107 criteria.add(Restrictions.ilike(param, queryString));
1108 } else if (matchmode == MatchMode.BEGINNING) {
1109 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START));
1110 } else if (matchmode == MatchMode.END) {
1111 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END));
1112 } else if (matchmode == MatchMode.EXACT) {
1113 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT));
1114 } else {
1115 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE));
1116 }
1117 }
1118
1119 addCriteria(criteria, criterion);
1120
1121 criteria.setProjection(Projections.rowCount());
1122
1123 return (Long) criteria.uniqueResult();
1124 }
1125
1126 /**
1127 * Creates a criteria query for the CDM <code>type</code> either for counting or listing matching entities.
1128 * <p>
1129 * The set of matching entities can be restricted by passing a list of {@link Restriction} objects.
1130 * Restrictions can logically combined:
1131 <pre>
1132 Arrays.asList(
1133 new Restriction<String>("titleCache", MatchMode.ANYWHERE, "foo"),
1134 new Restriction<String>("institute.name", Operator.OR, MatchMode.BEGINNING, "Bar")
1135 );
1136 </pre>
1137 * The first Restriction in the example above by default has the <code>Operator.AND</code> which will be
1138 * ignored since this is the first restriction. The <code>Operator</code> of further restrictions in the
1139 * list are used to combine with the previous restriction.
1140 *
1141 * @param type
1142 * @param restrictions
1143 * @param doCount
1144 * @return
1145 */
1146 protected Criteria createCriteria(Class<? extends T> type, List<Restriction<?>> restrictions, boolean doCount) {
1147
1148 DetachedCriteria idsOnlyCriteria = DetachedCriteria.forClass(entityType(type));
1149 idsOnlyCriteria.setProjection(Projections.distinct(Projections.id()));
1150
1151 addRestrictions(restrictions, idsOnlyCriteria);
1152
1153 Criteria criteria = criterionForType(type);
1154 criteria.add(Subqueries.propertyIn("id", idsOnlyCriteria));
1155
1156 if(doCount){
1157 criteria.setProjection(Projections.rowCount());
1158 } else {
1159 idsOnlyCriteria.setProjection(Projections.distinct(Projections.property("id")));
1160 }
1161
1162 return criteria;
1163 }
1164
1165
1166 @Override
1167 public <S extends T> List<S> findByParamWithRestrictions(Class<S> clazz, String param, String queryString,
1168 MatchMode matchmode, List<Restriction<?>> restrictions, Integer pageSize, Integer pageNumber,
1169 List<OrderHint> orderHints, List<String> propertyPaths) {
1170
1171 List<Restriction<?>> allRestrictions = new ArrayList<>();
1172 allRestrictions.add(new Restriction<String>(param, matchmode, queryString));
1173 if(restrictions != null){
1174 allRestrictions.addAll(restrictions);
1175 }
1176 Criteria criteria = createCriteria(clazz, allRestrictions, false);
1177
1178 if (pageSize != null) {
1179 criteria.setMaxResults(pageSize);
1180 if (pageNumber != null) {
1181 criteria.setFirstResult(pageNumber * pageSize);
1182 } else {
1183 criteria.setFirstResult(0);
1184 }
1185 }
1186
1187 addOrder(criteria, orderHints);
1188
1189 @SuppressWarnings("unchecked")
1190 List<S> result = criteria.list();
1191 defaultBeanInitializer.initializeAll(result, propertyPaths);
1192 return result;
1193
1194 }
1195
1196 @Override
1197 public long countByParamWithRestrictions(Class<? extends T> clazz, String param, String queryString,
1198 MatchMode matchmode, List<Restriction<?>> restrictions) {
1199
1200 List<Restriction<?>> allRestrictions = new ArrayList<>();
1201 allRestrictions.add(new Restriction<String>(param, matchmode, queryString));
1202 if(restrictions != null){
1203 allRestrictions.addAll(restrictions);
1204 }
1205 Criteria criteria = createCriteria(clazz, allRestrictions, true);
1206
1207 return (Long) criteria.uniqueResult();
1208 }
1209
1210 @Override
1211 public List<T> list(T example, Set<String> includeProperties, Integer limit, Integer start,
1212 List<OrderHint> orderHints, List<String> propertyPaths) {
1213 Criteria criteria = getSession().createCriteria(example.getClass());
1214 addExample(criteria, example, includeProperties);
1215
1216 addLimitAndStart(criteria, limit, start);
1217
1218 addOrder(criteria, orderHints);
1219
1220 @SuppressWarnings("unchecked")
1221 List<T> results = criteria.list();
1222 defaultBeanInitializer.initializeAll(results, propertyPaths);
1223 return results;
1224 }
1225
1226 private class PropertySelectorImpl implements PropertySelector {
1227
1228 private final Set<String> includeProperties;
1229 /**
1230 *
1231 */
1232 private static final long serialVersionUID = -3175311800911570546L;
1233
1234 public PropertySelectorImpl(Set<String> includeProperties) {
1235 this.includeProperties = includeProperties;
1236 }
1237
1238 @Override
1239 public boolean include(Object propertyValue, String propertyName, Type type) {
1240 if (includeProperties.contains(propertyName)) {
1241 return true;
1242 } else {
1243 return false;
1244 }
1245 }
1246
1247 }
1248
1249 private class CriterionWithOperator {
1250
1251 Restriction.Operator operator;
1252 Criterion criterion;
1253
1254
1255 public CriterionWithOperator(Operator operator, Criterion criterion) {
1256 super();
1257 this.operator = operator;
1258 this.criterion = criterion;
1259 }
1260
1261
1262 }
1263
1264 /**
1265 * Returns a Criteria for the given {@link Class class} or, if
1266 * <code>null</code>, for the base {@link Class class} of this DAO.
1267 *
1268 * @param clazz
1269 * @return the Criteria
1270 */
1271 protected Criteria getCriteria(Class<? extends CdmBase> clazz) {
1272 Criteria criteria = null;
1273 if (clazz == null) {
1274 criteria = getSession().createCriteria(type);
1275 } else {
1276 criteria = getSession().createCriteria(clazz);
1277 }
1278 return criteria;
1279 }
1280
1281 /**
1282 * @param clazz
1283 * @param auditEvent
1284 * @return
1285 */
1286 protected AuditQuery makeAuditQuery(Class<? extends CdmBase> clazz, AuditEvent auditEvent) {
1287 AuditQuery query = null;
1288
1289 if (clazz == null) {
1290 query = getAuditReader().createQuery().forEntitiesAtRevision(type, auditEvent.getRevisionNumber());
1291 } else {
1292 query = getAuditReader().createQuery().forEntitiesAtRevision(clazz, auditEvent.getRevisionNumber());
1293 }
1294 return query;
1295 }
1296
1297 protected AuditReader getAuditReader() {
1298 return AuditReaderFactory.get(getSession());
1299 }
1300 }