improve generics for collection save in service and dao layer
[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.Disjunction;
35 import org.hibernate.criterion.Example;
36 import org.hibernate.criterion.Example.PropertySelector;
37 import org.hibernate.criterion.LogicalExpression;
38 import org.hibernate.criterion.Order;
39 import org.hibernate.criterion.ProjectionList;
40 import org.hibernate.criterion.Projections;
41 import org.hibernate.criterion.Restrictions;
42 import org.hibernate.criterion.Subqueries;
43 import org.hibernate.envers.AuditReader;
44 import org.hibernate.envers.AuditReaderFactory;
45 import org.hibernate.envers.query.AuditQuery;
46 import org.hibernate.metadata.ClassMetadata;
47 import org.hibernate.sql.JoinType;
48 import org.hibernate.type.Type;
49 import org.joda.time.DateTime;
50 import org.springframework.beans.factory.annotation.Autowired;
51 import org.springframework.dao.DataAccessException;
52 import org.springframework.dao.InvalidDataAccessApiUsageException;
53 import org.springframework.security.core.Authentication;
54 import org.springframework.security.core.context.SecurityContextHolder;
55 import org.springframework.stereotype.Repository;
56 import org.springframework.util.ReflectionUtils;
57
58 import eu.etaxonomy.cdm.model.common.CdmBase;
59 import eu.etaxonomy.cdm.model.common.IPublishable;
60 import eu.etaxonomy.cdm.model.common.VersionableEntity;
61 import eu.etaxonomy.cdm.model.permission.User;
62 import eu.etaxonomy.cdm.model.view.AuditEvent;
63 import eu.etaxonomy.cdm.persistence.dao.common.ICdmEntityDao;
64 import eu.etaxonomy.cdm.persistence.dao.common.Restriction;
65 import eu.etaxonomy.cdm.persistence.dao.common.Restriction.Operator;
66 import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;
67 import eu.etaxonomy.cdm.persistence.dto.MergeResult;
68 import eu.etaxonomy.cdm.persistence.hibernate.PostMergeEntityListener;
69 import eu.etaxonomy.cdm.persistence.hibernate.replace.ReferringObjectMetadata;
70 import eu.etaxonomy.cdm.persistence.hibernate.replace.ReferringObjectMetadataFactory;
71 import eu.etaxonomy.cdm.persistence.query.Grouping;
72 import eu.etaxonomy.cdm.persistence.query.MatchMode;
73 import eu.etaxonomy.cdm.persistence.query.OrderHint;
74
75 /**
76 * @author a.mueller FIXME CdmEntityDaoBase is abstract, can it be annotated
77 * with @Repository?
78 */
79 @Repository
80 public abstract class CdmEntityDaoBase<T extends CdmBase>
81 extends DaoBase
82 implements ICdmEntityDao<T> {
83
84 private static final Logger logger = Logger.getLogger(CdmEntityDaoBase.class);
85
86 protected int flushAfterNo = 1000; // large numbers may cause
87 // synchronisation errors when commiting
88 // the session !!
89
90 protected Class<T> type;
91
92 @Autowired
93 // @Qualifier("defaultBeanInitializer")
94 protected IBeanInitializer defaultBeanInitializer;
95
96 public void setDefaultBeanInitializer(IBeanInitializer defaultBeanInitializer) {
97 this.defaultBeanInitializer = defaultBeanInitializer;
98 }
99
100 @Autowired
101 private ReferringObjectMetadataFactory referringObjectMetadataFactory;
102
103 protected static final EnumSet<Operator> LEFTOUTER_OPS = EnumSet.of(Operator.AND_NOT, Operator.OR, Operator.OR_NOT);
104
105 public CdmEntityDaoBase(Class<T> type) {
106 this.type = type;
107 assert type != null;
108 logger.debug("Creating DAO of type [" + type.getSimpleName() + "]");
109 }
110
111 @Override
112 public void lock(T t, LockOptions lockOptions) {
113 getSession().buildLockRequest(lockOptions).lock(t);
114 }
115
116 @Override
117 public void refresh(T t, LockOptions lockOptions, List<String> propertyPaths) {
118 getSession().refresh(t, lockOptions);
119 defaultBeanInitializer.initialize(t, propertyPaths);
120 }
121
122 // TODO this method should be moved to a concrete class (not typed)
123 public UUID saveCdmObj(CdmBase cdmObj) throws DataAccessException {
124 getSession().saveOrUpdate(cdmObj);
125 return cdmObj.getUuid();
126 }
127
128 // TODO: Replace saveCdmObj() by saveCdmObject_
129 private UUID saveCdmObject_(T cdmObj) {
130 getSession().saveOrUpdate(cdmObj);
131 return cdmObj.getUuid();
132 }
133
134 // TODO: Use everywhere CdmEntityDaoBase.saveAll() instead of
135 // ServiceBase.saveCdmObjectAll()?
136 // TODO: why does this use saveCdmObject_ which actually savesOrUpdateds
137 // data ?
138 @Override
139 public Map<UUID, T> saveAll(Collection<? extends T> cdmObjCollection) {
140 int types = cdmObjCollection.getClass().getTypeParameters().length;
141 if (types > 0) {
142 if (logger.isDebugEnabled()) {
143 logger.debug("ClassType: + " + cdmObjCollection.getClass().getTypeParameters()[0]);
144 }
145 }
146
147 Map<UUID, T> resultMap = new HashMap<>();
148 Iterator<? extends T> iterator = cdmObjCollection.iterator();
149 int i = 0;
150 while (iterator.hasNext()) {
151 if (((i % 2000) == 0) && (i > 0)) {
152 logger.debug("Saved " + i + " objects");
153 }
154 T cdmObj = iterator.next();
155 UUID uuid = saveCdmObject_(cdmObj);
156 if (logger.isDebugEnabled()) {
157 logger.debug("Save cdmObj: " + (cdmObj == null ? null : cdmObj.toString()));
158 }
159 resultMap.put(uuid, cdmObj);
160 i++;
161 if ((i % flushAfterNo) == 0) {
162 try {
163 if (logger.isDebugEnabled()) {
164 logger.debug("flush");
165 }
166 flush();
167 } catch (Exception e) {
168 logger.error("An exception occurred when trying to flush data");
169 e.printStackTrace();
170 throw new RuntimeException(e);
171 }
172 }
173 }
174
175 if (logger.isInfoEnabled()) {
176 logger.info("Saved " + i + " objects");
177 }
178 return resultMap;
179 }
180
181 private UUID saveOrUpdateCdmObject(T cdmObj) {
182 getSession().saveOrUpdate(cdmObj);
183 return cdmObj.getUuid();
184 }
185
186 @Override
187 public Map<UUID, T> saveOrUpdateAll(Collection<T> cdmObjCollection) {
188 int types = cdmObjCollection.getClass().getTypeParameters().length;
189 if (types > 0) {
190 if (logger.isDebugEnabled()) {
191 logger.debug("ClassType: + " + cdmObjCollection.getClass().getTypeParameters()[0]);
192 }
193 }
194
195 Map<UUID, T> resultMap = new HashMap<>();
196 Iterator<T> iterator = cdmObjCollection.iterator();
197 int i = 0;
198 while (iterator.hasNext()) {
199 if (((i % 2000) == 0) && (i > 0)) {
200 logger.debug("Saved " + i + " objects");
201 }
202 T cdmObj = iterator.next();
203 UUID uuid = saveOrUpdateCdmObject(cdmObj);
204 if (logger.isDebugEnabled()) {
205 logger.debug("Save cdmObj: " + (cdmObj == null ? null : cdmObj.toString()));
206 }
207 resultMap.put(uuid, cdmObj);
208 i++;
209 if ((i % flushAfterNo) == 0) {
210 try {
211 if (logger.isDebugEnabled()) {
212 logger.debug("flush");
213 }
214 flush();
215 } catch (Exception e) {
216 logger.error("An exception occurred when trying to flush data");
217 e.printStackTrace();
218 throw new RuntimeException(e);
219 }
220 }
221 }
222
223 if (logger.isInfoEnabled()) {
224 logger.info("Saved " + i + " objects");
225 }
226 return resultMap;
227 }
228
229 @Override
230 public T replace(T x, T y) {
231 if (x.equals(y)) {
232 return y;
233 }
234
235 Class<?> commonClass = x.getClass();
236 if (y != null) {
237 while (!commonClass.isAssignableFrom(y.getClass())) {
238 if (commonClass.equals(type)) {
239 throw new RuntimeException();
240 }
241 commonClass = commonClass.getSuperclass();
242 }
243 }
244
245 getSession().merge(x);
246
247 Set<ReferringObjectMetadata> referringObjectMetas = referringObjectMetadataFactory.get(x.getClass());
248
249 for (ReferringObjectMetadata referringObjectMetadata : referringObjectMetas) {
250
251 List<CdmBase> referringObjects = referringObjectMetadata.getReferringObjects(x, getSession());
252
253 for (CdmBase referringObject : referringObjects) {
254 try {
255 referringObjectMetadata.replace(referringObject, x, y);
256 getSession().update(referringObject);
257
258 } catch (IllegalArgumentException e) {
259 throw new RuntimeException(e.getMessage(), e);
260 } catch (IllegalAccessException e) {
261 throw new RuntimeException(e.getMessage(), e);
262 }
263 }
264 }
265 return y;
266 }
267
268 @Override
269 public Session getSession() throws DataAccessException {
270 return super.getSession();
271 }
272
273 @Override
274 public void clear() throws DataAccessException {
275 Session session = getSession();
276 session.clear();
277 if (logger.isDebugEnabled()) {
278 logger.debug("dao clear end");
279 }
280 }
281
282 @Override
283 public MergeResult<T> merge(T transientObject, boolean returnTransientEntity) throws DataAccessException {
284 Session session = getSession();
285 PostMergeEntityListener.addSession(session);
286 MergeResult<T> result = null;
287 try {
288 @SuppressWarnings("unchecked")
289 T persistentObject = (T) session.merge(transientObject);
290 if (logger.isDebugEnabled()) {
291 logger.debug("dao merge end");
292 }
293
294 if (returnTransientEntity) {
295 if (transientObject != null && persistentObject != null) {
296 transientObject.setId(persistentObject.getId());
297 }
298 result = new MergeResult(transientObject, PostMergeEntityListener.getNewEntities(session));
299 } else {
300 result = new MergeResult(persistentObject, null);
301 }
302 return result;
303 } finally {
304 PostMergeEntityListener.removeSession(session);
305 }
306 }
307
308 @Override
309 public T merge(T transientObject) throws DataAccessException {
310 Session session = getSession();
311 @SuppressWarnings("unchecked")
312 T persistentObject = (T) session.merge(transientObject);
313 if (logger.isDebugEnabled()) {
314 logger.debug("dao merge end");
315 }
316 return persistentObject;
317 }
318
319 @Override
320 public UUID saveOrUpdate(T transientObject) throws DataAccessException {
321 if (transientObject == null) {
322 logger.warn("Object to save should not be null. NOP");
323 return null;
324 }
325 try {
326 if (logger.isDebugEnabled()) {
327 logger.debug("dao saveOrUpdate start...");
328 }
329 if (logger.isDebugEnabled()) {
330 logger.debug("transientObject(" + transientObject.getClass().getSimpleName() + ") ID:"
331 + transientObject.getId() + ", UUID: " + transientObject.getUuid());
332 }
333 Session session = getSession();
334 if (transientObject.getId() != 0 && VersionableEntity.class.isAssignableFrom(transientObject.getClass())) {
335 VersionableEntity versionableEntity = (VersionableEntity) transientObject;
336 versionableEntity.setUpdated(new DateTime());
337 Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
338 if (authentication != null && authentication.getPrincipal() != null
339 && authentication.getPrincipal() instanceof User) {
340 User user = (User) authentication.getPrincipal();
341 versionableEntity.setUpdatedBy(user);
342 }
343 }
344 session.saveOrUpdate(transientObject);
345 if (logger.isDebugEnabled()) {
346 logger.debug("dao saveOrUpdate end");
347 }
348 return transientObject.getUuid();
349 } catch (NonUniqueObjectException e) {
350 logger.error("Error in CdmEntityDaoBase.saveOrUpdate(obj). ID=" + e.getIdentifier() + ". Class="
351 + e.getEntityName());
352 logger.error(e.getMessage());
353
354 e.printStackTrace();
355 throw e;
356 } catch (HibernateException e) {
357
358 e.printStackTrace();
359 throw e;
360 }
361 }
362
363 @Override
364 public <S extends T> S save(S newInstance) throws DataAccessException {
365 if (newInstance == null) {
366 logger.warn("Object to save should not be null. NOP");
367 return null;
368 }
369 getSession().save(newInstance);
370 return newInstance;
371 }
372
373 @Override
374 public UUID update(T transientObject) throws DataAccessException {
375 if (transientObject == null) {
376 logger.warn("Object to update should not be null. NOP");
377 return null;
378 }
379 getSession().update(transientObject);
380 return transientObject.getUuid();
381 }
382
383 @Override
384 public UUID refresh(T persistentObject) throws DataAccessException {
385 getSession().refresh(persistentObject);
386 return persistentObject.getUuid();
387 }
388
389 @Override
390 public UUID delete(T persistentObject) throws DataAccessException {
391 if (persistentObject == null) {
392 logger.warn(type.getName() + " was 'null'");
393 return null;
394 }
395
396 // Merge the object in if it is detached
397 //
398 // I think this is preferable to catching lazy initialization errors
399 // as that solution only swallows and hides the exception, but doesn't
400 // actually solve it.
401 persistentObject = (T) getSession().merge(persistentObject);
402 getSession().delete(persistentObject);
403 return persistentObject.getUuid();
404 }
405
406 @Override
407 public T findById(int id) throws DataAccessException {
408 return getSession().get(type, id);
409 }
410
411 @Override
412 public T findByUuid(UUID uuid) throws DataAccessException {
413 return this.findByUuid(uuid, INCLUDE_UNPUBLISHED);
414 }
415
416 protected T findByUuid(UUID uuid, boolean includeUnpublished) throws DataAccessException {
417 Session session = getSession();
418 Criteria crit = session.createCriteria(type);
419 crit.add(Restrictions.eq("uuid", uuid));
420 crit.addOrder(Order.desc("created"));
421 if (IPublishable.class.isAssignableFrom(type) && !includeUnpublished) {
422 crit.add(Restrictions.eq("publish", Boolean.TRUE));
423 }
424
425 @SuppressWarnings("unchecked")
426 List<T> results = crit.list();
427 Set<T> resultSet = new HashSet<>();
428 resultSet.addAll(results);
429 if (resultSet.isEmpty()) {
430 return null;
431 } else {
432 if (resultSet.size() > 1) {
433 logger.error("findByUuid() delivers more than one result for UUID: " + uuid);
434 }
435 return results.get(0);
436 }
437 }
438
439 @Override
440 public T findByUuidWithoutFlush(UUID uuid) throws DataAccessException {
441 Session session = getSession();
442 FlushMode currentFlushMode = session.getFlushMode();
443 try {
444 // set flush mode to manual so that the session does not flush
445 // when before performing the query
446 session.setFlushMode(FlushMode.MANUAL);
447 Criteria crit = session.createCriteria(type);
448 crit.add(Restrictions.eq("uuid", uuid));
449 crit.addOrder(Order.desc("created"));
450 @SuppressWarnings("unchecked")
451 List<T> results = crit.list();
452 if (results.isEmpty()) {
453 return null;
454 } else {
455 if (results.size() > 1) {
456 logger.error("findByUuid() delivers more than one result for UUID: " + uuid);
457 }
458 return results.get(0);
459 }
460 } finally {
461 // set back the session flush mode
462 if (currentFlushMode != null) {
463 session.setFlushMode(currentFlushMode);
464 }
465 }
466 }
467
468 @Override
469 public List<T> loadList(Collection<Integer> ids, List<OrderHint> orderHints, List<String> propertyPaths) throws DataAccessException {
470
471 if (ids.isEmpty()) {
472 return new ArrayList<>(0);
473 }
474
475 Criteria criteria = prepareList(null, ids, null, null, orderHints, "id");
476
477 if (logger.isDebugEnabled()) {
478 logger.debug(criteria.toString());
479 }
480
481 @SuppressWarnings("unchecked")
482 List<T> result = criteria.list();
483 defaultBeanInitializer.initializeAll(result, propertyPaths);
484 return result;
485 }
486
487 @Override
488 public List<T> list(Collection<UUID> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
489 List<String> propertyPaths) throws DataAccessException {
490
491 if (uuids == null || uuids.isEmpty()) {
492 return new ArrayList<>();
493 }
494
495 Criteria criteria = prepareList(null, uuids, pageSize, pageNumber, orderHints, "uuid");
496 @SuppressWarnings("unchecked")
497 List<T> result = criteria.list();
498 defaultBeanInitializer.initializeAll(result, propertyPaths);
499 return result;
500 }
501
502 @Override
503 public <S extends T> List<S> list(Class<S> clazz, Collection<UUID> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
504 List<String> propertyPaths) throws DataAccessException {
505
506 if (uuids == null || uuids.isEmpty()) {
507 return new ArrayList<>();
508 }
509
510 Criteria criteria = prepareList(clazz, uuids, pageSize, pageNumber, orderHints, "uuid");
511 @SuppressWarnings("unchecked")
512 List<S> result = criteria.list();
513 defaultBeanInitializer.initializeAll(result, propertyPaths);
514 return result;
515 }
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 loadWithoutInitializing(int id){
777 return this.getSession().load(type, id);
778 }
779
780 @Override
781 public T load(UUID uuid, List<String> propertyPaths) {
782 return this.load(uuid, INCLUDE_UNPUBLISHED, propertyPaths);
783 }
784
785 protected T load(UUID uuid, boolean includeUnpublished, List<String> propertyPaths) {
786 T bean = findByUuid(uuid, includeUnpublished);
787 if (bean == null) {
788 return bean;
789 }
790 defaultBeanInitializer.initialize(bean, propertyPaths);
791
792 return bean;
793 }
794
795 @Override
796 public Boolean exists(UUID uuid) {
797 if (findByUuid(uuid) == null) {
798 return false;
799 }
800 return true;
801 }
802
803 @Override
804 public long count() {
805 return count(type);
806 }
807
808 @Override
809 public long count(Class<? extends T> clazz) {
810 Session session = getSession();
811 Criteria criteria = null;
812 if (clazz == null) {
813 criteria = session.createCriteria(type);
814 } else {
815 criteria = session.createCriteria(clazz);
816 }
817 criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
818
819 // since hibernate 4 (or so) uniqueResult returns Long, not Integer,
820 // therefore needs
821 // to be casted. Think about returning long rather then int!
822 return (long) criteria.uniqueResult();
823 }
824
825 @Override
826 public List<T> list(Integer limit, Integer start) {
827 return list(limit, start, null);
828 }
829
830 @Override
831 public List<Object[]> group(Class<? extends T> clazz, Integer limit, Integer start, List<Grouping> groups,
832 List<String> propertyPaths) {
833
834 Criteria criteria = null;
835 criteria = criterionForType(clazz);
836
837 addGroups(criteria, groups);
838
839 if (limit != null) {
840 criteria.setFirstResult(start);
841 criteria.setMaxResults(limit);
842 }
843
844 @SuppressWarnings("unchecked")
845 List<Object[]> result = criteria.list();
846
847 if (propertyPaths != null && !propertyPaths.isEmpty()) {
848 for (Object[] objects : result) {
849 defaultBeanInitializer.initialize(objects[0], propertyPaths);
850 }
851 }
852
853 return result;
854 }
855
856 protected void countGroups(DetachedCriteria criteria, List<Grouping> groups) {
857 if (groups != null) {
858
859 Map<String, String> aliases = new HashMap<String, String>();
860
861 for (Grouping grouping : groups) {
862 if (grouping.getAssociatedObj() != null) {
863 String alias = null;
864 if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
865 alias = grouping.getAssociatedObjectAlias();
866 aliases.put(grouping.getAssociatedObj(), alias);
867 criteria.createAlias(grouping.getAssociatedObj(), alias);
868 }
869 }
870 }
871
872 ProjectionList projectionList = Projections.projectionList();
873
874 for (Grouping grouping : groups) {
875 grouping.addProjection(projectionList);
876 }
877 criteria.setProjection(projectionList);
878 }
879 }
880
881 protected void addGroups(Criteria criteria, List<Grouping> groups) {
882 if (groups != null) {
883
884 Map<String, String> aliases = new HashMap<String, String>();
885
886 for (Grouping grouping : groups) {
887 if (grouping.getAssociatedObj() != null) {
888 String alias = null;
889 if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
890 alias = grouping.getAssociatedObjectAlias();
891 aliases.put(grouping.getAssociatedObj(), alias);
892 criteria.createAlias(grouping.getAssociatedObj(), alias);
893 }
894 }
895 }
896
897 ProjectionList projectionList = Projections.projectionList();
898
899 for (Grouping grouping : groups) {
900 grouping.addProjection(projectionList);
901 }
902 criteria.setProjection(projectionList);
903
904 for (Grouping grouping : groups) {
905 grouping.addOrder(criteria);
906
907 }
908 }
909 }
910
911 @Override
912 public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints) {
913 return list(limit, start, orderHints, null);
914 }
915
916 @Override
917 public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
918 Criteria criteria = getSession().createCriteria(type);
919 if (limit != null) {
920 criteria.setFirstResult(start);
921 criteria.setMaxResults(limit);
922 }
923
924 addOrder(criteria, orderHints);
925 @SuppressWarnings("unchecked")
926 List<T> results = criteria.list();
927
928 defaultBeanInitializer.initializeAll(results, propertyPaths);
929 return results;
930 }
931
932 @Override
933 public <S extends T> List<S> list(Class<S> clazz, Integer limit, Integer start, List<OrderHint> orderHints,
934 List<String> propertyPaths) {
935 Criteria criteria = null;
936 if (clazz == null) {
937 criteria = getSession().createCriteria(type);
938 } else {
939 criteria = getSession().createCriteria(clazz);
940 }
941
942 addLimitAndStart(criteria, limit, start);
943
944 addOrder(criteria, orderHints);
945
946 @SuppressWarnings("unchecked")
947 List<S> results = criteria.list();
948
949 defaultBeanInitializer.initializeAll(results, propertyPaths);
950 return results;
951 }
952
953 public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start, List<OrderHint> orderHints) {
954 return list(type, limit, start, orderHints, null);
955 }
956
957 @Override
958 public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start) {
959 return list(type, limit, start, null, null);
960 }
961
962 @Override
963 public Class<T> getType() {
964 return type;
965 }
966
967 protected void setPagingParameter(Query query, Integer pageSize, Integer pageIndex) {
968 if (pageSize != null) {
969 query.setMaxResults(pageSize);
970 if (pageIndex != null) {
971 query.setFirstResult(pageIndex * pageSize);
972 } else {
973 query.setFirstResult(0);
974 }
975 }
976 }
977
978 protected void setPagingParameter(AuditQuery query, Integer pageSize, Integer pageIndex) {
979 if (pageSize != null) {
980 query.setMaxResults(pageSize);
981 if (pageIndex != null) {
982 query.setFirstResult(pageIndex * pageSize);
983 } else {
984 query.setFirstResult(0);
985 }
986 }
987 }
988
989 @Override
990 public long count(T example, Set<String> includeProperties) {
991 Criteria criteria = getSession().createCriteria(example.getClass());
992 addExample(criteria, example, includeProperties);
993
994 criteria.setProjection(Projections.rowCount());
995 return (Long) criteria.uniqueResult();
996 }
997
998 protected void addExample(Criteria criteria, T example, Set<String> includeProperties) {
999 if (includeProperties != null && !includeProperties.isEmpty()) {
1000 criteria.add(Example.create(example).setPropertySelector(new PropertySelectorImpl(includeProperties)));
1001 ClassMetadata classMetadata = getSession().getSessionFactory().getClassMetadata(example.getClass());
1002 for (String property : includeProperties) {
1003 Type type = classMetadata.getPropertyType(property);
1004 if (type.isEntityType()) {
1005 try {
1006 Field field = ReflectionUtils.findField(example.getClass(), property);
1007 field.setAccessible(true);
1008 Object value = field.get(example);
1009 if (value != null) {
1010 criteria.add(Restrictions.eq(property, value));
1011 } else {
1012 criteria.add(Restrictions.isNull(property));
1013 }
1014 } catch (SecurityException se) {
1015 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1016 se);
1017 } catch (HibernateException he) {
1018 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1019 he);
1020 } catch (IllegalArgumentException iae) {
1021 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1022 iae);
1023 } catch (IllegalAccessException ie) {
1024 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1025 ie);
1026 }
1027 }
1028 }
1029 } else {
1030 criteria.add(Example.create(example));
1031 }
1032 }
1033
1034 /**
1035 *
1036 * NOTE: We can't reuse
1037 * {@link #list(Class, String, Object, MatchMode, Integer, Integer, List, List)
1038 * here due to different default behavior of the <code>matchmode</code>
1039 * parameter.
1040 *
1041 * @param clazz
1042 * @param param
1043 * @param queryString
1044 * @param matchmode
1045 * @param criterion
1046 * @param pageSize
1047 * @param pageNumber
1048 * @param orderHints
1049 * @param propertyPaths
1050 * @return
1051 */
1052 @Override
1053 public <S extends T> List<S> findByParam(Class<S> clazz, String param, String queryString, MatchMode matchmode,
1054 List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
1055 List<String> propertyPaths) {
1056 Set<String> stringSet = new HashSet<>();
1057 stringSet.add(param);
1058 return this.findByParam(clazz, stringSet, queryString, matchmode,
1059 criterion, pageSize, pageNumber, orderHints,
1060 propertyPaths);
1061 }
1062
1063 @Override
1064 public <S extends T> List<S> findByParam(Class<S> clazz, Set<String> params, String queryString, MatchMode matchmode,
1065 List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
1066 List<String> propertyPaths) {
1067
1068 Criteria criteria = criterionForType(clazz);
1069
1070 if (queryString != null) {
1071 Set<Criterion> criterions = new HashSet<>();
1072 for (String param: params){
1073 Criterion crit;
1074 if (matchmode == null) {
1075 crit = Restrictions.ilike(param, queryString);
1076 } else if (matchmode == MatchMode.BEGINNING) {
1077 crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START);
1078 } else if (matchmode == MatchMode.END) {
1079 crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END);
1080 } else if (matchmode == MatchMode.EXACT) {
1081 crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT);
1082 } else {
1083 crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE);
1084 }
1085 criterions.add(crit);
1086 }
1087 if (criterions.size()>1){
1088 Iterator<Criterion> critIterator = criterions.iterator();
1089 Disjunction disjunction = Restrictions.disjunction();
1090 while (critIterator.hasNext()){
1091 disjunction.add(critIterator.next());
1092 }
1093
1094 criteria.add(disjunction);
1095
1096 }else{
1097 if (!criterions.isEmpty()){
1098 criteria.add(criterions.iterator().next());
1099 }
1100 }
1101
1102 }
1103
1104 addCriteria(criteria, criterion);
1105
1106 if (pageSize != null) {
1107 criteria.setMaxResults(pageSize);
1108 if (pageNumber != null) {
1109 criteria.setFirstResult(pageNumber * pageSize);
1110 } else {
1111 criteria.setFirstResult(0);
1112 }
1113 }
1114
1115 addOrder(criteria, orderHints);
1116
1117 @SuppressWarnings("unchecked")
1118 List<S> result = criteria.list();
1119 defaultBeanInitializer.initializeAll(result, propertyPaths);
1120 return result;
1121 }
1122
1123 /**
1124 *
1125 * @param clazz
1126 * @param param
1127 * @param queryString
1128 * @param matchmode
1129 * @param criterion
1130 * @return
1131 */
1132 @Override
1133 public long countByParam(Class<? extends T> clazz, String param, String queryString, MatchMode matchmode,
1134 List<Criterion> criterion) {
1135
1136 Criteria criteria = null;
1137
1138 criteria = criterionForType(clazz);
1139
1140 if (queryString != null) {
1141 if (matchmode == null) {
1142 criteria.add(Restrictions.ilike(param, queryString));
1143 } else if (matchmode == MatchMode.BEGINNING) {
1144 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START));
1145 } else if (matchmode == MatchMode.END) {
1146 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END));
1147 } else if (matchmode == MatchMode.EXACT) {
1148 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT));
1149 } else {
1150 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE));
1151 }
1152 }
1153
1154 addCriteria(criteria, criterion);
1155
1156 criteria.setProjection(Projections.rowCount());
1157
1158 return (Long) criteria.uniqueResult();
1159 }
1160
1161 /**
1162 * Creates a criteria query for the CDM <code>type</code> either for counting or listing matching entities.
1163 * <p>
1164 * The set of matching entities can be restricted by passing a list of {@link Restriction} objects.
1165 * Restrictions can logically combined:
1166 <pre>
1167 Arrays.asList(
1168 new Restriction<String>("titleCache", MatchMode.ANYWHERE, "foo"),
1169 new Restriction<String>("institute.name", Operator.OR, MatchMode.BEGINNING, "Bar")
1170 );
1171 </pre>
1172 * The first Restriction in the example above by default has the <code>Operator.AND</code> which will be
1173 * ignored since this is the first restriction. The <code>Operator</code> of further restrictions in the
1174 * list are used to combine with the previous restriction.
1175 *
1176 * @param type
1177 * @param restrictions
1178 * @param doCount
1179 * @return
1180 */
1181 protected Criteria createCriteria(Class<? extends T> type, List<Restriction<?>> restrictions, boolean doCount) {
1182
1183 DetachedCriteria idsOnlyCriteria = DetachedCriteria.forClass(entityType(type));
1184 idsOnlyCriteria.setProjection(Projections.distinct(Projections.id()));
1185
1186 addRestrictions(restrictions, idsOnlyCriteria);
1187
1188 Criteria criteria = criterionForType(type);
1189 criteria.add(Subqueries.propertyIn("id", idsOnlyCriteria));
1190
1191 if(doCount){
1192 criteria.setProjection(Projections.rowCount());
1193 } else {
1194 idsOnlyCriteria.setProjection(Projections.distinct(Projections.property("id")));
1195 }
1196
1197 return criteria;
1198 }
1199
1200
1201 @Override
1202 public <S extends T> List<S> findByParamWithRestrictions(Class<S> clazz, String param, String queryString,
1203 MatchMode matchmode, List<Restriction<?>> restrictions, Integer pageSize, Integer pageNumber,
1204 List<OrderHint> orderHints, List<String> propertyPaths) {
1205
1206 List<Restriction<?>> allRestrictions = new ArrayList<>();
1207 allRestrictions.add(new Restriction<String>(param, matchmode, queryString));
1208 if(restrictions != null){
1209 allRestrictions.addAll(restrictions);
1210 }
1211 Criteria criteria = createCriteria(clazz, allRestrictions, false);
1212
1213 if (pageSize != null) {
1214 criteria.setMaxResults(pageSize);
1215 if (pageNumber != null) {
1216 criteria.setFirstResult(pageNumber * pageSize);
1217 } else {
1218 criteria.setFirstResult(0);
1219 }
1220 }
1221
1222 addOrder(criteria, orderHints);
1223
1224 @SuppressWarnings("unchecked")
1225 List<S> result = criteria.list();
1226 defaultBeanInitializer.initializeAll(result, propertyPaths);
1227 return result;
1228
1229 }
1230
1231 @Override
1232 public long countByParamWithRestrictions(Class<? extends T> clazz, String param, String queryString,
1233 MatchMode matchmode, List<Restriction<?>> restrictions) {
1234
1235 List<Restriction<?>> allRestrictions = new ArrayList<>();
1236 allRestrictions.add(new Restriction<String>(param, matchmode, queryString));
1237 if(restrictions != null){
1238 allRestrictions.addAll(restrictions);
1239 }
1240 Criteria criteria = createCriteria(clazz, allRestrictions, true);
1241
1242 return (Long) criteria.uniqueResult();
1243 }
1244
1245 @Override
1246 public <S extends T> List<S> list(S example, Set<String> includeProperties, Integer limit, Integer start,
1247 List<OrderHint> orderHints, List<String> propertyPaths) {
1248 Criteria criteria = getSession().createCriteria(example.getClass());
1249 addExample(criteria, example, includeProperties);
1250
1251 addLimitAndStart(criteria, limit, start);
1252
1253 addOrder(criteria, orderHints);
1254
1255 @SuppressWarnings("unchecked")
1256 List<S> results = criteria.list();
1257 defaultBeanInitializer.initializeAll(results, propertyPaths);
1258 return results;
1259 }
1260
1261 private class PropertySelectorImpl implements PropertySelector {
1262
1263 private final Set<String> includeProperties;
1264 /**
1265 *
1266 */
1267 private static final long serialVersionUID = -3175311800911570546L;
1268
1269 public PropertySelectorImpl(Set<String> includeProperties) {
1270 this.includeProperties = includeProperties;
1271 }
1272
1273 @Override
1274 public boolean include(Object propertyValue, String propertyName, Type type) {
1275 if (includeProperties.contains(propertyName)) {
1276 return true;
1277 } else {
1278 return false;
1279 }
1280 }
1281
1282 }
1283
1284 private class CriterionWithOperator {
1285
1286 Restriction.Operator operator;
1287 Criterion criterion;
1288
1289
1290 public CriterionWithOperator(Operator operator, Criterion criterion) {
1291 super();
1292 this.operator = operator;
1293 this.criterion = criterion;
1294 }
1295
1296
1297 }
1298
1299 /**
1300 * Returns a Criteria for the given {@link Class class} or, if
1301 * <code>null</code>, for the base {@link Class class} of this DAO.
1302 *
1303 * @param clazz
1304 * @return the Criteria
1305 */
1306 protected Criteria getCriteria(Class<? extends CdmBase> clazz) {
1307 Criteria criteria = null;
1308 if (clazz == null) {
1309 criteria = getSession().createCriteria(type);
1310 } else {
1311 criteria = getSession().createCriteria(clazz);
1312 }
1313 return criteria;
1314 }
1315
1316 /**
1317 * @param clazz
1318 * @param auditEvent
1319 * @return
1320 */
1321 protected AuditQuery makeAuditQuery(Class<? extends CdmBase> clazz, AuditEvent auditEvent) {
1322 AuditQuery query = null;
1323
1324 if (clazz == null) {
1325 query = getAuditReader().createQuery().forEntitiesAtRevision(type, auditEvent.getRevisionNumber());
1326 } else {
1327 query = getAuditReader().createQuery().forEntitiesAtRevision(clazz, auditEvent.getRevisionNumber());
1328 }
1329 return query;
1330 }
1331
1332 protected AuditReader getAuditReader() {
1333 return AuditReaderFactory.get(getSession());
1334 }
1335 }