ref #9683 add loadProxy method to services and adapt test to work with current auto...
[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<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<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 /**
518 * {@inheritDoc}
519 */
520 @Override
521 public <S extends T> List<S> list(Class<S> type, List<Restriction<?>> restrictions, Integer limit, Integer start,
522 List<OrderHint> orderHints, List<String> propertyPaths) {
523
524 Criteria criteria = createCriteria(type, restrictions, false);
525
526 addLimitAndStart(criteria, limit, start);
527 addOrder(criteria, orderHints);
528
529 @SuppressWarnings("unchecked")
530 List<S> result = criteria.list();
531 defaultBeanInitializer.initializeAll(result, propertyPaths);
532 return result;
533 }
534
535 /**
536 * @param restrictions
537 * @param criteria
538 */
539 private void addRestrictions(List<Restriction<?>> restrictions, DetachedCriteria criteria) {
540
541 if(restrictions == null || restrictions.isEmpty()){
542 return ;
543 }
544
545 List<CriterionWithOperator> perProperty = new ArrayList<>(restrictions.size());
546 Map<String, String> aliases = new HashMap<>();
547
548
549
550 for(Restriction<?> restriction : restrictions){
551 Collection<? extends Object> values = restriction.getValues();
552 JoinType jointype = LEFTOUTER_OPS.contains(restriction.getOperator()) ? JoinType.LEFT_OUTER_JOIN : JoinType.INNER_JOIN;
553 if(values != null && !values.isEmpty()){
554 // ---
555 String propertyPath = restriction.getPropertyName();
556 String[] props = propertyPath.split("\\.");
557 String propertyName;
558 if(props.length == 1){
559 // direct property of the base type of the criteria
560 propertyName = propertyPath;
561 } else {
562 // create aliases if the propertyName is a dot separated property path
563 String aĺiasKey = jointype.name() + "_";
564 String aliasedProperty = null;
565 String alias = "";
566 for(int p = 0; p < props.length -1; p++){
567 aĺiasKey = aĺiasKey + (aĺiasKey.isEmpty() ? "" : ".") + props[p];
568 aliasedProperty = alias + (alias.isEmpty() ? "" : ".") + props[p];
569 if(!aliases.containsKey(aliasedProperty)){
570 alias = alias + (alias.isEmpty() ? "" : "_" ) + props[p];
571 aliases.put(aĺiasKey, alias);
572 criteria.createAlias(aliasedProperty, alias, jointype);
573 if(logger.isDebugEnabled()){
574 logger.debug("addRestrictions() alias created with aliasKey " + aĺiasKey + " => " + aliasedProperty + " as " + alias);
575 }
576 }
577 }
578 propertyName = alias + "." + props[props.length -1];
579 }
580 // ---
581 Criterion[] predicates = new Criterion[values.size()];
582 int i = 0;
583 for(Object v : values){
584 Criterion criterion = createRestriction(propertyName, v, restriction.getMatchMode());
585 if(restriction.isNot()){
586 if(props.length > 1){
587 criterion = Restrictions.or(Restrictions.not(criterion), Restrictions.isNull(propertyName));
588 } else {
589 criterion = Restrictions.not(criterion);
590 }
591 }
592 predicates[i++] = criterion;
593 if(logger.isDebugEnabled()){
594 logger.debug("addRestrictions() predicate with " + propertyName + " " + (restriction.getMatchMode() == null ? "=" : restriction.getMatchMode().name()) + " " + v.toString());
595 }
596 }
597 if(restriction.getOperator() == Operator.AND_NOT){
598 perProperty.add(new CriterionWithOperator(restriction.getOperator(), Restrictions.and(predicates)));
599 } else {
600 perProperty.add(new CriterionWithOperator(restriction.getOperator(), Restrictions.or(predicates)));
601 }
602 } // check has values
603 } // loop over restrictions
604
605 Restriction.Operator firstOperator = null;
606 if(!perProperty.isEmpty()){
607 LogicalExpression logicalExpression = null;
608 for(CriterionWithOperator cwo : perProperty){
609 if(logicalExpression == null){
610 firstOperator = cwo.operator;
611 logicalExpression = Restrictions.and(Restrictions.sqlRestriction("1=1"), cwo.criterion);
612 } else {
613 switch(cwo.operator){
614 case AND:
615 case AND_NOT:
616 logicalExpression = Restrictions.and(logicalExpression, cwo.criterion);
617 break;
618 case OR:
619 case OR_NOT:
620 logicalExpression = Restrictions.or(logicalExpression, cwo.criterion);
621 break;
622 default:
623 throw new RuntimeException("Unsupported Operator");
624 }
625 }
626
627 }
628
629
630 criteria.add(logicalExpression);
631 // if(firstOperator == Operator.OR){
632 // // OR
633 // } else {
634 // // AND
635 // criteria.add(Restrictions.and(queryStringCriterion, logicalExpression));
636 // }
637 }
638 if(logger.isDebugEnabled()){
639 logger.debug("addRestrictions() final criteria: " + criteria.toString());
640 }
641 }
642
643 /**
644 * @param propertyName
645 * @param value
646 * @param matchMode
647 * is only applied if the <code>value</code> is a
648 * <code>String</code> object
649 * @param criteria
650 * @return
651 */
652 private Criterion createRestriction(String propertyName, Object value, MatchMode matchMode) {
653
654 Criterion restriction;
655 if (value == null) {
656 if (logger.isDebugEnabled()) {
657 logger.debug("createRestriction() " + propertyName + " is null ");
658 }
659 restriction = Restrictions.isNull(propertyName);
660 } else if (matchMode == null || !(value instanceof String)) {
661 if (logger.isDebugEnabled()) {
662 logger.debug("createRestriction() " + propertyName + " = " + value.toString());
663 }
664 restriction = Restrictions.eq(propertyName, value);
665 } else {
666 String queryString = (String) value;
667 if (logger.isDebugEnabled()) {
668 logger.debug("createRestriction() " + propertyName + " " + matchMode.getMatchOperator() + " "
669 + matchMode.queryStringFrom(queryString));
670 }
671 switch(matchMode){
672 case BEGINNING:
673 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.START);
674 break;
675 case END:
676 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.END);
677 break;
678 case LIKE:
679 restriction = Restrictions.ilike(propertyName, matchMode.queryStringFrom(queryString), org.hibernate.criterion.MatchMode.ANYWHERE);
680 break;
681 case EXACT:
682 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.EXACT);
683 break;
684 case ANYWHERE:
685 restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.ANYWHERE);
686 break;
687 default:
688 throw new RuntimeException("Unknown MatchMode: " + matchMode.name());
689 }
690 }
691 return restriction;
692 }
693
694 /**
695 * {@inheritDoc}
696 */
697 @Override
698 public long count(Class<? extends T> type, List<Restriction<?>> restrictions) {
699
700 Criteria criteria = createCriteria(type, restrictions, false);
701
702 criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
703
704 return (Long) criteria.uniqueResult();
705
706 }
707
708 /**
709 * @param uuids
710 * @param pageSize
711 * @param pageNumber
712 * @param orderHints
713 * @param propertyName
714 * @return
715 */
716 private Criteria prepareList(Class<? extends T> clazz, Collection<?> uuids, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
717 String propertyName) {
718 if (clazz == null){
719 clazz = type;
720 }
721 Criteria criteria = getSession().createCriteria(clazz);
722 criteria.add(Restrictions.in(propertyName, uuids));
723
724 if (pageSize != null) {
725 criteria.setMaxResults(pageSize);
726 if (pageNumber != null) {
727 criteria.setFirstResult(pageNumber * pageSize);
728 } else {
729 criteria.setFirstResult(0);
730 }
731 }
732
733 if (orderHints == null) {
734 orderHints = OrderHint.defaultOrderHintsFor(type);
735 }
736 addOrder(criteria, orderHints);
737 return criteria;
738 }
739
740 /**
741 * @param clazz
742 * @return
743 */
744 private Criteria criterionForType(Class<? extends T> clazz) {
745 return getSession().createCriteria(entityType(clazz));
746 }
747
748 protected Class<? extends T> entityType(Class<? extends T> clazz){
749 if (clazz != null) {
750 return clazz;
751 } else {
752 return type;
753 }
754 }
755
756 @Override
757 public T load(UUID uuid) {
758 T bean = findByUuid(uuid);
759 if (bean == null) {
760 return null;
761 }
762 defaultBeanInitializer.load(bean);
763
764 return bean;
765 }
766
767 @Override
768 public T load(int id, List<String> propertyPaths) {
769 T bean = findById(id);
770 if (bean == null) {
771 return bean;
772 }
773 defaultBeanInitializer.initialize(bean, propertyPaths);
774
775 return bean;
776 }
777
778 @Override
779 public T loadProxy(int id){
780 return this.getSession().load(type, id);
781 }
782
783 @Override
784 public T load(UUID uuid, List<String> propertyPaths) {
785 return this.load(uuid, INCLUDE_UNPUBLISHED, propertyPaths);
786 }
787
788 protected T load(UUID uuid, boolean includeUnpublished, List<String> propertyPaths) {
789 T bean = findByUuid(uuid, includeUnpublished);
790 if (bean == null) {
791 return bean;
792 }
793 defaultBeanInitializer.initialize(bean, propertyPaths);
794
795 return bean;
796 }
797
798 @Override
799 public Boolean exists(UUID uuid) {
800 if (findByUuid(uuid) == null) {
801 return false;
802 }
803 return true;
804 }
805
806 @Override
807 public long count() {
808 return count(type);
809 }
810
811 @Override
812 public long count(Class<? extends T> clazz) {
813 Session session = getSession();
814 Criteria criteria = null;
815 if (clazz == null) {
816 criteria = session.createCriteria(type);
817 } else {
818 criteria = session.createCriteria(clazz);
819 }
820 criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
821
822 // since hibernate 4 (or so) uniqueResult returns Long, not Integer,
823 // therefore needs
824 // to be casted. Think about returning long rather then int!
825 return (long) criteria.uniqueResult();
826 }
827
828 @Override
829 public List<T> list(Integer limit, Integer start) {
830 return list(limit, start, null);
831 }
832
833 @Override
834 public List<Object[]> group(Class<? extends T> clazz, Integer limit, Integer start, List<Grouping> groups,
835 List<String> propertyPaths) {
836
837 Criteria criteria = null;
838 criteria = criterionForType(clazz);
839
840 addGroups(criteria, groups);
841
842 if (limit != null) {
843 criteria.setFirstResult(start);
844 criteria.setMaxResults(limit);
845 }
846
847 @SuppressWarnings("unchecked")
848 List<Object[]> result = criteria.list();
849
850 if (propertyPaths != null && !propertyPaths.isEmpty()) {
851 for (Object[] objects : result) {
852 defaultBeanInitializer.initialize(objects[0], propertyPaths);
853 }
854 }
855
856 return result;
857 }
858
859 protected void countGroups(DetachedCriteria criteria, List<Grouping> groups) {
860 if (groups != null) {
861
862 Map<String, String> aliases = new HashMap<String, String>();
863
864 for (Grouping grouping : groups) {
865 if (grouping.getAssociatedObj() != null) {
866 String alias = null;
867 if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
868 alias = grouping.getAssociatedObjectAlias();
869 aliases.put(grouping.getAssociatedObj(), alias);
870 criteria.createAlias(grouping.getAssociatedObj(), alias);
871 }
872 }
873 }
874
875 ProjectionList projectionList = Projections.projectionList();
876
877 for (Grouping grouping : groups) {
878 grouping.addProjection(projectionList);
879 }
880 criteria.setProjection(projectionList);
881 }
882 }
883
884 protected void addGroups(Criteria criteria, List<Grouping> groups) {
885 if (groups != null) {
886
887 Map<String, String> aliases = new HashMap<String, String>();
888
889 for (Grouping grouping : groups) {
890 if (grouping.getAssociatedObj() != null) {
891 String alias = null;
892 if ((alias = aliases.get(grouping.getAssociatedObj())) == null) {
893 alias = grouping.getAssociatedObjectAlias();
894 aliases.put(grouping.getAssociatedObj(), alias);
895 criteria.createAlias(grouping.getAssociatedObj(), alias);
896 }
897 }
898 }
899
900 ProjectionList projectionList = Projections.projectionList();
901
902 for (Grouping grouping : groups) {
903 grouping.addProjection(projectionList);
904 }
905 criteria.setProjection(projectionList);
906
907 for (Grouping grouping : groups) {
908 grouping.addOrder(criteria);
909
910 }
911 }
912 }
913
914 @Override
915 public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints) {
916 return list(limit, start, orderHints, null);
917 }
918
919 @Override
920 public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
921 Criteria criteria = getSession().createCriteria(type);
922 if (limit != null) {
923 criteria.setFirstResult(start);
924 criteria.setMaxResults(limit);
925 }
926
927 addOrder(criteria, orderHints);
928 @SuppressWarnings("unchecked")
929 List<T> results = criteria.list();
930
931 defaultBeanInitializer.initializeAll(results, propertyPaths);
932 return results;
933 }
934
935 @Override
936 public <S extends T> List<S> list(Class<S> clazz, Integer limit, Integer start, List<OrderHint> orderHints,
937 List<String> propertyPaths) {
938 Criteria criteria = null;
939 if (clazz == null) {
940 criteria = getSession().createCriteria(type);
941 } else {
942 criteria = getSession().createCriteria(clazz);
943 }
944
945 addLimitAndStart(criteria, limit, start);
946
947 addOrder(criteria, orderHints);
948
949 @SuppressWarnings("unchecked")
950 List<S> results = criteria.list();
951
952 defaultBeanInitializer.initializeAll(results, propertyPaths);
953 return results;
954 }
955
956 public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start, List<OrderHint> orderHints) {
957 return list(type, limit, start, orderHints, null);
958 }
959
960 @Override
961 public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start) {
962 return list(type, limit, start, null, null);
963 }
964
965 @Override
966 public Class<T> getType() {
967 return type;
968 }
969
970 protected void setPagingParameter(Query query, Integer pageSize, Integer pageIndex) {
971 if (pageSize != null) {
972 query.setMaxResults(pageSize);
973 if (pageIndex != null) {
974 query.setFirstResult(pageIndex * pageSize);
975 } else {
976 query.setFirstResult(0);
977 }
978 }
979 }
980
981 protected void setPagingParameter(AuditQuery query, Integer pageSize, Integer pageIndex) {
982 if (pageSize != null) {
983 query.setMaxResults(pageSize);
984 if (pageIndex != null) {
985 query.setFirstResult(pageIndex * pageSize);
986 } else {
987 query.setFirstResult(0);
988 }
989 }
990 }
991
992 @Override
993 public long count(T example, Set<String> includeProperties) {
994 Criteria criteria = getSession().createCriteria(example.getClass());
995 addExample(criteria, example, includeProperties);
996
997 criteria.setProjection(Projections.rowCount());
998 return (Long) criteria.uniqueResult();
999 }
1000
1001 protected void addExample(Criteria criteria, T example, Set<String> includeProperties) {
1002 if (includeProperties != null && !includeProperties.isEmpty()) {
1003 criteria.add(Example.create(example).setPropertySelector(new PropertySelectorImpl(includeProperties)));
1004 ClassMetadata classMetadata = getSession().getSessionFactory().getClassMetadata(example.getClass());
1005 for (String property : includeProperties) {
1006 Type type = classMetadata.getPropertyType(property);
1007 if (type.isEntityType()) {
1008 try {
1009 Field field = ReflectionUtils.findField(example.getClass(), property);
1010 field.setAccessible(true);
1011 Object value = field.get(example);
1012 if (value != null) {
1013 criteria.add(Restrictions.eq(property, value));
1014 } else {
1015 criteria.add(Restrictions.isNull(property));
1016 }
1017 } catch (SecurityException se) {
1018 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1019 se);
1020 } catch (HibernateException he) {
1021 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1022 he);
1023 } catch (IllegalArgumentException iae) {
1024 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1025 iae);
1026 } catch (IllegalAccessException ie) {
1027 throw new InvalidDataAccessApiUsageException("Tried to add criteria for property " + property,
1028 ie);
1029 }
1030 }
1031 }
1032 } else {
1033 criteria.add(Example.create(example));
1034 }
1035 }
1036
1037 /**
1038 *
1039 * NOTE: We can't reuse
1040 * {@link #list(Class, String, Object, MatchMode, Integer, Integer, List, List)
1041 * here due to different default behavior of the <code>matchmode</code>
1042 * parameter.
1043 *
1044 * @param clazz
1045 * @param param
1046 * @param queryString
1047 * @param matchmode
1048 * @param criterion
1049 * @param pageSize
1050 * @param pageNumber
1051 * @param orderHints
1052 * @param propertyPaths
1053 * @return
1054 */
1055 @Override
1056 public <S extends T> List<S> findByParam(Class<S> clazz, String param, String queryString, MatchMode matchmode,
1057 List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
1058 List<String> propertyPaths) {
1059 Set<String> stringSet = new HashSet<>();
1060 stringSet.add(param);
1061 return this.findByParam(clazz, stringSet, queryString, matchmode,
1062 criterion, pageSize, pageNumber, orderHints,
1063 propertyPaths);
1064 }
1065
1066 @Override
1067 public <S extends T> List<S> findByParam(Class<S> clazz, Set<String> params, String queryString, MatchMode matchmode,
1068 List<Criterion> criterion, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
1069 List<String> propertyPaths) {
1070
1071 Criteria criteria = criterionForType(clazz);
1072
1073 if (queryString != null) {
1074 Set<Criterion> criterions = new HashSet<>();
1075 for (String param: params){
1076 Criterion crit;
1077 if (matchmode == null) {
1078 crit = Restrictions.ilike(param, queryString);
1079 } else if (matchmode == MatchMode.BEGINNING) {
1080 crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START);
1081 } else if (matchmode == MatchMode.END) {
1082 crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END);
1083 } else if (matchmode == MatchMode.EXACT) {
1084 crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT);
1085 } else {
1086 crit = Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE);
1087 }
1088 criterions.add(crit);
1089 }
1090 if (criterions.size()>1){
1091 Iterator<Criterion> critIterator = criterions.iterator();
1092 Disjunction disjunction = Restrictions.disjunction();
1093 while (critIterator.hasNext()){
1094 disjunction.add(critIterator.next());
1095 }
1096
1097 criteria.add(disjunction);
1098
1099 }else{
1100 if (!criterions.isEmpty()){
1101 criteria.add(criterions.iterator().next());
1102 }
1103 }
1104
1105 }
1106
1107 addCriteria(criteria, criterion);
1108
1109 if (pageSize != null) {
1110 criteria.setMaxResults(pageSize);
1111 if (pageNumber != null) {
1112 criteria.setFirstResult(pageNumber * pageSize);
1113 } else {
1114 criteria.setFirstResult(0);
1115 }
1116 }
1117
1118 addOrder(criteria, orderHints);
1119
1120 @SuppressWarnings("unchecked")
1121 List<S> result = criteria.list();
1122 defaultBeanInitializer.initializeAll(result, propertyPaths);
1123 return result;
1124 }
1125
1126 /**
1127 *
1128 * @param clazz
1129 * @param param
1130 * @param queryString
1131 * @param matchmode
1132 * @param criterion
1133 * @return
1134 */
1135 @Override
1136 public long countByParam(Class<? extends T> clazz, String param, String queryString, MatchMode matchmode,
1137 List<Criterion> criterion) {
1138
1139 Criteria criteria = null;
1140
1141 criteria = criterionForType(clazz);
1142
1143 if (queryString != null) {
1144 if (matchmode == null) {
1145 criteria.add(Restrictions.ilike(param, queryString));
1146 } else if (matchmode == MatchMode.BEGINNING) {
1147 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.START));
1148 } else if (matchmode == MatchMode.END) {
1149 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.END));
1150 } else if (matchmode == MatchMode.EXACT) {
1151 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.EXACT));
1152 } else {
1153 criteria.add(Restrictions.ilike(param, queryString, org.hibernate.criterion.MatchMode.ANYWHERE));
1154 }
1155 }
1156
1157 addCriteria(criteria, criterion);
1158
1159 criteria.setProjection(Projections.rowCount());
1160
1161 return (Long) criteria.uniqueResult();
1162 }
1163
1164 /**
1165 * Creates a criteria query for the CDM <code>type</code> either for counting or listing matching entities.
1166 * <p>
1167 * The set of matching entities can be restricted by passing a list of {@link Restriction} objects.
1168 * Restrictions can logically combined:
1169 <pre>
1170 Arrays.asList(
1171 new Restriction<String>("titleCache", MatchMode.ANYWHERE, "foo"),
1172 new Restriction<String>("institute.name", Operator.OR, MatchMode.BEGINNING, "Bar")
1173 );
1174 </pre>
1175 * The first Restriction in the example above by default has the <code>Operator.AND</code> which will be
1176 * ignored since this is the first restriction. The <code>Operator</code> of further restrictions in the
1177 * list are used to combine with the previous restriction.
1178 *
1179 * @param type
1180 * @param restrictions
1181 * @param doCount
1182 * @return
1183 */
1184 protected Criteria createCriteria(Class<? extends T> type, List<Restriction<?>> restrictions, boolean doCount) {
1185
1186 DetachedCriteria idsOnlyCriteria = DetachedCriteria.forClass(entityType(type));
1187 idsOnlyCriteria.setProjection(Projections.distinct(Projections.id()));
1188
1189 addRestrictions(restrictions, idsOnlyCriteria);
1190
1191 Criteria criteria = criterionForType(type);
1192 criteria.add(Subqueries.propertyIn("id", idsOnlyCriteria));
1193
1194 if(doCount){
1195 criteria.setProjection(Projections.rowCount());
1196 } else {
1197 idsOnlyCriteria.setProjection(Projections.distinct(Projections.property("id")));
1198 }
1199
1200 return criteria;
1201 }
1202
1203
1204 @Override
1205 public <S extends T> List<S> findByParamWithRestrictions(Class<S> clazz, String param, String queryString,
1206 MatchMode matchmode, List<Restriction<?>> restrictions, Integer pageSize, Integer pageNumber,
1207 List<OrderHint> orderHints, List<String> propertyPaths) {
1208
1209 List<Restriction<?>> allRestrictions = new ArrayList<>();
1210 allRestrictions.add(new Restriction<String>(param, matchmode, queryString));
1211 if(restrictions != null){
1212 allRestrictions.addAll(restrictions);
1213 }
1214 Criteria criteria = createCriteria(clazz, allRestrictions, false);
1215
1216 if (pageSize != null) {
1217 criteria.setMaxResults(pageSize);
1218 if (pageNumber != null) {
1219 criteria.setFirstResult(pageNumber * pageSize);
1220 } else {
1221 criteria.setFirstResult(0);
1222 }
1223 }
1224
1225 addOrder(criteria, orderHints);
1226
1227 @SuppressWarnings("unchecked")
1228 List<S> result = criteria.list();
1229 defaultBeanInitializer.initializeAll(result, propertyPaths);
1230 return result;
1231
1232 }
1233
1234 @Override
1235 public long countByParamWithRestrictions(Class<? extends T> clazz, String param, String queryString,
1236 MatchMode matchmode, List<Restriction<?>> restrictions) {
1237
1238 List<Restriction<?>> allRestrictions = new ArrayList<>();
1239 allRestrictions.add(new Restriction<String>(param, matchmode, queryString));
1240 if(restrictions != null){
1241 allRestrictions.addAll(restrictions);
1242 }
1243 Criteria criteria = createCriteria(clazz, allRestrictions, true);
1244
1245 return (Long) criteria.uniqueResult();
1246 }
1247
1248 @Override
1249 public <S extends T> List<S> list(S example, Set<String> includeProperties, Integer limit, Integer start,
1250 List<OrderHint> orderHints, List<String> propertyPaths) {
1251 Criteria criteria = getSession().createCriteria(example.getClass());
1252 addExample(criteria, example, includeProperties);
1253
1254 addLimitAndStart(criteria, limit, start);
1255
1256 addOrder(criteria, orderHints);
1257
1258 @SuppressWarnings("unchecked")
1259 List<S> results = criteria.list();
1260 defaultBeanInitializer.initializeAll(results, propertyPaths);
1261 return results;
1262 }
1263
1264 private class PropertySelectorImpl implements PropertySelector {
1265
1266 private final Set<String> includeProperties;
1267 /**
1268 *
1269 */
1270 private static final long serialVersionUID = -3175311800911570546L;
1271
1272 public PropertySelectorImpl(Set<String> includeProperties) {
1273 this.includeProperties = includeProperties;
1274 }
1275
1276 @Override
1277 public boolean include(Object propertyValue, String propertyName, Type type) {
1278 if (includeProperties.contains(propertyName)) {
1279 return true;
1280 } else {
1281 return false;
1282 }
1283 }
1284
1285 }
1286
1287 private class CriterionWithOperator {
1288
1289 Restriction.Operator operator;
1290 Criterion criterion;
1291
1292
1293 public CriterionWithOperator(Operator operator, Criterion criterion) {
1294 super();
1295 this.operator = operator;
1296 this.criterion = criterion;
1297 }
1298
1299
1300 }
1301
1302 /**
1303 * Returns a Criteria for the given {@link Class class} or, if
1304 * <code>null</code>, for the base {@link Class class} of this DAO.
1305 *
1306 * @param clazz
1307 * @return the Criteria
1308 */
1309 protected Criteria getCriteria(Class<? extends CdmBase> clazz) {
1310 Criteria criteria = null;
1311 if (clazz == null) {
1312 criteria = getSession().createCriteria(type);
1313 } else {
1314 criteria = getSession().createCriteria(clazz);
1315 }
1316 return criteria;
1317 }
1318
1319 /**
1320 * @param clazz
1321 * @param auditEvent
1322 * @return
1323 */
1324 protected AuditQuery makeAuditQuery(Class<? extends CdmBase> clazz, AuditEvent auditEvent) {
1325 AuditQuery query = null;
1326
1327 if (clazz == null) {
1328 query = getAuditReader().createQuery().forEntitiesAtRevision(type, auditEvent.getRevisionNumber());
1329 } else {
1330 query = getAuditReader().createQuery().forEntitiesAtRevision(clazz, auditEvent.getRevisionNumber());
1331 }
1332 return query;
1333 }
1334
1335 protected AuditReader getAuditReader() {
1336 return AuditReaderFactory.get(getSession());
1337 }
1338 }