ref #7590 documentation
[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.HashMap;
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.UUID;
22
23 import org.apache.log4j.Logger;
24 import org.hibernate.Criteria;
25 import org.hibernate.FlushMode;
26 import org.hibernate.HibernateException;
27 import org.hibernate.LockOptions;
28 import org.hibernate.NonUniqueObjectException;
29 import org.hibernate.Query;
30 import org.hibernate.Session;
31 import org.hibernate.criterion.Criterion;
32 import org.hibernate.criterion.DetachedCriteria;
33 import org.hibernate.criterion.Example;
34 import org.hibernate.criterion.Example.PropertySelector;
35 import org.hibernate.criterion.LogicalExpression;
36 import org.hibernate.criterion.Order;
37 import org.hibernate.criterion.ProjectionList;
38 import org.hibernate.criterion.Projections;
39 import org.hibernate.criterion.Restrictions;
40 import org.hibernate.criterion.Subqueries;
41 import org.hibernate.envers.AuditReader;
42 import org.hibernate.envers.AuditReaderFactory;
43 import org.hibernate.envers.query.AuditQuery;
44 import org.hibernate.metadata.ClassMetadata;
45 import org.hibernate.sql.JoinType;
46 import org.hibernate.type.Type;
47 import org.joda.time.DateTime;
48 import org.springframework.beans.factory.annotation.Autowired;
49 import org.springframework.dao.DataAccessException;
50 import org.springframework.dao.InvalidDataAccessApiUsageException;
51 import org.springframework.security.core.Authentication;
52 import org.springframework.security.core.context.SecurityContextHolder;
53 import org.springframework.stereotype.Repository;
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.User;
59 import eu.etaxonomy.cdm.model.common.VersionableEntity;
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 * @author a.mueller FIXME CdmEntityDaoBase is abstract, can it be annotated
75 * with @Repository?
76 */
77 @Repository
78 public abstract class CdmEntityDaoBase<T extends CdmBase> extends DaoBase implements ICdmEntityDao<T> {
79
80 private static final Logger logger = Logger.getLogger(CdmEntityDaoBase.class);
81
82 protected int flushAfterNo = 1000; // large numbers may cause
83 // synchronisation errors when commiting
84 // the session !!
85
86 protected Class<T> type;
87
88 @Autowired
89 // @Qualifier("defaultBeanInitializer")
90 protected IBeanInitializer defaultBeanInitializer;
91
92 public void setDefaultBeanInitializer(IBeanInitializer defaultBeanInitializer) {
93 this.defaultBeanInitializer = defaultBeanInitializer;
94 }
95
96 @Autowired
97 private ReferringObjectMetadataFactory referringObjectMetadataFactory;
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<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<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 T save(T 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.getFlushMode();
437 try {
438 // set flush mode to manual so that the session does not flush
439 // when before performing the query
440 session.setFlushMode(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.setFlushMode(currentFlushMode);
458 }
459 }
460 }
461
462 @Override
463 public List<T> loadList(Collection<Integer> ids, List<String> propertyPaths) throws DataAccessException {
464
465 if (ids.isEmpty()) {
466 return new ArrayList<T>(0);
467 }
468
469 Criteria criteria = prepareList(ids, null, null, null, "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(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 /**
497 * {@inheritDoc}
498 */
499 @Override
500 public List<T> list(Class<? extends T> type, List<Restriction<?>> restrictions, Integer limit, Integer start,
501 List<OrderHint> orderHints, List<String> propertyPaths) {
502
503 Criteria criteria = createCriteria(type, restrictions, false);
504
505 addLimitAndStart(criteria, limit, start);
506 addOrder(criteria, orderHints);
507
508 @SuppressWarnings("unchecked")
509 List<T> result = criteria.list();
510 defaultBeanInitializer.initializeAll(result, propertyPaths);
511 return result;
512 }
513
514 /**
515 * @param restrictions
516 * @param criteria
517 */
518 private void addRestrictions(List<Restriction<?>> restrictions, DetachedCriteria criteria) {
519
520 if(restrictions == null || restrictions.isEmpty()){
521 return ;
522 }
523
524 List<CriterionWithOperator> perProperty = new ArrayList<>(restrictions.size());
525 Map<String, String> aliases = new HashMap<>();
526
527 for(Restriction<?> restriction : restrictions){
528 Collection<? extends Object> values = restriction.getValues();
529 JoinType jointype = restriction.isNot() ? JoinType.LEFT_OUTER_JOIN : JoinType.INNER_JOIN;
530 if(values != null && !values.isEmpty()){
531 // ---
532 String propertyPath = restriction.getPropertyName();
533 String[] props = propertyPath.split("\\.");
534 String propertyName;
535 if(props.length == 1){
536 // direct property of the base type of the criteria
537 propertyName = propertyPath;
538 } else {
539 // create aliases if the propertyName is a dot separated property path
540 String aĺiasKey = jointype.name() + "_";
541 String aliasedProperty = null;
542 String alias = "";
543 for(int p = 0; p < props.length -1; p++){
544 aĺiasKey = aĺiasKey + (aĺiasKey.isEmpty() ? "" : ".") + props[p];
545 aliasedProperty = alias + (alias.isEmpty() ? "" : ".") + props[p];
546 if(!aliases.containsKey(aliasedProperty)){
547 alias = alias + (alias.isEmpty() ? "" : "_" ) + props[p];
548 aliases.put(aĺiasKey, alias);
549 criteria.createAlias(aliasedProperty, alias, jointype);
550 if(logger.isDebugEnabled()){
551 logger.debug("addRestrictions() alias created with aliasKey " + aĺiasKey + " => " + aliasedProperty + " as " + alias);
552 }
553 }
554 }
555 propertyName = alias + "." + props[props.length -1];
556 }
557 // ---
558 Criterion[] predicates = new Criterion[values.size()];
559 int i = 0;
560 for(Object v : values){
561 Criterion criterion = createRestriction(propertyName, v, restriction.getMatchMode());
562 if(restriction.isNot()){
563 if(props.length > 1){
564 criterion = Restrictions.or(Restrictions.not(criterion), Restrictions.isNull(propertyName));
565 } else {
566 criterion = Restrictions.not(criterion);
567 }
568 }
569 predicates[i++] = criterion;
570 if(logger.isDebugEnabled()){
571 logger.debug("addRestrictions() predicate with " + propertyName + " " + (restriction.getMatchMode() == null ? "=" : restriction.getMatchMode().name()) + " " + v.toString());
572 }
573 }
574 perProperty.add(new CriterionWithOperator(restriction.getOperator(), Restrictions.or(predicates)));
575 } // check has values
576 } // loop over restrictions
577
578 Restriction.Operator firstOperator = null;
579 if(!perProperty.isEmpty()){
580 LogicalExpression logicalExpression = null;
581 for(CriterionWithOperator cwo : perProperty){
582 if(logicalExpression == null){
583 firstOperator = cwo.operator;
584 logicalExpression = Restrictions.and(Restrictions.sqlRestriction("1=1"), cwo.criterion);
585 } else {
586 switch(cwo.operator){
587 case AND:
588 logicalExpression = Restrictions.and(logicalExpression, cwo.criterion);
589 break;
590 case OR:
591 logicalExpression = Restrictions.or(logicalExpression, cwo.criterion);
592 break;
593 default:
594 throw new RuntimeException("Unsupported Operator");
595 }
596 }
597
598 }
599
600
601 criteria.add(logicalExpression);
602 // if(firstOperator == Operator.OR){
603 // // OR
604 // } else {
605 // // AND
606 // criteria.add(Restrictions.and(queryStringCriterion, logicalExpression));
607 // }
608 }
609 if(logger.isDebugEnabled()){
610 logger.debug("addRestrictions() final criteria: " + criteria.toString());
611 }
612 }
613
614 /**
615 * @param propertyName
616 * @param value
617 * @param matchMode
618 * is only applied if the <code>value</code> is a
619 * <code>String</code> object
620 * @param criteria
621 * @return
622 */
623 private Criterion createRestriction(String propertyName, Object value, MatchMode matchMode) {
624
625 Criterion restriction;
626 if (value == null) {
627 if (logger.isDebugEnabled()) {
628 logger.debug("createRestriction() " + propertyName + " is null ");
629 }
630 restriction = Restrictions.isNull(propertyName);
631 } else if (matchMode == null || !(value instanceof String)) {
632 if (logger.isDebugEnabled()) {
633 logger.debug("createRestriction() " + propertyName + " = " + value.toString());
634 }
635 restriction = Restrictions.eq(propertyName, value);
636 } else {
637 String queryString = (String) value;
638 if (logger.isDebugEnabled()) {
639 logger.debug("createRestriction() " + propertyName + " " + matchMode.getMatchOperator() + " "
640 + matchMode.queryStringFrom(queryString));
641 }
642 if (matchMode == MatchMode.BEGINNING) {
643 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.START);
644 } else if (matchMode == MatchMode.END) {
645 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.END);
646 } else if (matchMode == MatchMode.EXACT) {
647 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.EXACT);
648 } else {
649 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.ANYWHERE);
650 }
651 }
652 return restriction;
653 }
654
655 /**
656 * {@inheritDoc}
657 */
658 @Override
659 public long count(Class<? extends T> type, List<Restriction<?>> restrictions) {
660
661 Criteria criteria = createCriteria(type, restrictions, false);
662
663 criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
664
665 return (Long) criteria.uniqueResult();
666
667 }
668
669 /**
670 * @param uuids
671 * @param pageSize
672 * @param pageNumber
673 * @param orderHints
674 * @param propertyName
675 * @return
676 */
677 private Criteria prepareList(Collection<?> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
678 String propertyName) {
679 Criteria criteria = getSession().createCriteria(type);
680 criteria.add(Restrictions.in(propertyName, uuids));
681
682 if (pageSize != null) {
683 criteria.setMaxResults(pageSize);
684 if (pageNumber != null) {
685 criteria.setFirstResult(pageNumber * pageSize);
686 } else {
687 criteria.setFirstResult(0);
688 }
689 }
690
691 if (orderHints == null) {
692 orderHints = OrderHint.defaultOrderHintsFor(type);
693 }
694 addOrder(criteria, orderHints);
695 return criteria;
696 }
697
698 /**
699 * @param clazz
700 * @return
701 */
702 private Criteria criterionForType(Class<? extends T> clazz) {
703 return getSession().createCriteria(entityType(clazz));
704 }
705
706 protected Class<? extends T> entityType(Class<? extends T> clazz){
707 if (clazz != null) {
708 return clazz;
709 } else {
710 return type;
711 }
712 }
713
714 @Override
715 public T load(UUID uuid) {
716 T bean = findByUuid(uuid);
717 if (bean == null) {
718 return null;
719 }
720 defaultBeanInitializer.load(bean);
721
722 return bean;
723 }
724
725 @Override
726 public T load(int id, List<String> propertyPaths) {
727 T bean = findById(id);
728 if (bean == null) {
729 return bean;
730 }
731 defaultBeanInitializer.initialize(bean, propertyPaths);
732
733 return bean;
734 }
735
736 @Override
737 public T load(UUID uuid, List<String> propertyPaths) {
738 return this.load(uuid, INCLUDE_UNPUBLISHED, propertyPaths);
739 }
740
741 protected T load(UUID uuid, boolean includeUnpublished, List<String> propertyPaths) {
742 T bean = findByUuid(uuid, includeUnpublished);
743 if (bean == null) {
744 return bean;
745 }
746 defaultBeanInitializer.initialize(bean, propertyPaths);
747
748 return bean;
749 }
750
751 @Override
752 public Boolean exists(UUID uuid) {
753 if (findByUuid(uuid) == null) {
754 return false;
755 }
756 return true;
757 }
758
759 @Override
760 public long count() {
761 return count(type);
762 }
763
764 @Override
765 public long count(Class<? extends T> clazz) {
766 Session session = getSession();
767 Criteria criteria = null;
768 if (clazz == null) {
769 criteria = session.createCriteria(type);
770 } else {
771 criteria = session.createCriteria(clazz);
772 }
773 criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
774
775 // since hibernate 4 (or so) uniqueResult returns Long, not Integer,
776 // therefore needs
777 // to be casted. Think about returning long rather then int!
778 return (long) criteria.uniqueResult();
779 }
780
781 @Override
782 public List<T> list(Integer limit, Integer start) {
783 return list(limit, start, null);
784 }
785
786 @Override
787 public List<Object[]> group(Class<? extends T> clazz, Integer limit, Integer start, List<Grouping> groups,
788 List<String> propertyPaths) {
789
790 Criteria criteria = null;
791 criteria = criterionForType(clazz);
792
793 addGroups(criteria, groups);
794
795 if (limit != null) {
796 criteria.setFirstResult(start);
797 criteria.setMaxResults(limit);
798 }
799
800 @SuppressWarnings("unchecked")
801 List<Object[]> result = criteria.list();
802
803 if (propertyPaths != null && !propertyPaths.isEmpty()) {
804 for (Object[] objects : result) {
805 defaultBeanInitializer.initialize(objects[0], propertyPaths);
806 }
807 }
808
809 return result;
810 }
811
812 protected void countGroups(DetachedCriteria criteria, List<Grouping> groups) {
813 if (groups != null) {
814
815 Map<String, String> aliases = new HashMap<String, String>();
816
817 for (Grouping grouping : groups) {
818 if (grouping.getAssociatedObj() != null) {
819 String alias = null;
820 if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
821 alias = grouping.getAssociatedObjectAlias();
822 aliases.put(grouping.getAssociatedObj(), alias);
823 criteria.createAlias(grouping.getAssociatedObj(), alias);
824 }
825 }
826 }
827
828 ProjectionList projectionList = Projections.projectionList();
829
830 for (Grouping grouping : groups) {
831 grouping.addProjection(projectionList);
832 }
833 criteria.setProjection(projectionList);
834 }
835 }
836
837 protected void addGroups(Criteria criteria, List<Grouping> groups) {
838 if (groups != null) {
839
840 Map<String, String> aliases = new HashMap<String, String>();
841
842 for (Grouping grouping : groups) {
843 if (grouping.getAssociatedObj() != null) {
844 String alias = null;
845 if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
846 alias = grouping.getAssociatedObjectAlias();
847 aliases.put(grouping.getAssociatedObj(), alias);
848 criteria.createAlias(grouping.getAssociatedObj(), alias);
849 }
850 }
851 }
852
853 ProjectionList projectionList = Projections.projectionList();
854
855 for (Grouping grouping : groups) {
856 grouping.addProjection(projectionList);
857 }
858 criteria.setProjection(projectionList);
859
860 for (Grouping grouping : groups) {
861 grouping.addOrder(criteria);
862
863 }
864 }
865 }
866
867 @Override
868 public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints) {
869 return list(limit, start, orderHints, null);
870 }
871
872 @Override
873 public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
874 Criteria criteria = getSession().createCriteria(type);
875 if (limit != null) {
876 criteria.setFirstResult(start);
877 criteria.setMaxResults(limit);
878 }
879
880 addOrder(criteria, orderHints);
881 @SuppressWarnings("unchecked")
882 List<T> results = criteria.list();
883
884 defaultBeanInitializer.initializeAll(results, propertyPaths);
885 return results;
886 }
887
888 @Override
889 public <S extends T> List<S> list(Class<S> clazz, Integer limit, Integer start, List<OrderHint> orderHints,
890 List<String> propertyPaths) {
891 Criteria criteria = null;
892 if (clazz == null) {
893 criteria = getSession().createCriteria(type);
894 } else {
895 criteria = getSession().createCriteria(clazz);
896 }
897
898 addLimitAndStart(criteria, limit, start);
899
900 addOrder(criteria, orderHints);
901
902 @SuppressWarnings("unchecked")
903 List<S> results = criteria.list();
904
905 defaultBeanInitializer.initializeAll(results, propertyPaths);
906 return results;
907 }
908
909 public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start, List<OrderHint> orderHints) {
910 return list(type, limit, start, orderHints, null);
911 }
912
913 @Override
914 public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start) {
915 return list(type, limit, start, null, null);
916 }
917
918 @Override
919 public Class<T> getType() {
920 return type;
921 }
922
923 protected void setPagingParameter(Query query, Integer pageSize, Integer pageIndex) {
924 if (pageSize != null) {
925 query.setMaxResults(pageSize);
926 if (pageIndex != null) {
927 query.setFirstResult(pageIndex * pageSize);
928 } else {
929 query.setFirstResult(0);
930 }
931 }
932 }
933
934 protected void setPagingParameter(AuditQuery query, Integer pageSize, Integer pageIndex) {
935 if (pageSize != null) {
936 query.setMaxResults(pageSize);
937 if (pageIndex != null) {
938 query.setFirstResult(pageIndex * pageSize);
939 } else {
940 query.setFirstResult(0);
941 }
942 }
943 }
944
945 @Override
946 public long count(T example, Set<String> includeProperties) {
947 Criteria criteria = getSession().createCriteria(example.getClass());
948 addExample(criteria, example, includeProperties);
949
950 criteria.setProjection(Projections.rowCount());
951 return (Long) criteria.uniqueResult();
952 }
953
954 protected void addExample(Criteria criteria, T example, Set<String> includeProperties) {
955 if (includeProperties != null && !includeProperties.isEmpty()) {
956 criteria.add(Example.create(example).setPropertySelector(new PropertySelectorImpl(includeProperties)));
957 ClassMetadata classMetadata = getSession().getSessionFactory().getClassMetadata(example.getClass());
958 for (String property : includeProperties) {
959 Type type = classMetadata.getPropertyType(property);
960 if (type.isEntityType()) {
961 try {
962 Field field = ReflectionUtils.findField(example.getClass(), property);
963 field.setAccessible(true);
964 Object value = field.get(example);
965 if (value != null) {
966 criteria.add(Restrictions.eq(property, value));
967 } else {
968 criteria.add(Restrictions.isNull(property));
969 }
970 } catch (SecurityException se) {
971 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
972 se);
973 } catch (HibernateException he) {
974 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
975 he);
976 } catch (IllegalArgumentException iae) {
977 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
978 iae);
979 } catch (IllegalAccessException ie) {
980 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
981 ie);
982 }
983
984 }
985 }
986 } else {
987 criteria.add(Example.create(example));
988 }
989 }
990
991 /**
992 *
993 * NOTE: We can't reuse
994 * {@link #list(Class, String, Object, MatchMode, Integer, Integer, List, List)
995 * here due to different default behavior of the <code>matchmode</code>
996 * parameter.
997 *
998 * @param clazz
999 * @param param
1000 * @param queryString
1001 * @param matchmode
1002 * @param criterion
1003 * @param pageSize
1004 * @param pageNumber
1005 * @param orderHints
1006 * @param propertyPaths
1007 * @return
1008 */
1009 @Override
1010 public List<T> findByParam(Class<? extends T> clazz, String param, String queryString, MatchMode matchmode,
1011 List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
1012 List<String> propertyPaths) {
1013
1014 Criteria criteria = criterionForType(clazz);
1015
1016 if (queryString != null) {
1017 if (matchmode == null) {
1018 criteria.add(Restrictions.ilike(param, queryString));
1019 } else if (matchmode == MatchMode.BEGINNING) {
1020 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START));
1021 } else if (matchmode == MatchMode.END) {
1022 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END));
1023 } else if (matchmode == MatchMode.EXACT) {
1024 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT));
1025 } else {
1026 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE));
1027 }
1028 }
1029
1030 addCriteria(criteria, criterion);
1031
1032 if (pageSize != null) {
1033 criteria.setMaxResults(pageSize);
1034 if (pageNumber != null) {
1035 criteria.setFirstResult(pageNumber * pageSize);
1036 } else {
1037 criteria.setFirstResult(0);
1038 }
1039 }
1040
1041 addOrder(criteria, orderHints);
1042
1043 @SuppressWarnings("unchecked")
1044 List<T> result = criteria.list();
1045 defaultBeanInitializer.initializeAll(result, propertyPaths);
1046 return result;
1047 }
1048
1049 /**
1050 *
1051 * @param clazz
1052 * @param param
1053 * @param queryString
1054 * @param matchmode
1055 * @param criterion
1056 * @return
1057 */
1058 @Override
1059 public long countByParam(Class<? extends T> clazz, String param, String queryString, MatchMode matchmode,
1060 List<Criterion> criterion) {
1061
1062 Criteria criteria = null;
1063
1064 criteria = criterionForType(clazz);
1065
1066 if (queryString != null) {
1067 if (matchmode == null) {
1068 criteria.add(Restrictions.ilike(param, queryString));
1069 } else if (matchmode == MatchMode.BEGINNING) {
1070 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START));
1071 } else if (matchmode == MatchMode.END) {
1072 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END));
1073 } else if (matchmode == MatchMode.EXACT) {
1074 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT));
1075 } else {
1076 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE));
1077 }
1078 }
1079
1080 addCriteria(criteria, criterion);
1081
1082 criteria.setProjection(Projections.rowCount());
1083
1084 return (Long) criteria.uniqueResult();
1085 }
1086
1087 /**
1088 * Creates a criteria query for the CDM <code>type</code> either for counting or listing matching entities.
1089 * <p>
1090 * The set of matching entities can be restricted by passing a list of {@link Restriction} objects.
1091 * Restrictions can logically combined:
1092 <pre>
1093 Arrays.asList(
1094 new Restriction<String>("titleCache", MatchMode.ANYWHERE, "foo"),
1095 new Restriction<String>("institute.name", Operator.OR, MatchMode.BEGINNING, "Bar")
1096 );
1097 </pre>
1098 * The first Restriction in the example above by default has the <code>Operator.AND</code> which will be
1099 * ignored since this is the first restriction. The <code>Operator</code> of further restrictions in the
1100 * list are used to combine with the previous restriction.
1101 *
1102 * @param type
1103 * @param restrictions
1104 * @param doCount
1105 * @return
1106 */
1107 protected Criteria createCriteria(Class<? extends T> type, List<Restriction<?>> restrictions, boolean doCount) {
1108
1109 DetachedCriteria idsOnlyCriteria = DetachedCriteria.forClass(entityType(type));
1110 idsOnlyCriteria.setProjection(Projections.distinct(Projections.id()));
1111
1112 addRestrictions(restrictions, idsOnlyCriteria);
1113
1114 Criteria criteria = criterionForType(type);
1115 criteria.add(Subqueries.propertyIn("id", idsOnlyCriteria));
1116
1117 if(doCount){
1118 criteria.setProjection(Projections.rowCount());
1119 } else {
1120 idsOnlyCriteria.setProjection(Projections.distinct(Projections.property("id")));
1121 }
1122
1123 return criteria;
1124 }
1125
1126
1127 @Override
1128 public List<T> findByParamWithRestrictions(Class<? extends T> clazz, String param, String queryString,
1129 MatchMode matchmode, List<Restriction<?>> restrictions, Integer pageSize, Integer pageNumber,
1130 List<OrderHint> orderHints, List<String> propertyPaths) {
1131
1132 List<Restriction<?>> allRestrictions = new ArrayList<>();
1133 allRestrictions.add(new Restriction<String>(param, matchmode, queryString));
1134 if(restrictions != null){
1135 allRestrictions.addAll(restrictions);
1136 }
1137 Criteria criteria = createCriteria(clazz, allRestrictions, false);
1138
1139 if (pageSize != null) {
1140 criteria.setMaxResults(pageSize);
1141 if (pageNumber != null) {
1142 criteria.setFirstResult(pageNumber * pageSize);
1143 } else {
1144 criteria.setFirstResult(0);
1145 }
1146 }
1147
1148 addOrder(criteria, orderHints);
1149
1150 @SuppressWarnings("unchecked")
1151 List<T> result = criteria.list();
1152 defaultBeanInitializer.initializeAll(result, propertyPaths);
1153 return result;
1154
1155 }
1156
1157 @Override
1158 public long countByParamWithRestrictions(Class<? extends T> clazz, String param, String queryString,
1159 MatchMode matchmode, List<Restriction<?>> restrictions) {
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, true);
1167
1168 return (Long) criteria.uniqueResult();
1169 }
1170
1171 @Override
1172 public List<T> list(T example, Set<String> includeProperties, Integer limit, Integer start,
1173 List<OrderHint> orderHints, List<String> propertyPaths) {
1174 Criteria criteria = getSession().createCriteria(example.getClass());
1175 addExample(criteria, example, includeProperties);
1176
1177 addLimitAndStart(criteria, limit, start);
1178
1179 addOrder(criteria, orderHints);
1180
1181 @SuppressWarnings("unchecked")
1182 List<T> results = criteria.list();
1183 defaultBeanInitializer.initializeAll(results, propertyPaths);
1184 return results;
1185 }
1186
1187 private class PropertySelectorImpl implements PropertySelector {
1188
1189 private final Set<String> includeProperties;
1190 /**
1191 *
1192 */
1193 private static final long serialVersionUID = -3175311800911570546L;
1194
1195 public PropertySelectorImpl(Set<String> includeProperties) {
1196 this.includeProperties = includeProperties;
1197 }
1198
1199 @Override
1200 public boolean include(Object propertyValue, String propertyName, Type type) {
1201 if (includeProperties.contains(propertyName)) {
1202 return true;
1203 } else {
1204 return false;
1205 }
1206 }
1207
1208 }
1209
1210 private class CriterionWithOperator {
1211
1212 Restriction.Operator operator;
1213 Criterion criterion;
1214
1215
1216 public CriterionWithOperator(Operator operator, Criterion criterion) {
1217 super();
1218 this.operator = operator;
1219 this.criterion = criterion;
1220 }
1221
1222
1223 }
1224
1225 /**
1226 * Returns a Criteria for the given {@link Class class} or, if
1227 * <code>null</code>, for the base {@link Class class} of this DAO.
1228 *
1229 * @param clazz
1230 * @return the Criteria
1231 */
1232 protected Criteria getCriteria(Class<? extends CdmBase> clazz) {
1233 Criteria criteria = null;
1234 if (clazz == null) {
1235 criteria = getSession().createCriteria(type);
1236 } else {
1237 criteria = getSession().createCriteria(clazz);
1238 }
1239 return criteria;
1240 }
1241
1242 /**
1243 * @param clazz
1244 * @param auditEvent
1245 * @return
1246 */
1247 protected AuditQuery makeAuditQuery(Class<? extends CdmBase> clazz, AuditEvent auditEvent) {
1248 AuditQuery query = null;
1249
1250 if (clazz == null) {
1251 query = getAuditReader().createQuery().forEntitiesAtRevision(type, auditEvent.getRevisionNumber());
1252 } else {
1253 query = getAuditReader().createQuery().forEntitiesAtRevision(clazz, auditEvent.getRevisionNumber());
1254 }
1255 return query;
1256 }
1257
1258 protected AuditReader getAuditReader() {
1259 return AuditReaderFactory.get(getSession());
1260 }
1261 }