8c243d8436b752cdf502f879c09f8554d3042120
[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.logging.log4j.LogManager;import org.apache.logging.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.Session;
31 import org.hibernate.criterion.Criterion;
32 import org.hibernate.criterion.DetachedCriteria;
33 import org.hibernate.criterion.Disjunction;
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.util.ReflectionUtils;
55
56 import eu.etaxonomy.cdm.model.common.CdmBase;
57 import eu.etaxonomy.cdm.model.common.IPublishable;
58 import eu.etaxonomy.cdm.model.common.VersionableEntity;
59 import eu.etaxonomy.cdm.model.permission.User;
60 import eu.etaxonomy.cdm.model.view.AuditEvent;
61 import eu.etaxonomy.cdm.persistence.dao.common.ICdmEntityDao;
62 import eu.etaxonomy.cdm.persistence.dao.common.Restriction;
63 import eu.etaxonomy.cdm.persistence.dao.common.Restriction.Operator;
64 import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;
65 import eu.etaxonomy.cdm.persistence.dto.MergeResult;
66 import eu.etaxonomy.cdm.persistence.hibernate.PostMergeEntityListener;
67 import eu.etaxonomy.cdm.persistence.hibernate.replace.ReferringObjectMetadata;
68 import eu.etaxonomy.cdm.persistence.hibernate.replace.ReferringObjectMetadataFactory;
69 import eu.etaxonomy.cdm.persistence.query.Grouping;
70 import eu.etaxonomy.cdm.persistence.query.MatchMode;
71 import eu.etaxonomy.cdm.persistence.query.OrderHint;
72
73 /**
74 * Hibernate implementation for {@link ICdmEntityDao}.
75 */
76 public abstract class CdmEntityDaoBase<T extends CdmBase>
77 extends DaoBase
78 implements ICdmEntityDao<T> {
79
80 private static final Logger logger = LogManager.getLogger(CdmEntityDaoBase.class);
81
82 protected int flushAfterNo = 1000; // large numbers may cause synchronisation errors
83 // when commiting the session
84 protected Class<T> type;
85
86 @Autowired
87 // @Qualifier("defaultBeanInitializer")
88 protected IBeanInitializer defaultBeanInitializer;
89
90 public void setDefaultBeanInitializer(IBeanInitializer defaultBeanInitializer) {
91 this.defaultBeanInitializer = defaultBeanInitializer;
92 }
93
94 @Autowired
95 private ReferringObjectMetadataFactory referringObjectMetadataFactory;
96
97 protected static final EnumSet<Operator> LEFTOUTER_OPS = EnumSet.of(Operator.AND_NOT, Operator.OR, Operator.OR_NOT);
98
99 public CdmEntityDaoBase(Class<T> type) {
100 this.type = type;
101 assert type != null;
102 logger.debug("Creating DAO of type [" + type.getSimpleName() + "]");
103 }
104
105 @Override
106 public void lock(T t, LockOptions lockOptions) {
107 getSession().buildLockRequest(lockOptions).lock(t);
108 }
109
110 @Override
111 public void refresh(T t, LockOptions lockOptions, List<String> propertyPaths) {
112 getSession().refresh(t, lockOptions);
113 defaultBeanInitializer.initialize(t, propertyPaths);
114 }
115
116 // TODO this method should be moved to a concrete class (not typed)
117 public UUID saveCdmObj(CdmBase cdmObj) throws DataAccessException {
118 getSession().saveOrUpdate(cdmObj);
119 return cdmObj.getUuid();
120 }
121
122 // TODO: Replace saveCdmObj() by saveCdmObject_
123 private UUID saveCdmObject_(T cdmObj) {
124 getSession().saveOrUpdate(cdmObj);
125 return cdmObj.getUuid();
126 }
127
128 // TODO: Use everywhere CdmEntityDaoBase.saveAll() instead of
129 // ServiceBase.saveCdmObjectAll()?
130 // TODO: why does this use saveCdmObject_ which actually savesOrUpdateds
131 // data ?
132 @Override
133 public Map<UUID, T> saveAll(Collection<? extends T> cdmObjCollection) {
134 int types = cdmObjCollection.getClass().getTypeParameters().length;
135 if (types > 0) {
136 if (logger.isDebugEnabled()) {
137 logger.debug("ClassType: + " + cdmObjCollection.getClass().getTypeParameters()[0]);
138 }
139 }
140
141 Map<UUID, T> resultMap = new HashMap<>();
142 Iterator<? extends T> iterator = cdmObjCollection.iterator();
143 int i = 0;
144 while (iterator.hasNext()) {
145 if (((i % 2000) == 0) && (i > 0)) {
146 logger.debug("Saved " + i + " objects");
147 }
148 T cdmObj = iterator.next();
149 UUID uuid = saveCdmObject_(cdmObj);
150 if (logger.isDebugEnabled()) {
151 logger.debug("Save cdmObj: " + (cdmObj == null ? null : cdmObj.toString()));
152 }
153 resultMap.put(uuid, cdmObj);
154 i++;
155 if ((i % flushAfterNo) == 0) {
156 try {
157 if (logger.isDebugEnabled()) {
158 logger.debug("flush");
159 }
160 flush();
161 } catch (Exception e) {
162 logger.error("An exception occurred when trying to flush data");
163 e.printStackTrace();
164 throw new RuntimeException(e);
165 }
166 }
167 }
168
169 if (logger.isInfoEnabled()) {
170 logger.info("Saved " + i + " objects");
171 }
172 return resultMap;
173 }
174
175 private UUID saveOrUpdateCdmObject(T cdmObj) {
176 getSession().saveOrUpdate(cdmObj);
177 return cdmObj.getUuid();
178 }
179
180 @Override
181 public Map<UUID, T> saveOrUpdateAll(Collection<T> cdmObjCollection) {
182 int types = cdmObjCollection.getClass().getTypeParameters().length;
183 if (types > 0) {
184 if (logger.isDebugEnabled()) {
185 logger.debug("ClassType: + " + cdmObjCollection.getClass().getTypeParameters()[0]);
186 }
187 }
188
189 Map<UUID, T> resultMap = new HashMap<>();
190 Iterator<T> iterator = cdmObjCollection.iterator();
191 int i = 0;
192 while (iterator.hasNext()) {
193 if (((i % 2000) == 0) && (i > 0)) {
194 logger.debug("Saved " + i + " objects");
195 }
196 T cdmObj = iterator.next();
197 UUID uuid = saveOrUpdateCdmObject(cdmObj);
198 if (logger.isDebugEnabled()) {
199 logger.debug("Save cdmObj: " + (cdmObj == null ? null : cdmObj.toString()));
200 }
201 resultMap.put(uuid, cdmObj);
202 i++;
203 if ((i % flushAfterNo) == 0) {
204 try {
205 if (logger.isDebugEnabled()) {
206 logger.debug("flush");
207 }
208 flush();
209 } catch (Exception e) {
210 logger.error("An exception occurred when trying to flush data");
211 e.printStackTrace();
212 throw new RuntimeException(e);
213 }
214 }
215 }
216
217 if (logger.isInfoEnabled()) {
218 logger.info("Saved " + i + " objects");
219 }
220 return resultMap;
221 }
222
223 @Override
224 public T replace(T x, T y) {
225 if (x.equals(y)) {
226 return y;
227 }
228
229 Class<?> commonClass = x.getClass();
230 if (y != null) {
231 while (!commonClass.isAssignableFrom(y.getClass())) {
232 if (commonClass.equals(type)) {
233 throw new RuntimeException();
234 }
235 commonClass = commonClass.getSuperclass();
236 }
237 }
238
239 getSession().merge(x);
240
241 Set<ReferringObjectMetadata> referringObjectMetas = referringObjectMetadataFactory.get(x.getClass());
242
243 for (ReferringObjectMetadata referringObjectMetadata : referringObjectMetas) {
244
245 List<CdmBase> referringObjects = referringObjectMetadata.getReferringObjects(x, getSession());
246
247 for (CdmBase referringObject : referringObjects) {
248 try {
249 referringObjectMetadata.replace(referringObject, x, y);
250 getSession().update(referringObject);
251
252 } catch (IllegalArgumentException e) {
253 throw new RuntimeException(e.getMessage(), e);
254 } catch (IllegalAccessException e) {
255 throw new RuntimeException(e.getMessage(), e);
256 }
257 }
258 }
259 return y;
260 }
261
262 @Override
263 public Session getSession() throws DataAccessException {
264 return super.getSession();
265 }
266
267 @Override
268 public void clear() throws DataAccessException {
269 Session session = getSession();
270 session.clear();
271 if (logger.isDebugEnabled()) {
272 logger.debug("dao clear end");
273 }
274 }
275
276 @Override
277 public MergeResult<T> merge(T transientObject, boolean returnTransientEntity) throws DataAccessException {
278 Session session = getSession();
279 PostMergeEntityListener.addSession(session);
280 MergeResult<T> result = null;
281 try {
282 @SuppressWarnings("unchecked")
283 T persistentObject = (T) session.merge(transientObject);
284 if (logger.isDebugEnabled()) {
285 logger.debug("dao merge end");
286 }
287
288 if (returnTransientEntity) {
289 if (transientObject != null && persistentObject != null) {
290 transientObject.setId(persistentObject.getId());
291 }
292 result = new MergeResult(transientObject, PostMergeEntityListener.getNewEntities(session));
293 } else {
294 result = new MergeResult(persistentObject, null);
295 }
296 return result;
297 } finally {
298 PostMergeEntityListener.removeSession(session);
299 }
300 }
301
302 @Override
303 public T merge(T transientObject) throws DataAccessException {
304 Session session = getSession();
305 @SuppressWarnings("unchecked")
306 T persistentObject = (T) session.merge(transientObject);
307 if (logger.isDebugEnabled()) {
308 logger.debug("dao merge end");
309 }
310 return persistentObject;
311 }
312
313 @Override
314 public UUID saveOrUpdate(T transientObject) throws DataAccessException {
315 if (transientObject == null) {
316 logger.warn("Object to save should not be null. NOP");
317 return null;
318 }
319 try {
320 if (logger.isDebugEnabled()) {
321 logger.debug("dao saveOrUpdate start...");
322 }
323 if (logger.isDebugEnabled()) {
324 logger.debug("transientObject(" + transientObject.getClass().getSimpleName() + ") ID:"
325 + transientObject.getId() + ", UUID: " + transientObject.getUuid());
326 }
327 Session session = getSession();
328 if (transientObject.getId() != 0 && VersionableEntity.class.isAssignableFrom(transientObject.getClass())) {
329 VersionableEntity versionableEntity = (VersionableEntity) transientObject;
330 versionableEntity.setUpdated(new DateTime());
331 Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
332 if (authentication != null && authentication.getPrincipal() != null
333 && authentication.getPrincipal() instanceof User) {
334 User user = (User) authentication.getPrincipal();
335 versionableEntity.setUpdatedBy(user);
336 }
337 }
338 session.saveOrUpdate(transientObject);
339 if (logger.isDebugEnabled()) {
340 logger.debug("dao saveOrUpdate end");
341 }
342 return transientObject.getUuid();
343 } catch (NonUniqueObjectException e) {
344 logger.error("Error in CdmEntityDaoBase.saveOrUpdate(obj). ID=" + e.getIdentifier() + ". Class="
345 + e.getEntityName());
346 logger.error(e.getMessage());
347
348 e.printStackTrace();
349 throw e;
350 } catch (HibernateException e) {
351
352 e.printStackTrace();
353 throw e;
354 }
355 }
356
357 @Override
358 public <S extends T> S save(S newInstance) throws DataAccessException {
359 if (newInstance == null) {
360 logger.warn("Object to save should not be null. NOP");
361 return null;
362 }
363 getSession().save(newInstance);
364 return newInstance;
365 }
366
367 @Override
368 public UUID update(T transientObject) throws DataAccessException {
369 if (transientObject == null) {
370 logger.warn("Object to update should not be null. NOP");
371 return null;
372 }
373 getSession().update(transientObject);
374 return transientObject.getUuid();
375 }
376
377 @Override
378 public UUID refresh(T persistentObject) throws DataAccessException {
379 getSession().refresh(persistentObject);
380 return persistentObject.getUuid();
381 }
382
383 @Override
384 public UUID delete(T persistentObject) throws DataAccessException {
385 if (persistentObject == null) {
386 logger.warn(type.getName() + " was 'null'");
387 return null;
388 }
389
390 // Merge the object in if it is detached
391 //
392 // I think this is preferable to catching lazy initialization errors
393 // as that solution only swallows and hides the exception, but doesn't
394 // actually solve it.
395 persistentObject = (T) getSession().merge(persistentObject);
396 getSession().delete(persistentObject);
397 return persistentObject.getUuid();
398 }
399
400 @Override
401 public T findById(int id) throws DataAccessException {
402 return getSession().get(type, id);
403 }
404
405 @Override
406 public T findByUuid(UUID uuid) throws DataAccessException {
407 return this.findByUuid(uuid, INCLUDE_UNPUBLISHED);
408 }
409
410 protected T findByUuid(UUID uuid, boolean includeUnpublished) throws DataAccessException {
411 Session session = getSession();
412 Criteria crit = session.createCriteria(type);
413 crit.add(Restrictions.eq("uuid", uuid));
414 crit.addOrder(Order.desc("created"));
415 if (IPublishable.class.isAssignableFrom(type) && !includeUnpublished) {
416 crit.add(Restrictions.eq("publish", Boolean.TRUE));
417 }
418
419 @SuppressWarnings("unchecked")
420 List<T> results = crit.list();
421 Set<T> resultSet = new HashSet<>();
422 resultSet.addAll(results);
423 if (resultSet.isEmpty()) {
424 return null;
425 } else {
426 if (resultSet.size() > 1) {
427 logger.error("findByUuid() delivers more than one result for UUID: " + uuid);
428 }
429 return results.get(0);
430 }
431 }
432
433 @Override
434 public T findByUuidWithoutFlush(UUID uuid) throws DataAccessException {
435 Session session = getSession();
436 FlushMode currentFlushMode = session.getHibernateFlushMode();
437 try {
438 // set flush mode to manual so that the session does not flush
439 // when before performing the query
440 session.setHibernateFlushMode(FlushMode.MANUAL);
441 Criteria crit = session.createCriteria(type);
442 crit.add(Restrictions.eq("uuid", uuid));
443 crit.addOrder(Order.desc("created"));
444 @SuppressWarnings("unchecked")
445 List<T> results = crit.list();
446 if (results.isEmpty()) {
447 return null;
448 } else {
449 if (results.size() > 1) {
450 logger.error("findByUuid() delivers more than one result for UUID: " + uuid);
451 }
452 return results.get(0);
453 }
454 } finally {
455 // set back the session flush mode
456 if (currentFlushMode != null) {
457 session.setHibernateFlushMode(currentFlushMode);
458 }
459 }
460 }
461
462 @Override
463 public List<T> loadList(Collection<Integer> ids, List<OrderHint> orderHints, List<String> propertyPaths) throws DataAccessException {
464
465 if (ids.isEmpty()) {
466 return new ArrayList<>(0);
467 }
468
469 Criteria criteria = prepareList(null, ids, null, null, orderHints, "id");
470
471 if (logger.isDebugEnabled()) {
472 logger.debug(criteria.toString());
473 }
474
475 @SuppressWarnings("unchecked")
476 List<T> result = criteria.list();
477 defaultBeanInitializer.initializeAll(result, propertyPaths);
478 return result;
479 }
480
481 @Override
482 public List<T> list(Collection<UUID> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
483 List<String> propertyPaths) throws DataAccessException {
484
485 if (uuids == null || uuids.isEmpty()) {
486 return new ArrayList<>();
487 }
488
489 Criteria criteria = prepareList(null, uuids, pageSize, pageNumber, orderHints, "uuid");
490 @SuppressWarnings("unchecked")
491 List<T> result = criteria.list();
492 defaultBeanInitializer.initializeAll(result, propertyPaths);
493 return result;
494 }
495
496 @Override
497 public <S extends T> List<S> list(Class<S> clazz, Collection<UUID> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
498 List<String> propertyPaths) throws DataAccessException {
499
500 if (uuids == null || uuids.isEmpty()) {
501 return new ArrayList<>();
502 }
503
504 Criteria criteria = prepareList(clazz, uuids, pageSize, pageNumber, orderHints, "uuid");
505 @SuppressWarnings("unchecked")
506 List<S> result = criteria.list();
507 defaultBeanInitializer.initializeAll(result, propertyPaths);
508 return result;
509 }
510
511 @Override
512 public <S extends T> List<S> list(Class<S> type, List<Restriction<?>> restrictions, Integer limit, Integer start,
513 List<OrderHint> orderHints, List<String> propertyPaths) {
514
515 Criteria criteria = createCriteria(type, restrictions, false);
516
517 addLimitAndStart(criteria, limit, start);
518 addOrder(criteria, orderHints);
519
520 @SuppressWarnings("unchecked")
521 List<S> result = criteria.list();
522 defaultBeanInitializer.initializeAll(result, propertyPaths);
523 return result;
524 }
525
526 /**
527 * @param restrictions
528 * @param criteria
529 */
530 private void addRestrictions(List<Restriction<?>> restrictions, DetachedCriteria criteria) {
531
532 if(restrictions == null || restrictions.isEmpty()){
533 return ;
534 }
535
536 List<CriterionWithOperator> perProperty = new ArrayList<>(restrictions.size());
537 Map<String, String> aliases = new HashMap<>();
538
539
540
541 for(Restriction<?> restriction : restrictions){
542 Collection<? extends Object> values = restriction.getValues();
543 JoinType jointype = LEFTOUTER_OPS.contains(restriction.getOperator()) ? JoinType.LEFT_OUTER_JOIN : JoinType.INNER_JOIN;
544 if(values != null && !values.isEmpty()){
545 // ---
546 String propertyPath = restriction.getPropertyName();
547 String[] props = propertyPath.split("\\.");
548 String propertyName;
549 if(props.length == 1){
550 // direct property of the base type of the criteria
551 propertyName = propertyPath;
552 } else {
553 // create aliases if the propertyName is a dot separated property path
554 String aĺiasKey = jointype.name() + "_";
555 String aliasedProperty = null;
556 String alias = "";
557 for(int p = 0; p < props.length -1; p++){
558 aĺiasKey = aĺiasKey + (aĺiasKey.isEmpty() ? "" : ".") + props[p];
559 aliasedProperty = alias + (alias.isEmpty() ? "" : ".") + props[p];
560 if(!aliases.containsKey(aliasedProperty)){
561 alias = alias + (alias.isEmpty() ? "" : "_" ) + props[p];
562 aliases.put(aĺiasKey, alias);
563 criteria.createAlias(aliasedProperty, alias, jointype);
564 if(logger.isDebugEnabled()){
565 logger.debug("addRestrictions() alias created with aliasKey " + aĺiasKey + " => " + aliasedProperty + " as " + alias);
566 }
567 }
568 }
569 propertyName = alias + "." + props[props.length -1];
570 }
571 // ---
572 Criterion[] predicates = new Criterion[values.size()];
573 int i = 0;
574 for(Object v : values){
575 Criterion criterion = createRestriction(propertyName, v, restriction.getMatchMode());
576 if(restriction.isNot()){
577 if(props.length > 1){
578 criterion = Restrictions.or(Restrictions.not(criterion), Restrictions.isNull(propertyName));
579 } else {
580 criterion = Restrictions.not(criterion);
581 }
582 }
583 predicates[i++] = criterion;
584 if(logger.isDebugEnabled()){
585 logger.debug("addRestrictions() predicate with " + propertyName + " " + (restriction.getMatchMode() == null ? "=" : restriction.getMatchMode().name()) + " " + v.toString());
586 }
587 }
588 if(restriction.getOperator() == Operator.AND_NOT){
589 perProperty.add(new CriterionWithOperator(restriction.getOperator(), Restrictions.and(predicates)));
590 } else {
591 perProperty.add(new CriterionWithOperator(restriction.getOperator(), Restrictions.or(predicates)));
592 }
593 } // check has values
594 } // loop over restrictions
595
596 Restriction.Operator firstOperator = null;
597 if(!perProperty.isEmpty()){
598 LogicalExpression logicalExpression = null;
599 for(CriterionWithOperator cwo : perProperty){
600 if(logicalExpression == null){
601 firstOperator = cwo.operator;
602 logicalExpression = Restrictions.and(Restrictions.sqlRestriction("1=1"), cwo.criterion);
603 } else {
604 switch(cwo.operator){
605 case AND:
606 case AND_NOT:
607 logicalExpression = Restrictions.and(logicalExpression, cwo.criterion);
608 break;
609 case OR:
610 case OR_NOT:
611 logicalExpression = Restrictions.or(logicalExpression, cwo.criterion);
612 break;
613 default:
614 throw new RuntimeException("Unsupported Operator");
615 }
616 }
617
618 }
619
620
621 criteria.add(logicalExpression);
622 // if(firstOperator == Operator.OR){
623 // // OR
624 // } else {
625 // // AND
626 // criteria.add(Restrictions.and(queryStringCriterion, logicalExpression));
627 // }
628 }
629 if(logger.isDebugEnabled()){
630 logger.debug("addRestrictions() final criteria: " + criteria.toString());
631 }
632 }
633
634 /**
635 * @param propertyName
636 * @param value
637 * @param matchMode
638 * is only applied if the <code>value</code> is a
639 * <code>String</code> object
640 * @param criteria
641 * @return
642 */
643 private Criterion createRestriction(String propertyName, Object value, MatchMode matchMode) {
644
645 Criterion restriction;
646 if (value == null) {
647 if (logger.isDebugEnabled()) {
648 logger.debug("createRestriction() " + propertyName + " is null ");
649 }
650 restriction = Restrictions.isNull(propertyName);
651 } else if (matchMode == null || !(value instanceof String)) {
652 if (logger.isDebugEnabled()) {
653 logger.debug("createRestriction() " + propertyName + " = " + value.toString());
654 }
655 restriction = Restrictions.eq(propertyName, value);
656 } else {
657 String queryString = (String) value;
658 if (logger.isDebugEnabled()) {
659 logger.debug("createRestriction() " + propertyName + " " + matchMode.getMatchOperator() + " "
660 + matchMode.queryStringFrom(queryString));
661 }
662 switch(matchMode){
663 case BEGINNING:
664 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.START);
665 break;
666 case END:
667 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.END);
668 break;
669 case LIKE:
670 restriction = Restrictions.ilike(propertyName, matchMode.queryStringFrom(queryString), org.hibernate.criterion.MatchMode.ANYWHERE);
671 break;
672 case EXACT:
673 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.EXACT);
674 break;
675 case ANYWHERE:
676 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.ANYWHERE);
677 break;
678 default:
679 throw new RuntimeException("Unknown MatchMode: " + matchMode.name());
680 }
681 }
682 return restriction;
683 }
684
685 /**
686 * {@inheritDoc}
687 */
688 @Override
689 public long count(Class<? extends T> type, List<Restriction<?>> restrictions) {
690
691 Criteria criteria = createCriteria(type, restrictions, false);
692
693 criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
694
695 return (Long) criteria.uniqueResult();
696
697 }
698
699 /**
700 * @param uuids
701 * @param pageSize
702 * @param pageNumber
703 * @param orderHints
704 * @param propertyName
705 * @return
706 */
707 private Criteria prepareList(Class<? extends T> clazz, Collection<?> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
708 String propertyName) {
709 if (clazz == null){
710 clazz = type;
711 }
712 Criteria criteria = getSession().createCriteria(clazz);
713 criteria.add(Restrictions.in(propertyName, uuids));
714
715 if (pageSize != null) {
716 criteria.setMaxResults(pageSize);
717 if (pageNumber != null) {
718 criteria.setFirstResult(pageNumber * pageSize);
719 } else {
720 criteria.setFirstResult(0);
721 }
722 }
723
724 if (orderHints == null) {
725 orderHints = OrderHint.defaultOrderHintsFor(type);
726 }
727 addOrder(criteria, orderHints);
728 return criteria;
729 }
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 loadWithoutInitializing(int id){
767 return this.getSession().load(type, id);
768 }
769
770 @Override
771 public T load(UUID uuid, List<String> propertyPaths) {
772 return this.load(uuid, INCLUDE_UNPUBLISHED, propertyPaths);
773 }
774
775 protected T load(UUID uuid, boolean includeUnpublished, List<String> propertyPaths) {
776 T bean = findByUuid(uuid, includeUnpublished);
777 if (bean == null) {
778 return bean;
779 }
780 defaultBeanInitializer.initialize(bean, propertyPaths);
781
782 return bean;
783 }
784
785 @Override
786 public Boolean exists(UUID uuid) {
787 if (findByUuid(uuid) == null) {
788 return false;
789 }
790 return true;
791 }
792
793 @Override
794 public long count() {
795 return count(type);
796 }
797
798 @Override
799 public long count(Class<? extends T> clazz) {
800 Session session = getSession();
801 Criteria criteria = null;
802 if (clazz == null) {
803 criteria = session.createCriteria(type);
804 } else {
805 criteria = session.createCriteria(clazz);
806 }
807 criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
808
809 // since hibernate 4 (or so) uniqueResult returns Long, not Integer,
810 // therefore needs
811 // to be casted. Think about returning long rather then int!
812 return (long) criteria.uniqueResult();
813 }
814
815 @Override
816 public List<T> list(Integer limit, Integer start) {
817 return list(limit, start, null);
818 }
819
820 @Override
821 public List<Object[]> group(Class<? extends T> clazz, Integer limit, Integer start, List<Grouping> groups,
822 List<String> propertyPaths) {
823
824 Criteria criteria = null;
825 criteria = criterionForType(clazz);
826
827 addGroups(criteria, groups);
828
829 if (limit != null) {
830 criteria.setFirstResult(start);
831 criteria.setMaxResults(limit);
832 }
833
834 @SuppressWarnings("unchecked")
835 List<Object[]> result = criteria.list();
836
837 if (propertyPaths != null && !propertyPaths.isEmpty()) {
838 for (Object[] objects : result) {
839 defaultBeanInitializer.initialize(objects[0], propertyPaths);
840 }
841 }
842
843 return result;
844 }
845
846 protected void countGroups(DetachedCriteria criteria, List<Grouping> groups) {
847 if (groups != null) {
848
849 Map<String, String> aliases = new HashMap<String, String>();
850
851 for (Grouping grouping : groups) {
852 if (grouping.getAssociatedObj() != null) {
853 String alias = null;
854 if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
855 alias = grouping.getAssociatedObjectAlias();
856 aliases.put(grouping.getAssociatedObj(), alias);
857 criteria.createAlias(grouping.getAssociatedObj(), alias);
858 }
859 }
860 }
861
862 ProjectionList projectionList = Projections.projectionList();
863
864 for (Grouping grouping : groups) {
865 grouping.addProjection(projectionList);
866 }
867 criteria.setProjection(projectionList);
868 }
869 }
870
871 protected void addGroups(Criteria criteria, List<Grouping> groups) {
872 if (groups != null) {
873
874 Map<String, String> aliases = new HashMap<String, String>();
875
876 for (Grouping grouping : groups) {
877 if (grouping.getAssociatedObj() != null) {
878 String alias = null;
879 if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
880 alias = grouping.getAssociatedObjectAlias();
881 aliases.put(grouping.getAssociatedObj(), alias);
882 criteria.createAlias(grouping.getAssociatedObj(), alias);
883 }
884 }
885 }
886
887 ProjectionList projectionList = Projections.projectionList();
888
889 for (Grouping grouping : groups) {
890 grouping.addProjection(projectionList);
891 }
892 criteria.setProjection(projectionList);
893
894 for (Grouping grouping : groups) {
895 grouping.addOrder(criteria);
896
897 }
898 }
899 }
900
901 @Override
902 public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints) {
903 return list(limit, start, orderHints, null);
904 }
905
906 @Override
907 public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
908 Criteria criteria = getSession().createCriteria(type);
909 if (limit != null) {
910 criteria.setFirstResult(start);
911 criteria.setMaxResults(limit);
912 }
913
914 addOrder(criteria, orderHints);
915 @SuppressWarnings("unchecked")
916 List<T> results = criteria.list();
917
918 defaultBeanInitializer.initializeAll(results, propertyPaths);
919 return results;
920 }
921
922 @Override
923 public <S extends T> List<S> list(Class<S> clazz, Integer limit, Integer start, List<OrderHint> orderHints,
924 List<String> propertyPaths) {
925 Criteria criteria = null;
926 if (clazz == null) {
927 criteria = getSession().createCriteria(type);
928 } else {
929 criteria = getSession().createCriteria(clazz);
930 }
931
932 addLimitAndStart(criteria, limit, start);
933
934 addOrder(criteria, orderHints);
935
936 @SuppressWarnings("unchecked")
937 List<S> results = criteria.list();
938
939 defaultBeanInitializer.initializeAll(results, propertyPaths);
940 return results;
941 }
942
943 public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start, List<OrderHint> orderHints) {
944 return list(type, limit, start, orderHints, null);
945 }
946
947 @Override
948 public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start) {
949 return list(type, limit, start, null, null);
950 }
951
952 @Override
953 public Class<T> getType() {
954 return type;
955 }
956
957 @Override
958 public long count(T example, Set<String> includeProperties) {
959 Criteria criteria = getSession().createCriteria(example.getClass());
960 addExample(criteria, example, includeProperties);
961
962 criteria.setProjection(Projections.rowCount());
963 return (Long) criteria.uniqueResult();
964 }
965
966 protected void addExample(Criteria criteria, T example, Set<String> includeProperties) {
967 if (includeProperties != null && !includeProperties.isEmpty()) {
968 criteria.add(Example.create(example).setPropertySelector(new PropertySelectorImpl(includeProperties)));
969 ClassMetadata classMetadata = getSession().getSessionFactory().getClassMetadata(example.getClass());
970 for (String property : includeProperties) {
971 Type type = classMetadata.getPropertyType(property);
972 if (type.isEntityType()) {
973 try {
974 Field field = ReflectionUtils.findField(example.getClass(), property);
975 field.setAccessible(true);
976 Object value = field.get(example);
977 if (value != null) {
978 criteria.add(Restrictions.eq(property, value));
979 } else {
980 criteria.add(Restrictions.isNull(property));
981 }
982 } catch (SecurityException se) {
983 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
984 se);
985 } catch (HibernateException he) {
986 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
987 he);
988 } catch (IllegalArgumentException iae) {
989 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
990 iae);
991 } catch (IllegalAccessException ie) {
992 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
993 ie);
994 }
995 }
996 }
997 } else {
998 criteria.add(Example.create(example));
999 }
1000 }
1001
1002 /**
1003 *
1004 * NOTE: We can't reuse
1005 * {@link #list(Class, String, Object, MatchMode, Integer, Integer, List, List)
1006 * here due to different default behavior of the <code>matchmode</code>
1007 * parameter.
1008 *
1009 * @param clazz
1010 * @param param
1011 * @param queryString
1012 * @param matchmode
1013 * @param criterion
1014 * @param pageSize
1015 * @param pageNumber
1016 * @param orderHints
1017 * @param propertyPaths
1018 * @return
1019 */
1020 @Override
1021 public <S extends T> List<S> findByParam(Class<S> clazz, String param, String queryString, MatchMode matchmode,
1022 List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
1023 List<String> propertyPaths) {
1024 Set<String> stringSet = new HashSet<>();
1025 stringSet.add(param);
1026 return this.findByParam(clazz, stringSet, queryString, matchmode,
1027 criterion, pageSize, pageNumber, orderHints,
1028 propertyPaths);
1029 }
1030
1031 @Override
1032 public <S extends T> List<S> findByParam(Class<S> clazz, Set<String> params, String queryString, MatchMode matchmode,
1033 List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
1034 List<String> propertyPaths) {
1035
1036 Criteria criteria = criterionForType(clazz);
1037
1038 if (queryString != null) {
1039 Set<Criterion> criterions = new HashSet<>();
1040 for (String param: params){
1041 Criterion crit;
1042 if (matchmode == null) {
1043 crit = Restrictions.ilike(param, queryString);
1044 } else if (matchmode == MatchMode.BEGINNING) {
1045 crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START);
1046 } else if (matchmode == MatchMode.END) {
1047 crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END);
1048 } else if (matchmode == MatchMode.EXACT) {
1049 crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT);
1050 } else {
1051 crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE);
1052 }
1053 criterions.add(crit);
1054 }
1055 if (criterions.size()>1){
1056 Iterator<Criterion> critIterator = criterions.iterator();
1057 Disjunction disjunction = Restrictions.disjunction();
1058 while (critIterator.hasNext()){
1059 disjunction.add(critIterator.next());
1060 }
1061 criteria.add(disjunction);
1062 }else{
1063 if (!criterions.isEmpty()){
1064 criteria.add(criterions.iterator().next());
1065 }
1066 }
1067 }
1068
1069 addCriteria(criteria, criterion);
1070
1071 addPageSizeAndNumber(criteria, pageSize, pageNumber);
1072 addOrder(criteria, orderHints);
1073
1074 @SuppressWarnings("unchecked")
1075 List<S> result = criteria.list();
1076 defaultBeanInitializer.initializeAll(result, propertyPaths);
1077 return result;
1078 }
1079
1080 @Override
1081 public long countByParam(Class<? extends T> clazz, String param, String queryString, MatchMode matchmode,
1082 List<Criterion> criterion) {
1083
1084 Criteria criteria = null;
1085
1086 criteria = criterionForType(clazz);
1087
1088 if (queryString != null) {
1089 if (matchmode == null) {
1090 criteria.add(Restrictions.ilike(param, queryString));
1091 } else if (matchmode == MatchMode.BEGINNING) {
1092 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START));
1093 } else if (matchmode == MatchMode.END) {
1094 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END));
1095 } else if (matchmode == MatchMode.EXACT) {
1096 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT));
1097 } else {
1098 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE));
1099 }
1100 }
1101
1102 addCriteria(criteria, criterion);
1103
1104 criteria.setProjection(Projections.rowCount());
1105
1106 return (Long) criteria.uniqueResult();
1107 }
1108
1109 /**
1110 * Creates a criteria query for the CDM <code>type</code> either for counting or listing matching entities.
1111 * <p>
1112 * The set of matching entities can be restricted by passing a list of {@link Restriction} objects.
1113 * Restrictions can logically combined:
1114 <pre>
1115 Arrays.asList(
1116 new Restriction<String>("titleCache", MatchMode.ANYWHERE, "foo"),
1117 new Restriction<String>("institute.name", Operator.OR, MatchMode.BEGINNING, "Bar")
1118 );
1119 </pre>
1120 * The first Restriction in the example above by default has the <code>Operator.AND</code> which will be
1121 * ignored since this is the first restriction. The <code>Operator</code> of further restrictions in the
1122 * list are used to combine with the previous restriction.
1123 *
1124 * @param type
1125 * @param restrictions
1126 * @param doCount
1127 * @return
1128 */
1129 protected Criteria createCriteria(Class<? extends T> type, List<Restriction<?>> restrictions, boolean doCount) {
1130
1131 DetachedCriteria idsOnlyCriteria = DetachedCriteria.forClass(entityType(type));
1132 idsOnlyCriteria.setProjection(Projections.distinct(Projections.id()));
1133
1134 addRestrictions(restrictions, idsOnlyCriteria);
1135
1136 Criteria criteria = criterionForType(type);
1137 criteria.add(Subqueries.propertyIn("id", idsOnlyCriteria));
1138
1139 if(doCount){
1140 criteria.setProjection(Projections.rowCount());
1141 } else {
1142 idsOnlyCriteria.setProjection(Projections.distinct(Projections.property("id")));
1143 }
1144
1145 return criteria;
1146 }
1147
1148
1149 @Override
1150 public <S extends T> List<S> findByParamWithRestrictions(Class<S> clazz, String param, String queryString,
1151 MatchMode matchmode, List<Restriction<?>> restrictions, Integer pageSize, Integer pageNumber,
1152 List<OrderHint> orderHints, List<String> propertyPaths) {
1153
1154 List<Restriction<?>> allRestrictions = new ArrayList<>();
1155 allRestrictions.add(new Restriction<String>(param, matchmode, queryString));
1156 if(restrictions != null){
1157 allRestrictions.addAll(restrictions);
1158 }
1159 Criteria criteria = createCriteria(clazz, allRestrictions, false);
1160
1161 addPageSizeAndNumber(criteria, pageSize, pageNumber);
1162
1163 addOrder(criteria, orderHints);
1164
1165 @SuppressWarnings("unchecked")
1166 List<S> result = criteria.list();
1167 defaultBeanInitializer.initializeAll(result, propertyPaths);
1168 return result;
1169
1170 }
1171
1172 @Override
1173 public long countByParamWithRestrictions(Class<? extends T> clazz, String param, String queryString,
1174 MatchMode matchmode, List<Restriction<?>> restrictions) {
1175
1176 List<Restriction<?>> allRestrictions = new ArrayList<>();
1177 allRestrictions.add(new Restriction<String>(param, matchmode, queryString));
1178 if(restrictions != null){
1179 allRestrictions.addAll(restrictions);
1180 }
1181 Criteria criteria = createCriteria(clazz, allRestrictions, true);
1182
1183 return (Long) criteria.uniqueResult();
1184 }
1185
1186 @Override
1187 public <S extends T> List<S> list(S example, Set<String> includeProperties, Integer limit, Integer start,
1188 List<OrderHint> orderHints, List<String> propertyPaths) {
1189 Criteria criteria = getSession().createCriteria(example.getClass());
1190 addExample(criteria, example, includeProperties);
1191
1192 addLimitAndStart(criteria, limit, start);
1193
1194 addOrder(criteria, orderHints);
1195
1196 @SuppressWarnings("unchecked")
1197 List<S> results = criteria.list();
1198 defaultBeanInitializer.initializeAll(results, propertyPaths);
1199 return results;
1200 }
1201
1202 private class PropertySelectorImpl implements PropertySelector {
1203
1204 private final Set<String> includeProperties;
1205 /**
1206 *
1207 */
1208 private static final long serialVersionUID = -3175311800911570546L;
1209
1210 public PropertySelectorImpl(Set<String> includeProperties) {
1211 this.includeProperties = includeProperties;
1212 }
1213
1214 @Override
1215 public boolean include(Object propertyValue, String propertyName, Type type) {
1216 if (includeProperties.contains(propertyName)) {
1217 return true;
1218 } else {
1219 return false;
1220 }
1221 }
1222
1223 }
1224
1225 private class CriterionWithOperator {
1226
1227 Restriction.Operator operator;
1228 Criterion criterion;
1229
1230
1231 public CriterionWithOperator(Operator operator, Criterion criterion) {
1232 super();
1233 this.operator = operator;
1234 this.criterion = criterion;
1235 }
1236
1237
1238 }
1239
1240 /**
1241 * Returns a Criteria for the given {@link Class class} or, if
1242 * <code>null</code>, for the base {@link Class class} of this DAO.
1243 *
1244 * @param clazz
1245 * @return the Criteria
1246 */
1247 protected Criteria getCriteria(Class<? extends CdmBase> clazz) {
1248 Criteria criteria = null;
1249 if (clazz == null) {
1250 criteria = getSession().createCriteria(type);
1251 } else {
1252 criteria = getSession().createCriteria(clazz);
1253 }
1254 return criteria;
1255 }
1256
1257 /**
1258 * @param clazz
1259 * @param auditEvent
1260 * @return
1261 */
1262 protected AuditQuery makeAuditQuery(Class<? extends CdmBase> clazz, AuditEvent auditEvent) {
1263 AuditQuery query = null;
1264
1265 if (clazz == null) {
1266 query = getAuditReader().createQuery().forEntitiesAtRevision(type, auditEvent.getRevisionNumber());
1267 } else {
1268 query = getAuditReader().createQuery().forEntitiesAtRevision(clazz, auditEvent.getRevisionNumber());
1269 }
1270 return query;
1271 }
1272
1273 protected AuditReader getAuditReader() {
1274 return AuditReaderFactory.get(getSession());
1275 }
1276 }