71da9dd7b5064ab6727a54377cadd8f7774f5961
[cdmlib.git] / cdmlib-persistence / src / main / java / eu / etaxonomy / cdm / persistence / dao / hibernate / common / CdmGenericDaoImpl.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.lang.reflect.Modifier;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.HashMap;
17 import java.util.HashSet;
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.commons.lang.StringUtils;
24 import org.apache.log4j.Logger;
25 import org.hibernate.Criteria;
26 import org.hibernate.HibernateException;
27 import org.hibernate.Query;
28 import org.hibernate.Session;
29 import org.hibernate.SessionFactory;
30 import org.hibernate.criterion.Criterion;
31 import org.hibernate.criterion.Order;
32 import org.hibernate.criterion.Restrictions;
33 import org.hibernate.engine.spi.SessionFactoryImplementor;
34 import org.hibernate.internal.SessionImpl;
35 import org.hibernate.metadata.ClassMetadata;
36 import org.hibernate.sql.JoinType;
37 import org.hibernate.type.BooleanType;
38 import org.hibernate.type.CollectionType;
39 import org.hibernate.type.ComponentType;
40 import org.hibernate.type.DoubleType;
41 import org.hibernate.type.EntityType;
42 import org.hibernate.type.EnumType;
43 import org.hibernate.type.FloatType;
44 import org.hibernate.type.ForeignKeyDirection;
45 import org.hibernate.type.IntegerType;
46 import org.hibernate.type.LongType;
47 import org.hibernate.type.MaterializedClobType;
48 import org.hibernate.type.OneToOneType;
49 import org.hibernate.type.SerializableType;
50 import org.hibernate.type.StringType;
51 import org.hibernate.type.Type;
52 import org.jadira.usertype.dateandtime.joda.PersistentDateTime;
53 import org.springframework.dao.DataAccessException;
54 import org.springframework.stereotype.Repository;
55 import org.springframework.util.ReflectionUtils;
56
57 import eu.etaxonomy.cdm.common.CdmUtils;
58 import eu.etaxonomy.cdm.common.DoubleResult;
59 import eu.etaxonomy.cdm.database.data.FullCoverageDataGenerator;
60 import eu.etaxonomy.cdm.hibernate.BigDecimalUserType;
61 import eu.etaxonomy.cdm.hibernate.DOIUserType;
62 import eu.etaxonomy.cdm.hibernate.EnumSetUserType;
63 import eu.etaxonomy.cdm.hibernate.EnumUserType;
64 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
65 import eu.etaxonomy.cdm.hibernate.OrcidUserType;
66 import eu.etaxonomy.cdm.hibernate.PartialUserType;
67 import eu.etaxonomy.cdm.hibernate.SeverityUserType;
68 import eu.etaxonomy.cdm.hibernate.ShiftUserType;
69 import eu.etaxonomy.cdm.hibernate.URIUserType;
70 import eu.etaxonomy.cdm.hibernate.UUIDUserType;
71 import eu.etaxonomy.cdm.hibernate.WSDLDefinitionUserType;
72 import eu.etaxonomy.cdm.model.common.CdmBase;
73 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
74 import eu.etaxonomy.cdm.model.metadata.CdmMetaData;
75 import eu.etaxonomy.cdm.persistence.dao.common.ICdmGenericDao;
76 import eu.etaxonomy.cdm.persistence.dto.ReferencingObjectDto;
77 import eu.etaxonomy.cdm.strategy.match.CacheMatcher;
78 import eu.etaxonomy.cdm.strategy.match.DefaultMatchStrategy;
79 import eu.etaxonomy.cdm.strategy.match.FieldMatcher;
80 import eu.etaxonomy.cdm.strategy.match.IMatchStrategy;
81 import eu.etaxonomy.cdm.strategy.match.IMatchable;
82 import eu.etaxonomy.cdm.strategy.match.MatchException;
83 import eu.etaxonomy.cdm.strategy.match.MatchMode;
84 import eu.etaxonomy.cdm.strategy.match.Matching;
85 import eu.etaxonomy.cdm.strategy.merge.IMergeStrategy;
86 import eu.etaxonomy.cdm.strategy.merge.MergeException;
87
88 @Repository
89 public class CdmGenericDaoImpl
90 extends CdmEntityDaoBase<CdmBase>
91 implements ICdmGenericDao{
92
93 private static final Logger logger = Logger.getLogger(CdmGenericDaoImpl.class);
94
95 private Set<Class<? extends CdmBase>> allCdmClasses = null;
96 private final Map<Class<? extends CdmBase>, Set<ReferenceHolder>> referenceMap = new HashMap<>();
97
98
99 protected class ReferenceHolder{
100 String propertyName;
101 Class<? extends CdmBase> otherClass;
102 Class<?> itemClass;
103 Class<?> targetClass; //new as item class is used for isCollection we have a duplicate here
104 public boolean isCollection(){return itemClass != null;}
105 @Override
106 public String toString(){return otherClass.getSimpleName() + "." + propertyName ;}
107 }
108
109 public CdmGenericDaoImpl() {
110 super(CdmBase.class);
111 }
112
113 // @Override
114 private List<ReferencingObjectDto> getCdmBasesByFieldAndClassDto(Class<? extends CdmBase> clazz, String propertyName,
115 CdmBase referencedCdmBase, Integer limit){
116
117 Query query = getSession().createQuery("SELECT new eu.etaxonomy.cdm.persistence.dto.ReferencingObjectDto(this.uuid, this.id) "
118 + "FROM "+ clazz.getSimpleName() + " this "
119 + "WHERE this." + propertyName +" = :referencedObject")
120 .setEntity("referencedObject", referencedCdmBase);
121
122 if (limit != null){
123 query.setMaxResults(limit);
124 }
125 @SuppressWarnings("unchecked")
126 List<ReferencingObjectDto> result = query.list();
127 result.forEach(dto->dto.setType((Class<CdmBase>)clazz));
128 return result;
129 }
130
131 @Override
132 public List<CdmBase> getCdmBasesByFieldAndClass(Class<? extends CdmBase> clazz, String propertyName, CdmBase referencedCdmBase, Integer limit){
133 Session session = super.getSession();
134
135 Criteria criteria = session.createCriteria(clazz);
136 criteria.add(Restrictions.eq(propertyName, referencedCdmBase));
137 if (limit != null){
138 criteria.setMaxResults(limit);
139 }
140
141 @SuppressWarnings("unchecked")
142 List<CdmBase> result = criteria.list();
143 return result;
144 }
145
146 @Override
147 public long getCountByFieldAndClass(Class<? extends CdmBase> clazz, String propertyName, CdmBase referencedCdmBase){
148 Query query = getSession().createQuery("SELECT count(this) "
149 + "FROM "+ clazz.getSimpleName() + " this "
150 + "WHERE this." + propertyName +" = :referencedObject")
151 .setEntity("referencedObject", referencedCdmBase);
152
153 long result =(Long)query.uniqueResult();
154 return result;
155 }
156
157 @Override
158 public List<ReferencingObjectDto> getCdmBasesWithItemInCollectionDto(Class<?> itemClass,
159 Class<? extends CdmBase> clazz, String propertyName, CdmBase item, Integer limit){
160
161 String queryStr = withItemInCollectionHql(itemClass, clazz, propertyName,
162 "new eu.etaxonomy.cdm.persistence.dto.ReferencingObjectDto(other.uuid, other.id)");
163 Query query = getSession().createQuery(queryStr).setEntity("referencedObject", item);
164 if (limit != null){
165 query.setMaxResults(limit);
166 }
167 @SuppressWarnings("unchecked")
168 List<ReferencingObjectDto> result = query.list();
169 result.forEach(dto->dto.setType((Class)clazz));
170 return result;
171 }
172
173 @Override
174 public List<CdmBase> getCdmBasesWithItemInCollection(Class<?> itemClass,
175 Class<?> clazz, String propertyName, CdmBase item, Integer limit){
176
177 String queryStr = withItemInCollectionHql(itemClass, clazz, propertyName, "other");
178 Query query = getSession().createQuery(queryStr).setEntity("referencedObject", item);
179 if (limit != null){
180 query.setMaxResults(limit);
181 }
182 @SuppressWarnings("unchecked")
183 List<CdmBase> result = query.list();
184 return result;
185 }
186
187 private String withItemInCollectionHql(Class<?> itemClass, Class<?> clazz, String propertyName, String select) {
188 String thisClassStr = itemClass.getSimpleName();
189 String otherClassStr = clazz.getSimpleName();
190 String result = "SELECT "+select+" FROM "+ thisClassStr + " this, " + otherClassStr + " other " +
191 " WHERE this = :referencedObject AND this member of other." + propertyName ;
192 return result;
193 }
194
195 @Override
196 public long getCountWithItemInCollection(Class<?> itemClass, Class<?> clazz, String propertyName,
197 CdmBase item){
198
199 String queryStr = withItemInCollectionHql(itemClass, clazz, propertyName, "count(this)");
200
201 Query query = getSession().createQuery(queryStr).setEntity("referencedObject", item);
202 long result =(Long)query.uniqueResult();
203 return result;
204 }
205
206 @Override
207 public Set<Class<? extends CdmBase>> getAllPersistedClasses(boolean includeAbstractClasses){
208 Set<Class<? extends CdmBase>> result = new HashSet<>();
209
210 SessionFactory sessionFactory = getSession().getSessionFactory();
211 Map<String,?> allClassMetadata = sessionFactory.getAllClassMetadata();
212 Collection<String> keys = allClassMetadata.keySet();
213 for (String strKey : keys){
214 if (! strKey.endsWith("_AUD")){
215 try {
216 Class<?> clazz = Class.forName(strKey);
217 boolean isAbstractClass = Modifier.isAbstract(clazz.getModifiers());
218 if (! isAbstractClass || includeAbstractClasses){
219 result.add((Class)clazz);
220 }
221 } catch (ClassNotFoundException e) {
222 String message = "Persisted CDM class not found: " + strKey;
223 logger.warn(message);
224 //TODO better throw exception, but currently some keys are really not found yet
225 // throw new RuntimeException("Persisted CDM class not found: " + strKey,e);
226 }
227 }
228 }
229 return result;
230 }
231
232 @Override
233 public Set<ReferencingObjectDto> getReferencingObjectsDto(CdmBase referencedCdmBase){
234 Set<ReferencingObjectDto> result = new HashSet<>();
235 if (referencedCdmBase == null) {
236 return null;
237 }
238 try {
239
240 referencedCdmBase = HibernateProxyHelper.deproxy(referencedCdmBase);
241 Class<? extends CdmBase> referencedClass = referencedCdmBase.getClass();
242
243 Set<ReferenceHolder> holderSet = getOrMakeHolderSet(referencedClass);
244 for (ReferenceHolder refHolder: holderSet){
245 handleReferenceHolderDto(referencedCdmBase, result, refHolder, false);
246 }
247 return result;
248 } catch (Exception e) {
249 e.printStackTrace();
250 throw new RuntimeException(e);
251 }
252 }
253
254 @Override
255 public Set<CdmBase> getReferencingObjects(CdmBase referencedCdmBase){
256 Set<CdmBase> result = new HashSet<>();
257 if (referencedCdmBase == null) {
258 return null;
259 }
260 try {
261
262 referencedCdmBase = HibernateProxyHelper.deproxy(referencedCdmBase);
263 Class<? extends CdmBase> referencedClass = referencedCdmBase.getClass();
264
265 Set<ReferenceHolder> holderSet = getOrMakeHolderSet(referencedClass);
266 //Integer count = getReferencingObjectsCount(referencedCdmBase);
267 for (ReferenceHolder refHolder: holderSet){
268 // if (count > 100000) {
269 // handleReferenceHolder(referencedCdmBase, result, refHolder, true);
270 // }else{
271 handleReferenceHolder(referencedCdmBase, result, refHolder, false);
272 // }
273 }
274 return result;
275 } catch (Exception e) {
276 e.printStackTrace();
277 throw new RuntimeException(e);
278 }
279 }
280
281 @Override
282 public long getReferencingObjectsCount(CdmBase referencedCdmBase){
283 long result = 0;
284 if (referencedCdmBase == null) {
285 return 0;
286 }
287 try {
288
289 referencedCdmBase = HibernateProxyHelper.deproxy(referencedCdmBase);
290 Class<? extends CdmBase> referencedClass = referencedCdmBase.getClass();
291
292 Set<ReferenceHolder> holderSet = getOrMakeHolderSet(referencedClass);
293 for (ReferenceHolder refHolder: holderSet){
294 result =+ handleReferenceHolderForCount(referencedCdmBase, result, refHolder);
295 }
296 return result;
297 } catch (Exception e) {
298 e.printStackTrace();
299 throw new RuntimeException(e);
300 }
301
302 }
303
304 protected Set<ReferenceHolder> getOrMakeHolderSet(
305 Class<? extends CdmBase> referencedClass)
306 throws ClassNotFoundException, NoSuchFieldException {
307 Set<ReferenceHolder> holderSet = referenceMap.get(referencedClass);
308 if (holderSet == null){
309 holderSet = makeHolderSet(referencedClass);
310 referenceMap.put(referencedClass, holderSet);
311 }
312 return holderSet;
313 }
314
315 @Override
316 public Set<CdmBase> getReferencingObjectsForDeletion(CdmBase referencedCdmBase){
317 if (referencedCdmBase == null){
318 return null;
319 }
320 Set<CdmBase> result = getReferencingObjects(referencedCdmBase);
321 Set<ReferenceHolder> holderSet = referenceMap.get(IdentifiableEntity.class);
322 try {
323 if (holderSet == null){
324 holderSet = makeHolderSet(IdentifiableEntity.class);
325 referenceMap.put(IdentifiableEntity.class, holderSet);
326 }
327 Set<CdmBase> resultIdentifiableEntity = new HashSet<>();
328 for (ReferenceHolder refHolder: holderSet){
329 handleReferenceHolder(referencedCdmBase, resultIdentifiableEntity, refHolder, false);
330 }
331 result.removeAll(resultIdentifiableEntity);
332
333 return result;
334 } catch (Exception e) {
335 e.printStackTrace();
336 throw new RuntimeException(e);
337 }
338 }
339
340 private void handleReferenceHolderDto(CdmBase referencedCdmBase,
341 Set<ReferencingObjectDto> result, ReferenceHolder refHolder, boolean limited) {
342
343 boolean isCollection = refHolder.isCollection();
344 if (isCollection){
345 if (limited){
346 result.addAll(getCdmBasesWithItemInCollectionDto(refHolder.itemClass, refHolder.otherClass, refHolder.propertyName, referencedCdmBase, 100));
347 }else{
348 result.addAll(getCdmBasesWithItemInCollectionDto(refHolder.itemClass, refHolder.otherClass, refHolder.propertyName, referencedCdmBase, null));
349 }
350 }else{
351 if (limited){
352 result.addAll(getCdmBasesByFieldAndClassDto(refHolder.otherClass, refHolder.propertyName, referencedCdmBase, 100));
353 }else{
354 result.addAll(getCdmBasesByFieldAndClassDto(refHolder.otherClass, refHolder.propertyName, referencedCdmBase, null));
355 }
356 }
357 }
358
359 private void handleReferenceHolder(CdmBase referencedCdmBase,
360 Set<CdmBase> result, ReferenceHolder refHolder, boolean limited) {
361
362 boolean isCollection = refHolder.isCollection();
363 if (isCollection){
364 if (limited){
365 result.addAll(getCdmBasesWithItemInCollection(refHolder.itemClass, refHolder.otherClass, refHolder.propertyName, referencedCdmBase, 100));
366 }else{
367 result.addAll(getCdmBasesWithItemInCollection(refHolder.itemClass, refHolder.otherClass, refHolder.propertyName, referencedCdmBase, null));
368 }
369 }else{
370 if (limited){
371 result.addAll(getCdmBasesByFieldAndClass(refHolder.otherClass, refHolder.propertyName, referencedCdmBase, 100));
372 }else{
373 result.addAll(getCdmBasesByFieldAndClass(refHolder.otherClass, refHolder.propertyName, referencedCdmBase, null));
374 }
375 }
376 }
377
378 private Long handleReferenceHolderForCount(CdmBase referencedCdmBase,
379 Long result, ReferenceHolder refHolder) {
380 boolean isCollection = refHolder.isCollection();
381 if (isCollection){
382 result += getCountWithItemInCollection(refHolder.itemClass, refHolder.otherClass, refHolder.propertyName, referencedCdmBase);
383 }else{
384 result += getCountByFieldAndClass(refHolder.otherClass, refHolder.propertyName, referencedCdmBase);
385 }
386 return result;
387 }
388
389 /**
390 * @param referencedClass
391 * @return
392 * @throws NoSuchFieldException
393 * @throws ClassNotFoundException
394 */
395 protected Set<ReferenceHolder> makeHolderSet(Class<?> referencedClass) throws ClassNotFoundException, NoSuchFieldException {
396 Set<ReferenceHolder> result = new HashSet<>();
397
398 //init
399 if (allCdmClasses == null){
400 allCdmClasses = getAllPersistedClasses(false); //findAllCdmClasses();
401 }
402 SessionFactory sessionFactory = getSession().getSessionFactory();
403
404 for (Class<? extends CdmBase> cdmClass : allCdmClasses){
405 ClassMetadata classMetadata = sessionFactory.getClassMetadata(cdmClass);
406 Type[] propertyTypes = classMetadata.getPropertyTypes();
407 int propertyNr = 0;
408 for (Type propertyType: propertyTypes){
409 String propertyName = classMetadata.getPropertyNames()[propertyNr];
410 makePropertyType(result, referencedClass, sessionFactory, cdmClass, propertyType, propertyName, false);
411 propertyNr++;
412 }
413 }
414 return result;
415 }
416
417 private void makePropertyType(
418 // CdmBase referencedCdmBase,
419 Set<ReferenceHolder> result,
420 Class<?> referencedClass,
421 SessionFactory sessionFactory, Class<? extends CdmBase> cdmClass,
422 Type propertyType, String propertyName, boolean isCollection)
423 throws ClassNotFoundException, NoSuchFieldException {
424
425 if (propertyType.isEntityType()){
426 EntityType entityType = (EntityType)propertyType;
427 String associatedEntityName = entityType.getAssociatedEntityName();
428 Class<?> entityClass = Class.forName(associatedEntityName);
429 if (entityClass.isInterface()){
430 logger.debug("There is an interface");
431 }
432 if (entityType instanceof OneToOneType){
433 OneToOneType oneToOneType = (OneToOneType)entityType;
434 ForeignKeyDirection direction = oneToOneType.getForeignKeyDirection();
435 if (direction == ForeignKeyDirection.TO_PARENT){ //this
436 return;
437 }
438 }
439 if (entityClass.isAssignableFrom(referencedClass)){
440 makeSingleProperty(referencedClass, entityClass, propertyName, cdmClass, result, isCollection);
441 }
442 }else if (propertyType.isCollectionType()){
443 CollectionType collectionType = (CollectionType)propertyType;
444 //String role = collectionType.getRole();
445 Type elType = collectionType.getElementType((SessionFactoryImplementor)sessionFactory);
446 makePropertyType(result, referencedClass, sessionFactory, cdmClass, elType, propertyName, true);
447 }else if (propertyType.isAnyType()){
448 // AnyType anyType = (AnyType)propertyType;
449 Field field = cdmClass.getDeclaredField(propertyName);
450 Class<?> returnType = field.getType();
451 if (returnType.isInterface()){
452 logger.debug("There is an interface");
453 }
454 if (returnType.isAssignableFrom(referencedClass)){
455 makeSingleProperty(referencedClass, returnType, propertyName, cdmClass, result, isCollection);
456 }
457 }else if (propertyType.isComponentType()){
458 ComponentType componentType = (ComponentType)propertyType;
459 Type[] subTypes = componentType.getSubtypes();
460 // Field field = cdmClass.getDeclaredField(propertyName);
461 // Class returnType = field.getType();
462 int propertyNr = 0;
463 for (Type subType: subTypes){
464 String subPropertyName = componentType.getPropertyNames()[propertyNr];
465 if (!isNoDoType(subType)){
466 logger.warn("SubType not yet handled: " + subType);
467 }
468 // handlePropertyType(referencedCdmBase, result, referencedClass,
469 // sessionFactory, cdmClass, subType, subPropertyName, isCollection);
470 propertyNr++;
471 }
472 }else if (isNoDoType(propertyType)){
473 //do nothing
474 }else{
475 logger.warn("propertyType not yet handled: " + propertyType.getName());
476 }
477 //OLD:
478 // if (! type.isInterface()){
479 // if (referencedClass.isAssignableFrom(type)||
480 // type.isAssignableFrom(referencedClass) && CdmBase.class.isAssignableFrom(type)){
481 // handleSingleClass(referencedClass, type, field, cdmClass, result, referencedCdmBase, false);
482 // }
483 // //interface
484 // }else if (type.isAssignableFrom(referencedClass)){
485 // handleSingleClass(referencedClass, type, field, cdmClass, result, referencedCdmBase, false);
486
487 }
488
489 private boolean makeSingleProperty(Class<?> itemClass, Class<?> type, String propertyName, Class<? extends CdmBase> cdmClass, Set<ReferenceHolder> result,/*CdmBase item,*/ boolean isCollection){
490 // String fieldName = StringUtils.rightPad(propertyName, 30);
491 // String className = StringUtils.rightPad(cdmClass.getSimpleName(), 30);
492 // String returnTypeName = StringUtils.rightPad(type.getSimpleName(), 30);
493
494 // logger.debug(fieldName + "\t\t" + className + "\t\t" + returnTypeName);
495 ReferenceHolder refHolder = new ReferenceHolder();
496 refHolder.propertyName = propertyName;
497 refHolder.otherClass = cdmClass;
498 refHolder.itemClass = (isCollection ? itemClass : null) ;
499 refHolder.targetClass = type ;
500
501 result.add(refHolder);
502 return true;
503 }
504
505 protected static boolean isNoDoType(Type propertyType) {
506 boolean result = false;
507 Class<?>[] classes = new Class[]{
508 PersistentDateTime.class,
509 WSDLDefinitionUserType.class,
510 UUIDUserType.class,
511 PartialUserType.class,
512 StringType.class,
513 BooleanType.class,
514 IntegerType.class,
515 MaterializedClobType.class,
516 LongType.class,
517 FloatType.class,
518 SerializableType.class,
519 DoubleType.class,
520 URIUserType.class,
521 EnumType.class,
522 EnumUserType.class,
523 DOIUserType.class,
524 OrcidUserType.class,
525 ShiftUserType.class,
526 EnumSetUserType.class,
527 SeverityUserType.class,
528 BigDecimalUserType.class,
529 };
530 Set<String> classNames = new HashSet<>();
531 for (Class<?> clazz: classes){
532 classNames.add(clazz.getCanonicalName());
533 if (clazz == propertyType.getClass()){
534 return true;
535 }
536 }
537 String propertyTypeClassName = propertyType.getName();
538 if (classNames.contains(propertyTypeClassName)){
539 return true;
540 }
541 return result;
542 }
543
544 @Override
545 public List<CdmBase> getHqlResult(String hqlQuery, Object[] params){
546 Query query = getSession().createQuery(hqlQuery);
547 for(int i = 0; i<params.length; i++){
548 query.setParameter(String.valueOf(i), params[i]); //for some reason using int, not String, throws exceptions, this seems to be a hibernate bug
549 }
550 @SuppressWarnings("unchecked")
551 List<CdmBase> result = query.list();
552 return result;
553 }
554
555 @Override
556 public Query getHqlQuery(String hqlQuery){
557 Query query = getSession().createQuery(hqlQuery);
558 return query;
559 }
560
561 @Override
562 public <T extends CdmBase> void merge(T cdmBase1, T cdmBase2, IMergeStrategy mergeStrategy) throws MergeException {
563 SessionImpl session = (SessionImpl) getSession();
564
565 DeduplicationHelper helper = new DeduplicationHelper(session, this);
566 helper.merge(cdmBase1, cdmBase2, mergeStrategy);
567 }
568
569
570 @Override
571 public <T extends CdmBase> boolean isMergeable(T cdmBase1, T cdmBase2, IMergeStrategy mergeStrategy) throws MergeException {
572 SessionImpl session = (SessionImpl) getSession();
573 DeduplicationHelper helper = new DeduplicationHelper(session, this);
574 return helper.isMergeable(cdmBase1, cdmBase2, mergeStrategy);
575 }
576
577
578 @Override
579 public <T extends CdmBase> T find(Class<T> clazz, int id){
580 Session session;
581 session = getSession();
582 //session = getSession().getSessionFactory().getCurrentSession();
583 T o = session.get(clazz, id);
584 return o;
585 }
586
587 @Override
588 public <T extends CdmBase> T find(Class<T> clazz, int id, List<String> propertyPaths){
589 Session session;
590 session = getSession();
591 T bean = session.get(clazz, id);
592 if(bean == null){
593 return bean;
594 }
595 defaultBeanInitializer.initialize(bean, propertyPaths);
596 return bean;
597 }
598
599 @Override
600 public <T extends CdmBase> T find(Class<T> clazz, UUID uuid){
601 return find(clazz, uuid, null);
602 }
603
604 @Override
605 public <T extends CdmBase> T find(Class<T> clazz, UUID uuid, List<String> propertyPaths){
606 Session session = getSession();
607 Criteria crit = session.createCriteria(type);
608 crit.add(Restrictions.eq("uuid", uuid));
609 crit.addOrder(Order.desc("created"));
610 @SuppressWarnings("unchecked")
611 List<T> results = crit.list();
612 if (results.isEmpty()){
613 return null;
614 }else{
615 if(results.size() > 1){
616 logger.error("findByUuid() delivers more than one result for UUID: " + uuid);
617 }
618 T result = results.get(0);
619 if (result == null || propertyPaths == null){
620 return result;
621 }else{
622 defaultBeanInitializer.initialize(result, propertyPaths);
623 return result;
624 }
625 }
626 }
627
628 @Override
629 public <T extends IMatchable> List<T> findMatching(T objectToMatch,
630 IMatchStrategy matchStrategy) throws MatchException {
631
632 getSession().flush();
633 try {
634 List<T> result = new ArrayList<>();
635 if(objectToMatch == null){
636 return result;
637 }
638 if (matchStrategy == null){
639 matchStrategy = DefaultMatchStrategy.NewInstance(objectToMatch.getClass());
640 }
641 result.addAll(findMatchingNullSafe(objectToMatch, matchStrategy));
642 return result;
643 } catch (IllegalArgumentException e) {
644 throw new MatchException(e);
645 } catch (IllegalAccessException e) {
646 throw new MatchException(e);
647 }
648 }
649
650 private <T extends IMatchable> List<T> findMatchingNullSafe(T objectToMatch,
651 IMatchStrategy matchStrategy) throws IllegalArgumentException, IllegalAccessException, MatchException {
652
653 List<T> result = new ArrayList<>();
654 Session session = getSession();
655 Class<?> matchClass = objectToMatch.getClass();
656 ClassMetadata classMetaData = session.getSessionFactory().getClassMetadata(matchClass.getCanonicalName());
657 Criteria criteria = session.createCriteria(matchClass);
658 boolean noMatch = makeCriteria(objectToMatch, matchStrategy, classMetaData, criteria);
659 if (logger.isDebugEnabled()){logger.debug(criteria);}
660 //session.flush();
661 if (noMatch == false){
662 @SuppressWarnings("unchecked")
663 List<T> matchCandidates = criteria.list();
664 matchCandidates.remove(objectToMatch);
665 for (T matchCandidate : matchCandidates ){
666 if (matchStrategy.invoke(objectToMatch, matchCandidate).isSuccessful()){
667 result.add(matchCandidate);
668 }else{
669 logger.info("Match candidate did not match: " + matchCandidate);
670 }
671 }
672 }
673 return result;
674 }
675
676 private <T> boolean makeCriteria(T objectToMatch,
677 IMatchStrategy matchStrategy, ClassMetadata classMetaData,
678 Criteria criteria) throws IllegalAccessException, MatchException {
679
680 Matching matching = matchStrategy.getMatching((IMatchable)objectToMatch);
681 boolean noMatch = false;
682 Map<String, List<MatchMode>> replaceMatchers = new HashMap<>();
683 for (CacheMatcher cacheMatcher: matching.getCacheMatchers()){
684 Field protectedField = cacheMatcher.getProtectedField(matching);
685 boolean cacheProtected = protectedField == null ? false : (Boolean)protectedField.get(objectToMatch);
686 if (cacheProtected == true){
687 String cacheValue = (String)cacheMatcher.getField().get(objectToMatch);
688 if (StringUtils.isBlank(cacheValue)){
689 return true; //no match
690 }else{
691 criteria.add(Restrictions.eq(cacheMatcher.getPropertyName(), cacheValue));
692 criteria.add(Restrictions.eq(cacheMatcher.getProtectedPropertyName(), cacheProtected));
693
694 List<DoubleResult<String, MatchMode>> replacementModes = cacheMatcher.getReplaceMatchModes(matching);
695 for (DoubleResult<String, MatchMode> replacementMode: replacementModes ){
696 String propertyName = replacementMode.getFirstResult();
697 List<MatchMode> replaceMatcherList = replaceMatchers.get(propertyName);
698 if (replaceMatcherList == null){
699 replaceMatcherList = new ArrayList<>();
700 replaceMatchers.put(propertyName, replaceMatcherList);
701 }
702 replaceMatcherList.add(replacementMode.getSecondResult());
703 }
704
705 }
706 }
707 }
708 for (FieldMatcher fieldMatcher : matching.getFieldMatchers(false)){
709 String propertyName = fieldMatcher.getPropertyName();
710 Type propertyType = classMetaData.getPropertyType(propertyName);
711 Object value = fieldMatcher.getField().get(objectToMatch);
712 List<MatchMode> matchModes= new ArrayList<>();
713 matchModes.add(fieldMatcher.getMatchMode());
714 if (replaceMatchers.get(propertyName) != null){
715 matchModes.addAll(replaceMatchers.get(propertyName));
716 }
717
718 boolean isIgnore = false;
719 for (MatchMode matchMode : matchModes){
720 isIgnore |= matchMode.isIgnore(value);
721 }
722 if (! isIgnore ){
723 if (propertyType.isComponentType()){
724 matchComponentType(criteria, fieldMatcher, propertyName, value, matchModes);
725 }else{
726 noMatch = matchNonComponentType(criteria, fieldMatcher, propertyName, value, matchModes, propertyType);
727 }
728 }
729 if (noMatch){
730 return noMatch;
731 }
732 }
733 return noMatch;
734 }
735
736 private void matchComponentType(Criteria criteria,
737 FieldMatcher fieldMatcher, String propertyName, Object value,
738 List<MatchMode> matchModes) throws MatchException, IllegalAccessException {
739 if (value == null){
740 boolean requiresSecondNull = requiresSecondNull(matchModes, null);
741 if (requiresSecondNull){
742 criteria.add(Restrictions.isNull(propertyName));
743 }else{
744 //this should not happen, should be handled as ignore before
745 logger.warn("Component type not yet implemented for (null) value: " + propertyName);
746 throw new MatchException("Component type not yet fully implemented for (null) value. Property: " + propertyName);
747 }
748 }else{
749 Class<?> componentClass = fieldMatcher.getField().getType();
750 Map<String, Field> fields = CdmUtils.getAllFields(componentClass, Object.class, false, false, true, false);
751 for (String fieldName : fields.keySet()){
752 String restrictionPath = propertyName +"."+fieldName;
753 Object componentValue = fields.get(fieldName).get(value);
754 //TODO differentiate matchMode
755 createCriterion(criteria, restrictionPath, componentValue, matchModes);
756 }
757 }
758 }
759
760 private boolean matchNonComponentType(Criteria criteria,
761 FieldMatcher fieldMatcher,
762 String propertyName,
763 Object value,
764 List<MatchMode> matchModes,
765 Type propertyType)
766 throws HibernateException, DataAccessException, MatchException, IllegalAccessException{
767
768 boolean noMatch = false;
769 if (isRequired(matchModes) && value == null){
770 noMatch = true;
771 return noMatch;
772 }else if (requiresSecondNull(matchModes,value)){
773 criteria.add(Restrictions.isNull(propertyName));
774 }else{
775 if (isMatch(matchModes)){
776 if (propertyType.isCollectionType()){
777 //TODO collection not yet handled for match
778 }else{
779 JoinType joinType = JoinType.INNER_JOIN;
780 if (! requiresSecondValue(matchModes,value)){
781 joinType = JoinType.LEFT_OUTER_JOIN;
782 }
783 Criteria matchCriteria = criteria.createCriteria(propertyName, joinType).add(Restrictions.isNotNull("id"));
784 @SuppressWarnings("rawtypes")
785 Class matchClass = value.getClass();
786 if (IMatchable.class.isAssignableFrom(matchClass)){
787 IMatchStrategy valueMatchStrategy = fieldMatcher.getMatchStrategy() != null? fieldMatcher.getMatchStrategy() : DefaultMatchStrategy.NewInstance(matchClass);
788 ClassMetadata valueClassMetaData = getSession().getSessionFactory().getClassMetadata(matchClass.getCanonicalName());
789 noMatch = makeCriteria(value, valueMatchStrategy, valueClassMetaData, matchCriteria);
790 }else{
791 logger.error("Class to match (" + matchClass + ") is not of type IMatchable");
792 throw new MatchException("Class to match (" + matchClass + ") is not of type IMatchable");
793 }
794 }
795 }else if (isEqual(matchModes)){
796 createCriterion(criteria, propertyName, value, matchModes);
797 }else {
798 logger.warn("Unhandled match mode: " + matchModes + ", value: " + (value==null?"null":value));
799 }
800 }
801 return noMatch;
802 }
803
804 private void createCriterion(Criteria criteria, String propertyName,
805 Object value, List<MatchMode> matchModes) throws MatchException {
806 Criterion finalRestriction = null;
807 Criterion equalRestriction = Restrictions.eq(propertyName, value);
808 Criterion nullRestriction = Restrictions.isNull(propertyName);
809 if (this.requiresSecondValue(matchModes, value)){
810 finalRestriction = equalRestriction;
811 }else if (requiresSecondNull(matchModes, value) ){
812 finalRestriction = nullRestriction;
813 }else{
814 finalRestriction = Restrictions.or(equalRestriction, nullRestriction);
815 }
816 //return finalRestriction;
817 criteria.add(finalRestriction);
818 }
819
820 private boolean requiresSecondNull(List<MatchMode> matchModes, Object value) throws MatchException {
821 boolean result = true;
822 for (MatchMode matchMode: matchModes){
823 result &= matchMode.requiresSecondNull(value);
824 }
825 return result;
826 }
827
828 private boolean requiresSecondValue(List<MatchMode> matchModes, Object value) {
829 boolean result = true;
830 for (MatchMode matchMode: matchModes){
831 result &= matchMode.requiresSecondValue(value);
832 }
833 return result;
834 }
835
836 private boolean isRequired(List<MatchMode> matchModes) {
837 boolean result = true;
838 for (MatchMode matchMode: matchModes){
839 result &= matchMode.isRequired();
840 }
841 return result;
842 }
843
844 /**
845 * Returns true if at least one match mode is of type MATCH_XXX
846 * @param matchModes
847 * @param value
848 * @return
849 * @throws MatchException
850 */
851 private boolean isMatch(List<MatchMode> matchModes) throws MatchException {
852 boolean result = false;
853 for (MatchMode matchMode: matchModes){
854 result |= matchMode.isMatch();
855 }
856 return result;
857 }
858
859 /**
860 * Returns true if at least one match mode is of typ EQUAL_XXX
861 * @param matchModes
862 * @param value
863 * @return
864 * @throws MatchException
865 */
866 private boolean isEqual(List<MatchMode> matchModes) throws MatchException {
867 boolean result = false;
868 for (MatchMode matchMode: matchModes){
869 result |= matchMode.isEqual();
870 }
871 return result;
872 }
873
874 @Override
875 public void saveMetaData(CdmMetaData cdmMetaData) {
876 getSession().saveOrUpdate(cdmMetaData);
877 }
878
879 @Override
880 public List<CdmMetaData> getMetaData() {
881 Session session = getSession();
882 Criteria crit = session.createCriteria(CdmMetaData.class);
883 @SuppressWarnings("unchecked")
884 List<CdmMetaData> results = crit.list();
885 return results;
886 }
887
888 @Override
889 public Object initializeCollection(UUID ownerUuid, String fieldName, List<String> appendedPropertyPaths) {
890 List<String> propertyPaths = new ArrayList<>();
891 propertyPaths.add(fieldName);
892 if(appendedPropertyPaths != null && !appendedPropertyPaths.isEmpty()) {
893 for(String app : appendedPropertyPaths) {
894 propertyPaths.add(fieldName + "." + app);
895 }
896 }
897 CdmBase cdmBase = load(ownerUuid, propertyPaths);
898 Field field = ReflectionUtils.findField(cdmBase.getClass(), fieldName);
899 field.setAccessible(true);
900 Object obj;
901 try {
902 obj = field.get(cdmBase);
903 } catch (IllegalAccessException e) {
904 throw new IllegalArgumentException("Requested object is not accessible");
905 }
906 if(obj instanceof Collection || obj instanceof Map) {
907 return obj;
908 } else {
909 throw new IllegalArgumentException("Field name provided does not correspond to a collection or map");
910 }
911 }
912
913 @Override
914 public Object initializeCollection(UUID ownerUuid, String fieldName) {
915 return initializeCollection(ownerUuid, fieldName, null);
916 }
917
918 @Override
919 public boolean isEmpty(UUID ownerUuid, String fieldName) {
920 Object col = initializeCollection(ownerUuid, fieldName);
921 if(col instanceof Collection) {
922 return ((Collection<?>)col).isEmpty();
923 } else if(col instanceof Map){
924 return ((Map<?,?>)col).isEmpty();
925 }
926
927 return false;
928 }
929
930 @Override
931 public int size(UUID ownerUuid, String fieldName) {
932 Object col = initializeCollection(ownerUuid, fieldName);
933 if(col instanceof Collection) {
934 return ((Collection<?>)col).size();
935 } else if(col instanceof Map){
936 return ((Map<?,?>)col).size();
937 }
938 return 0;
939 }
940
941 @Override
942 public Object get(UUID ownerUuid, String fieldName, int index) {
943 Object col = initializeCollection(ownerUuid, fieldName);
944 if(col instanceof List) {
945 return ((List<?>)col).get(index);
946 } else {
947 throw new IllegalArgumentException("Field name provided does not correspond to a list");
948 }
949 }
950
951 @Override
952 public boolean contains(UUID ownerUuid, String fieldName, Object element) {
953 Object col = initializeCollection(ownerUuid, fieldName);
954 if(col instanceof Collection) {
955 return ((Collection<?>)col).contains(element);
956 } else {
957 throw new IllegalArgumentException("Field name provided does not correspond to a collection");
958 }
959 }
960
961 @Override
962 public boolean containsKey(UUID ownerUuid, String fieldName, Object key) {
963 Object col = initializeCollection(ownerUuid, fieldName);
964 if(col instanceof Map) {
965 return ((Map<?,?>)col).containsKey(key);
966 } else {
967 throw new IllegalArgumentException("Field name provided does not correspond to a map");
968 }
969 }
970
971 @Override
972 public boolean containsValue(UUID ownerUuid, String fieldName, Object value) {
973 Object col = initializeCollection(ownerUuid, fieldName);
974 if(col instanceof Map) {
975 return ((Map<?,?>)col).containsValue(value);
976 } else {
977 throw new IllegalArgumentException("Field name provided does not correspond to a map");
978 }
979 }
980
981 @Override
982 public void createFullSampleData() {
983 FullCoverageDataGenerator dataGenerator = new FullCoverageDataGenerator();
984 dataGenerator.fillWithData(getSession());
985 }
986
987 @Override
988 public List<UUID> listUuid(Class<? extends CdmBase> clazz) {
989 String queryString = "SELECT uuid FROM " + clazz.getSimpleName();
990 Query query = getSession().createQuery(queryString);
991 @SuppressWarnings("unchecked")
992 List<UUID> list = query.list();
993 return list;
994 }
995 }