Project

General

Profile

Download (33.6 KB) Statistics
| Branch: | Tag: | Revision:
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.strategy.match.CacheMatcher;
77
import eu.etaxonomy.cdm.strategy.match.DefaultMatchStrategy;
78
import eu.etaxonomy.cdm.strategy.match.FieldMatcher;
79
import eu.etaxonomy.cdm.strategy.match.IMatchStrategy;
80
import eu.etaxonomy.cdm.strategy.match.IMatchable;
81
import eu.etaxonomy.cdm.strategy.match.MatchException;
82
import eu.etaxonomy.cdm.strategy.match.MatchMode;
83
import eu.etaxonomy.cdm.strategy.match.Matching;
84
import eu.etaxonomy.cdm.strategy.merge.IMergeStrategy;
85
import eu.etaxonomy.cdm.strategy.merge.MergeException;
86

    
87
@Repository
88
public class CdmGenericDaoImpl
89
         extends CdmEntityDaoBase<CdmBase>
90
         implements ICdmGenericDao{
91

    
92
    private static final Logger logger = Logger.getLogger(CdmGenericDaoImpl.class);
93

    
94
	private Set<Class<? extends CdmBase>> allCdmClasses = null;
95
	private final Map<Class<? extends CdmBase>, Set<ReferenceHolder>> referenceMap = new HashMap<>();
96

    
97

    
98
	protected class ReferenceHolder{
99
		String propertyName;
100
		Class<? extends CdmBase> otherClass;
101
		Class<?> itemClass;
102
		Class<?> targetClass;  //new as item class is used for isCollection we have a duplicate here
103
		public boolean isCollection(){return itemClass != null;}
104
		@Override
105
        public String toString(){return otherClass.getSimpleName() + "." + propertyName ;}
106
	}
107

    
108
	public CdmGenericDaoImpl() {
109
		super(CdmBase.class);
110
	}
111

    
112
	@Override
113
    public List<CdmBase> getCdmBasesByFieldAndClass(Class<? extends CdmBase> clazz, String propertyName, CdmBase referencedCdmBase, Integer limit){
114
        Session session = super.getSession();
115

    
116
      Criteria criteria = session.createCriteria(clazz);
117
      criteria.add(Restrictions.eq(propertyName, referencedCdmBase));
118
      if (limit != null){
119
          criteria.setMaxResults(limit);
120
      }
121

    
122
        @SuppressWarnings("unchecked")
123
        List<CdmBase> result = criteria.list();
124
        return result;
125
	}
126

    
127
	@Override
128
    public long getCountByFieldAndClass(Class<? extends CdmBase> clazz, String propertyName, CdmBase referencedCdmBase){
129
        Session session = super.getSession();
130
        Query query = session.createQuery("SELECT count(this) "
131
                + "FROM "+ clazz.getSimpleName() + " this "
132
                + "WHERE this." + propertyName +" = :referencedObject")
133
                .setEntity("referencedObject", referencedCdmBase);
134

    
135
        long result =(Long)query.uniqueResult();
136
        return result;
137
    }
138

    
139
	@Override
140
	public List<CdmBase> getCdmBasesWithItemInCollection(Class<?> itemClass,
141
	        Class<?> clazz, String propertyName, CdmBase item, Integer limit){
142

    
143
	    Session session = super.getSession();
144
		String thisClassStr = itemClass.getSimpleName();
145
		String otherClassStr = clazz.getSimpleName();
146
		String queryStr = " SELECT other FROM "+ thisClassStr + " this, " + otherClassStr + " other " +
147
			" WHERE this = :referencedObject AND this member of other." + propertyName ;
148
		Query query = session.createQuery(queryStr).setEntity("referencedObject", item);
149
		if (limit != null){
150
		    query.setMaxResults(limit);
151
		}
152
		@SuppressWarnings("unchecked")
153
		List<CdmBase> result = query.list();
154
		return result;
155
	}
156

    
157
	@Override
158
    public long getCountWithItemInCollection(Class<?> itemClass, Class<?> clazz, String propertyName,
159
            CdmBase item){
160
        Session session = super.getSession();
161
        String thisClassStr = itemClass.getSimpleName();
162
        String otherClassStr = clazz.getSimpleName();
163
        String queryStr = " SELECT count(this) FROM "+ thisClassStr + " this, " + otherClassStr + " other " +
164
            " WHERE this = :referencedObject AND this member of other."+propertyName ;
165

    
166
        Query query = session.createQuery(queryStr).setEntity("referencedObject", item);
167
        long result =(Long)query.uniqueResult();
168
        return result;
169
    }
170

    
171
	@Override
172
	public Set<Class<? extends CdmBase>> getAllPersistedClasses(boolean includeAbstractClasses){
173
		Set<Class<? extends CdmBase>> result = new HashSet<>();
174

    
175
		SessionFactory sessionFactory = getSession().getSessionFactory();
176
		Map<String,?> allClassMetadata = sessionFactory.getAllClassMetadata();
177
		Collection<String> keys = allClassMetadata.keySet();
178
		for (String strKey : keys){
179
			if (! strKey.endsWith("_AUD")){
180
				try {
181
                    Class<?> clazz = Class.forName(strKey);
182
					boolean isAbstractClass = Modifier.isAbstract(clazz.getModifiers());
183
					if (! isAbstractClass || includeAbstractClasses){
184
						result.add((Class)clazz);
185
					}
186
				} catch (ClassNotFoundException e) {
187
				    String message = "Persisted CDM class not found: " + strKey;
188
				    logger.warn(message);
189
				    //TODO better throw exception, but currently some keys are really not found yet
190
//					throw new RuntimeException("Persisted CDM class not found: " + strKey,e);
191
				}
192
			}
193
		}
194
		return result;
195
	}
196

    
197
	@Override
198
	public Set<CdmBase> getReferencingObjects(CdmBase referencedCdmBase){
199
		Set<CdmBase> result = new HashSet<>();
200
		if (referencedCdmBase == null) {
201
			return null;
202
		}
203
		try {
204

    
205
			referencedCdmBase = HibernateProxyHelper.deproxy(referencedCdmBase);
206
			Class<? extends CdmBase> referencedClass = referencedCdmBase.getClass();
207

    
208
			Set<ReferenceHolder> holderSet = getOrMakeHolderSet(referencedClass);
209
			//Integer count = getReferencingObjectsCount(referencedCdmBase);
210
			for (ReferenceHolder refHolder: holderSet){
211
//			    if (count > 100000) {
212
//                    handleReferenceHolder(referencedCdmBase, result, refHolder, true);
213
//                }else{
214
                    handleReferenceHolder(referencedCdmBase, result, refHolder, false);
215
//                }
216
			}
217
			return result;
218
		} catch (Exception e) {
219
			e.printStackTrace();
220
			throw new RuntimeException(e);
221
		}
222

    
223
	}
224

    
225
	@Override
226
    public long getReferencingObjectsCount(CdmBase referencedCdmBase){
227
        long result = 0;
228
        if (referencedCdmBase == null) {
229
            return 0;
230
        }
231
        try {
232

    
233
            referencedCdmBase = HibernateProxyHelper.deproxy(referencedCdmBase);
234
            Class<? extends CdmBase> referencedClass = referencedCdmBase.getClass();
235

    
236
            Set<ReferenceHolder> holderSet = getOrMakeHolderSet(referencedClass);
237
            for (ReferenceHolder refHolder: holderSet){
238
                result =+ handleReferenceHolderForCount(referencedCdmBase, result, refHolder);
239
            }
240
            return result;
241
        } catch (Exception e) {
242
            e.printStackTrace();
243
            throw new RuntimeException(e);
244
        }
245

    
246
    }
247

    
248
	protected Set<ReferenceHolder> getOrMakeHolderSet(
249
			Class<? extends CdmBase> referencedClass)
250
			throws ClassNotFoundException, NoSuchFieldException {
251
		Set<ReferenceHolder> holderSet = referenceMap.get(referencedClass);
252
		if (holderSet == null){
253
			holderSet = makeHolderSet(referencedClass);
254
			referenceMap.put(referencedClass, holderSet);
255
		}
256
		return holderSet;
257
	}
258

    
259
	@Override
260
	public Set<CdmBase> getReferencingObjectsForDeletion(CdmBase referencedCdmBase){
261
	    if (referencedCdmBase == null){
262
	        return null;
263
	    }
264
		Set<CdmBase> result = getReferencingObjects(referencedCdmBase);
265
		Set<ReferenceHolder> holderSet = referenceMap.get(IdentifiableEntity.class);
266
		try {
267
			if (holderSet == null){
268
				holderSet = makeHolderSet(IdentifiableEntity.class);
269
				referenceMap.put(IdentifiableEntity.class, holderSet);
270
			}
271
			Set<CdmBase> resultIdentifiableEntity = new HashSet<>();
272
			for (ReferenceHolder refHolder: holderSet){
273
				handleReferenceHolder(referencedCdmBase, resultIdentifiableEntity, refHolder, false);
274
			}
275
			result.removeAll(resultIdentifiableEntity);
276

    
277
			return result;
278
		} catch (Exception e) {
279
			e.printStackTrace();
280
			throw new RuntimeException(e);
281
		}
282

    
283
	}
284

    
285
	/**
286
	 * @param referencedCdmBase
287
	 * @param result
288
	 * @param refHolder
289
	 */
290
	private void handleReferenceHolder(CdmBase referencedCdmBase,
291
			Set<CdmBase> result, ReferenceHolder refHolder, boolean limited) {
292

    
293
	    boolean isCollection = refHolder.isCollection();
294
		if (isCollection){
295
		    if (limited){
296
		        result.addAll(getCdmBasesWithItemInCollection(refHolder.itemClass, refHolder.otherClass, refHolder.propertyName, referencedCdmBase, 100));
297
		    }else{
298
		        result.addAll(getCdmBasesWithItemInCollection(refHolder.itemClass, refHolder.otherClass, refHolder.propertyName, referencedCdmBase, null));
299
		    }
300
		}else{
301
		    if (limited){
302
		        result.addAll(getCdmBasesByFieldAndClass(refHolder.otherClass, refHolder.propertyName, referencedCdmBase, 100));
303
		    }else{
304
		        result.addAll(getCdmBasesByFieldAndClass(refHolder.otherClass, refHolder.propertyName, referencedCdmBase, null));
305
		    }
306
		}
307
	}
308

    
309
	/**
310
     * @param referencedCdmBase
311
     * @param result
312
     * @param refHolder
313
     */
314
    private Long handleReferenceHolderForCount(CdmBase referencedCdmBase,
315
            Long result, ReferenceHolder refHolder) {
316
        boolean isCollection = refHolder.isCollection();
317
        if (isCollection){
318
            result += getCountWithItemInCollection(refHolder.itemClass, refHolder.otherClass, refHolder.propertyName, referencedCdmBase);
319
        }else{
320
            result += getCountByFieldAndClass(refHolder.otherClass, refHolder.propertyName, referencedCdmBase);
321
        }
322
        return result;
323
    }
324

    
325
	/**
326
	 * @param referencedClass
327
	 * @return
328
	 * @throws NoSuchFieldException
329
	 * @throws ClassNotFoundException
330
	 */
331
	protected Set<ReferenceHolder> makeHolderSet(Class<?> referencedClass) throws ClassNotFoundException, NoSuchFieldException {
332
		Set<ReferenceHolder> result = new HashSet<>();
333

    
334
		//init
335
		if (allCdmClasses == null){
336
			allCdmClasses = getAllPersistedClasses(false); //findAllCdmClasses();
337
		}
338
		SessionFactory sessionFactory = getSession().getSessionFactory();
339

    
340
		for (Class<? extends CdmBase> cdmClass : allCdmClasses){
341
			ClassMetadata classMetadata = sessionFactory.getClassMetadata(cdmClass);
342
			Type[] propertyTypes = classMetadata.getPropertyTypes();
343
			int propertyNr = 0;
344
			for (Type propertyType: propertyTypes){
345
				String propertyName = classMetadata.getPropertyNames()[propertyNr];
346
				makePropertyType(result, referencedClass, sessionFactory, cdmClass, propertyType, propertyName, false);
347
				propertyNr++;
348
			}
349
		}
350
		return result;
351
	}
352

    
353
	/**
354
	 * @param referencedCdmBase
355
	 * @param result
356
	 * @param referencedClass
357
	 * @param sessionFactory
358
	 * @param cdmClass
359
	 * @param propertyType
360
	 * @param propertyName
361
	 * @throws ClassNotFoundException
362
	 * @throws NoSuchFieldException
363
	 */
364
	private void makePropertyType(
365
//			CdmBase referencedCdmBase,
366
			Set<ReferenceHolder> result,
367
			Class<?> referencedClass,
368
			SessionFactory sessionFactory, Class<? extends CdmBase> cdmClass,
369
			Type propertyType, String propertyName, boolean isCollection)
370
				throws ClassNotFoundException, NoSuchFieldException {
371

    
372
		if (propertyType.isEntityType()){
373
			EntityType entityType = (EntityType)propertyType;
374
			String associatedEntityName = entityType.getAssociatedEntityName();
375
			Class<?> entityClass = Class.forName(associatedEntityName);
376
			if (entityClass.isInterface()){
377
			    logger.debug("There is an interface");
378
			}
379
			if (entityType instanceof OneToOneType){
380
			    OneToOneType oneToOneType = (OneToOneType)entityType;
381
			    ForeignKeyDirection direction = oneToOneType.getForeignKeyDirection();
382
			    if (direction == ForeignKeyDirection.TO_PARENT){  //this
383
			        return;
384
			    }
385
			}
386
			if (entityClass.isAssignableFrom(referencedClass)){
387
			    makeSingleProperty(referencedClass, entityClass, propertyName, cdmClass, result, isCollection);
388
			}
389
		}else if (propertyType.isCollectionType()){
390
			CollectionType collectionType = (CollectionType)propertyType;
391
			//String role = collectionType.getRole();
392
			Type elType = collectionType.getElementType((SessionFactoryImplementor)sessionFactory);
393
			makePropertyType(result, referencedClass, sessionFactory, cdmClass, elType, propertyName, true);
394
		}else if (propertyType.isAnyType()){
395
//			AnyType anyType = (AnyType)propertyType;
396
			Field field = cdmClass.getDeclaredField(propertyName);
397
			Class<?> returnType = field.getType();
398
			if (returnType.isInterface()){
399
				logger.debug("There is an interface");
400
			}
401
			if (returnType.isAssignableFrom(referencedClass)){
402
				makeSingleProperty(referencedClass, returnType, propertyName, cdmClass, result, isCollection);
403
			}
404
		}else if (propertyType.isComponentType()){
405
			ComponentType componentType = (ComponentType)propertyType;
406
			Type[] subTypes = componentType.getSubtypes();
407
//			Field field = cdmClass.getDeclaredField(propertyName);
408
//			Class returnType = field.getType();
409
			int propertyNr = 0;
410
			for (Type subType: subTypes){
411
				String subPropertyName = componentType.getPropertyNames()[propertyNr];
412
				if (!isNoDoType(subType)){
413
					logger.warn("SubType not yet handled: " + subType);
414
				}
415
//				handlePropertyType(referencedCdmBase, result, referencedClass,
416
//						sessionFactory, cdmClass, subType, subPropertyName, isCollection);
417
				propertyNr++;
418
			}
419
		}else if (isNoDoType(propertyType)){
420
			//do nothing
421
		}else{
422
			logger.warn("propertyType not yet handled: " + propertyType.getName());
423
		}
424
		//OLD:
425
				//		if (! type.isInterface()){
426
		//		if (referencedClass.isAssignableFrom(type)||
427
		//				type.isAssignableFrom(referencedClass) && CdmBase.class.isAssignableFrom(type)){
428
		//			handleSingleClass(referencedClass, type, field, cdmClass, result, referencedCdmBase, false);
429
		//		}
430
		//	//interface
431
		//	}else if (type.isAssignableFrom(referencedClass)){
432
		//			handleSingleClass(referencedClass, type, field, cdmClass, result, referencedCdmBase, false);
433

    
434
	}
435

    
436
	private boolean makeSingleProperty(Class<?> itemClass, Class<?> type, String propertyName, Class<? extends CdmBase> cdmClass, Set<ReferenceHolder> result,/*CdmBase item,*/ boolean isCollection){
437
//			String fieldName = StringUtils.rightPad(propertyName, 30);
438
//			String className = StringUtils.rightPad(cdmClass.getSimpleName(), 30);
439
//			String returnTypeName = StringUtils.rightPad(type.getSimpleName(), 30);
440

    
441
//			logger.debug(fieldName +   "\t\t" + className + "\t\t" + returnTypeName);
442
			ReferenceHolder refHolder = new ReferenceHolder();
443
			refHolder.propertyName = propertyName;
444
			refHolder.otherClass = cdmClass;
445
			refHolder.itemClass = (isCollection ? itemClass : null) ;
446
			refHolder.targetClass = type ;
447

    
448
			result.add(refHolder);
449
		return true;
450
	}
451

    
452
	/**
453
	 * @param propertyType
454
	 * @return
455
	 */
456
	protected static boolean isNoDoType(Type propertyType) {
457
		boolean result = false;
458
		Class<?>[] classes = new Class[]{
459
				PersistentDateTime.class,
460
				WSDLDefinitionUserType.class,
461
				UUIDUserType.class,
462
				PartialUserType.class,
463
				StringType.class,
464
				BooleanType.class,
465
				IntegerType.class,
466
				MaterializedClobType.class,
467
				LongType.class,
468
				FloatType.class,
469
				SerializableType.class,
470
				DoubleType.class,
471
				URIUserType.class,
472
				EnumType.class,
473
				EnumUserType.class,
474
				DOIUserType.class,
475
				OrcidUserType.class,
476
				ShiftUserType.class,
477
				EnumSetUserType.class,
478
				SeverityUserType.class,
479
				BigDecimalUserType.class,
480
				};
481
		Set<String> classNames = new HashSet<>();
482
		for (Class<?> clazz: classes){
483
			classNames.add(clazz.getCanonicalName());
484
			if (clazz == propertyType.getClass()){
485
				return true;
486
			}
487
		}
488
		String propertyTypeClassName = propertyType.getName();
489
		if (classNames.contains(propertyTypeClassName)){
490
			return true;
491
		}
492
		return result;
493
	}
494

    
495
	@Override
496
	public List<CdmBase> getHqlResult(String hqlQuery, Object[] params){
497
		Query query = getSession().createQuery(hqlQuery);
498
		for(int i = 0; i<params.length; i++){
499
		    query.setParameter(String.valueOf(i), params[i]);  //for some reason using int, not String, throws exceptions, this seems to be a hibernate bug
500
		}
501
		@SuppressWarnings("unchecked")
502
        List<CdmBase> result = query.list();
503
		return result;
504
	}
505

    
506
	@Override
507
	public Query getHqlQuery(String hqlQuery){
508
		Query query = getSession().createQuery(hqlQuery);
509
		return query;
510
	}
511

    
512
	@Override
513
	public <T extends CdmBase> void   merge(T cdmBase1, T cdmBase2, IMergeStrategy mergeStrategy) throws MergeException {
514
		SessionImpl session = (SessionImpl) getSession();
515

    
516
		DeduplicationHelper helper = new DeduplicationHelper(session, this);
517
		helper.merge(cdmBase1, cdmBase2, mergeStrategy);
518
	}
519

    
520

    
521
	@Override
522
	public <T extends CdmBase> boolean isMergeable(T cdmBase1, T cdmBase2, IMergeStrategy mergeStrategy) throws MergeException {
523
		SessionImpl session = (SessionImpl) getSession();
524
		DeduplicationHelper helper = new DeduplicationHelper(session, this);
525
		return helper.isMergeable(cdmBase1, cdmBase2, mergeStrategy);
526
	}
527

    
528

    
529
	@Override
530
	public <T extends CdmBase> T find(Class<T> clazz, int id){
531
		Session session;
532
		session =  getSession();
533
		//session = getSession().getSessionFactory().getCurrentSession();
534
		T o = session.get(clazz, id);
535
		return o;
536
	}
537

    
538
	@Override
539
	public <T extends CdmBase> T find(Class<T> clazz, int id, List<String> propertyPaths){
540
	    Session session;
541
	    session =  getSession();
542
	    T bean = session.get(clazz, id);
543
	    if(bean == null){
544
            return bean;
545
        }
546
        defaultBeanInitializer.initialize(bean, propertyPaths);
547
        return bean;
548
	}
549

    
550
	@Override
551
    public <T extends CdmBase> T find(Class<T> clazz, UUID uuid){
552
	    return find(clazz, uuid, null);
553
	}
554

    
555
    @Override
556
    public <T extends CdmBase> T find(Class<T> clazz, UUID uuid, List<String> propertyPaths){
557
        Session session = getSession();
558
        Criteria crit = session.createCriteria(type);
559
        crit.add(Restrictions.eq("uuid", uuid));
560
        crit.addOrder(Order.desc("created"));
561
        @SuppressWarnings("unchecked")
562
        List<T> results = crit.list();
563
        if (results.isEmpty()){
564
            return null;
565
        }else{
566
            if(results.size() > 1){
567
                logger.error("findByUuid() delivers more than one result for UUID: " + uuid);
568
            }
569
            T result = results.get(0);
570
            if (result == null || propertyPaths == null){
571
                return result;
572
            }else{
573
                defaultBeanInitializer.initialize(result, propertyPaths);
574
                return result;
575
            }
576
        }
577
    }
578

    
579
	@Override
580
	public <T extends IMatchable> List<T> findMatching(T objectToMatch,
581
			IMatchStrategy matchStrategy) throws MatchException {
582

    
583
		getSession().flush();
584
		try {
585
			List<T> result = new ArrayList<>();
586
			if(objectToMatch == null){
587
				return result;
588
			}
589
			if (matchStrategy == null){
590
				matchStrategy = DefaultMatchStrategy.NewInstance(objectToMatch.getClass());
591
			}
592
			result.addAll(findMatchingNullSafe(objectToMatch, matchStrategy));
593
			return result;
594
		} catch (IllegalArgumentException e) {
595
			throw new MatchException(e);
596
		} catch (IllegalAccessException e) {
597
			throw new MatchException(e);
598
		}
599
	}
600

    
601
	private <T extends IMatchable> List<T> findMatchingNullSafe(T objectToMatch,
602
	        IMatchStrategy matchStrategy) throws IllegalArgumentException, IllegalAccessException, MatchException {
603

    
604
	    List<T> result = new ArrayList<>();
605
		Session session = getSession();
606
		Class<?> matchClass = objectToMatch.getClass();
607
		ClassMetadata classMetaData = session.getSessionFactory().getClassMetadata(matchClass.getCanonicalName());
608
		Criteria criteria = session.createCriteria(matchClass);
609
		boolean noMatch = makeCriteria(objectToMatch, matchStrategy, classMetaData, criteria);
610
		if (logger.isDebugEnabled()){logger.debug(criteria);}
611
		//session.flush();
612
		if (noMatch == false){
613
			@SuppressWarnings("unchecked")
614
            List<T> matchCandidates = criteria.list();
615
			matchCandidates.remove(objectToMatch);
616
			for (T matchCandidate : matchCandidates ){
617
				if (matchStrategy.invoke(objectToMatch, matchCandidate).isSuccessful()){
618
					result.add(matchCandidate);
619
				}else{
620
					logger.info("Match candidate did not match: " + matchCandidate);
621
				}
622
			}
623
		}
624
		return result;
625
	}
626

    
627
	private <T> boolean makeCriteria(T objectToMatch,
628
			IMatchStrategy matchStrategy, ClassMetadata classMetaData,
629
			Criteria criteria) throws IllegalAccessException, MatchException {
630

    
631
	    Matching matching = matchStrategy.getMatching((IMatchable)objectToMatch);
632
		boolean noMatch = false;
633
		Map<String, List<MatchMode>> replaceMatchers = new HashMap<>();
634
		for (CacheMatcher cacheMatcher: matching.getCacheMatchers()){
635
			boolean cacheProtected = (Boolean)cacheMatcher.getProtectedField(matching).get(objectToMatch);
636
			if (cacheProtected == true){
637
				String cacheValue = (String)cacheMatcher.getField().get(objectToMatch);
638
				if (StringUtils.isBlank(cacheValue)){
639
					return true;  //no match
640
				}else{
641
					criteria.add(Restrictions.eq(cacheMatcher.getPropertyName(), cacheValue));
642
					criteria.add(Restrictions.eq(cacheMatcher.getProtectedPropertyName(), cacheProtected));
643

    
644
					List<DoubleResult<String, MatchMode>> replacementModes = cacheMatcher.getReplaceMatchModes(matching);
645
					for (DoubleResult<String, MatchMode> replacementMode: replacementModes ){
646
						String propertyName = replacementMode.getFirstResult();
647
						List<MatchMode> replaceMatcherList = replaceMatchers.get(propertyName);
648
						if (replaceMatcherList == null){
649
							replaceMatcherList = new ArrayList<>();
650
							replaceMatchers.put(propertyName, replaceMatcherList);
651
						}
652
						replaceMatcherList.add(replacementMode.getSecondResult());
653
					}
654

    
655
				}
656
			}
657
		}
658
		for (FieldMatcher fieldMatcher : matching.getFieldMatchers(false)){
659
			String propertyName = fieldMatcher.getPropertyName();
660
			Type propertyType = classMetaData.getPropertyType(propertyName);
661
			Object value = fieldMatcher.getField().get(objectToMatch);
662
			List<MatchMode> matchModes= new ArrayList<>();
663
			matchModes.add(fieldMatcher.getMatchMode());
664
			if (replaceMatchers.get(propertyName) != null){
665
				matchModes.addAll(replaceMatchers.get(propertyName));
666
			}
667

    
668
			boolean isIgnore = false;
669
			for (MatchMode matchMode : matchModes){
670
				isIgnore |= matchMode.isIgnore(value);
671
			}
672
			if (! isIgnore ){
673
				if (propertyType.isComponentType()){
674
					matchComponentType(criteria, fieldMatcher, propertyName, value, matchModes);
675
				}else{
676
					noMatch = matchNonComponentType(criteria, fieldMatcher, propertyName, value, matchModes, propertyType);
677
				}
678
			}
679
			if (noMatch){
680
				return noMatch;
681
			}
682
		}
683
		return noMatch;
684
	}
685

    
686
	private void matchComponentType(Criteria criteria,
687
			FieldMatcher fieldMatcher, String propertyName, Object value,
688
			List<MatchMode> matchModes) throws MatchException, IllegalAccessException {
689
		if (value == null){
690
			boolean requiresSecondNull = requiresSecondNull(matchModes, null);
691
			if (requiresSecondNull){
692
				criteria.add(Restrictions.isNull(propertyName));
693
			}else{
694
				//this should not happen, should be handled as ignore before
695
				logger.warn("Component type not yet implemented for (null) value: " + propertyName);
696
				throw new MatchException("Component type not yet fully implemented for (null) value. Property: " + propertyName);
697
			}
698
		}else{
699
			Class<?> componentClass = fieldMatcher.getField().getType();
700
			Map<String, Field> fields = CdmUtils.getAllFields(componentClass, Object.class, false, false, true, false);
701
			for (String fieldName : fields.keySet()){
702
				String restrictionPath = propertyName +"."+fieldName;
703
				Object componentValue = fields.get(fieldName).get(value);
704
				//TODO differentiate matchMode
705
				createCriterion(criteria, restrictionPath, componentValue, matchModes);
706
			}
707
		}
708
	}
709

    
710
    private boolean matchNonComponentType(Criteria criteria,
711
			FieldMatcher fieldMatcher,
712
			String propertyName,
713
			Object value,
714
			List<MatchMode> matchModes,
715
			Type propertyType)
716
			throws HibernateException, DataAccessException, MatchException, IllegalAccessException{
717

    
718
	    boolean noMatch = false;
719
		if (isRequired(matchModes) && value == null){
720
			noMatch = true;
721
			return noMatch;
722
		}else if (requiresSecondNull(matchModes,value)){
723
			criteria.add(Restrictions.isNull(propertyName));
724
		}else{
725
			if (isMatch(matchModes)){
726
				if (propertyType.isCollectionType()){
727
					//TODO collection not yet handled for match
728
				}else{
729
					JoinType joinType = JoinType.INNER_JOIN;
730
					if (! requiresSecondValue(matchModes,value)){
731
						joinType = JoinType.LEFT_OUTER_JOIN;
732
					}
733
					Criteria matchCriteria = criteria.createCriteria(propertyName, joinType).add(Restrictions.isNotNull("id"));
734
					@SuppressWarnings("rawtypes")
735
                    Class matchClass = value.getClass();
736
					if (IMatchable.class.isAssignableFrom(matchClass)){
737
						IMatchStrategy valueMatchStrategy = fieldMatcher.getMatchStrategy() != null? fieldMatcher.getMatchStrategy() : DefaultMatchStrategy.NewInstance(matchClass);
738
						ClassMetadata valueClassMetaData = getSession().getSessionFactory().getClassMetadata(matchClass.getCanonicalName());
739
						noMatch = makeCriteria(value, valueMatchStrategy, valueClassMetaData, matchCriteria);
740
					}else{
741
						logger.error("Class to match (" + matchClass + ") is not of type IMatchable");
742
						throw new MatchException("Class to match (" + matchClass + ") is not of type IMatchable");
743
					}
744
				}
745
			}else if (isEqual(matchModes)){
746
				createCriterion(criteria, propertyName, value, matchModes);
747
			}else {
748
				logger.warn("Unhandled match mode: " + matchModes + ", value: " + (value==null?"null":value));
749
			}
750
		}
751
		return noMatch;
752
	}
753

    
754
	private void createCriterion(Criteria criteria, String propertyName,
755
			Object value, List<MatchMode> matchModes) throws MatchException {
756
		Criterion finalRestriction = null;
757
		Criterion equalRestriction = Restrictions.eq(propertyName, value);
758
		Criterion nullRestriction = Restrictions.isNull(propertyName);
759
		if (this.requiresSecondValue(matchModes, value)){
760
			finalRestriction = equalRestriction;
761
		}else if (requiresSecondNull(matchModes, value) ){
762
			finalRestriction = nullRestriction;
763
		}else{
764
			finalRestriction = Restrictions.or(equalRestriction, nullRestriction);
765
		}
766
		//return finalRestriction;
767
		criteria.add(finalRestriction);
768
	}
769

    
770
	private boolean requiresSecondNull(List<MatchMode> matchModes, Object value) throws MatchException {
771
		boolean result = true;
772
		for (MatchMode matchMode: matchModes){
773
			result &= matchMode.requiresSecondNull(value);
774
		}
775
		return result;
776
	}
777

    
778
	private boolean requiresSecondValue(List<MatchMode> matchModes, Object value) {
779
		boolean result = true;
780
		for (MatchMode matchMode: matchModes){
781
			result &= matchMode.requiresSecondValue(value);
782
		}
783
		return result;
784
	}
785

    
786
	private boolean isRequired(List<MatchMode> matchModes) {
787
		boolean result = true;
788
		for (MatchMode matchMode: matchModes){
789
			result &= matchMode.isRequired();
790
		}
791
		return result;
792
	}
793

    
794
	/**
795
	 * Returns true if at least one match mode is of type MATCH_XXX
796
	 * @param matchModes
797
	 * @param value
798
	 * @return
799
	 * @throws MatchException
800
	 */
801
	private boolean isMatch(List<MatchMode> matchModes) throws MatchException {
802
		boolean result = false;
803
		for (MatchMode matchMode: matchModes){
804
			result |= matchMode.isMatch();
805
		}
806
		return result;
807
	}
808

    
809
	/**
810
	 * Returns true if at least one match mode is of typ EQUAL_XXX
811
	 * @param matchModes
812
	 * @param value
813
	 * @return
814
	 * @throws MatchException
815
	 */
816
	private boolean isEqual(List<MatchMode> matchModes) throws MatchException {
817
		boolean result = false;
818
		for (MatchMode matchMode: matchModes){
819
			result |= matchMode.isEqual();
820
		}
821
		return result;
822
	}
823

    
824
	@Override
825
    public void saveMetaData(CdmMetaData cdmMetaData) {
826
		getSession().saveOrUpdate(cdmMetaData);
827
	}
828

    
829
	@Override
830
    public List<CdmMetaData> getMetaData() {
831
		Session session = getSession();
832
		Criteria crit = session.createCriteria(CdmMetaData.class);
833
		@SuppressWarnings("unchecked")
834
        List<CdmMetaData> results = crit.list();
835
		return results;
836
	}
837

    
838
    @Override
839
    public Object initializeCollection(UUID ownerUuid, String fieldName, List<String> appendedPropertyPaths)  {
840
        List<String> propertyPaths = new ArrayList<>();
841
        propertyPaths.add(fieldName);
842
        if(appendedPropertyPaths != null && !appendedPropertyPaths.isEmpty()) {
843
            for(String app : appendedPropertyPaths) {
844
                propertyPaths.add(fieldName + "." + app);
845
            }
846
        }
847
        CdmBase cdmBase = load(ownerUuid, propertyPaths);
848
        Field field = ReflectionUtils.findField(cdmBase.getClass(), fieldName);
849
        field.setAccessible(true);
850
        Object obj;
851
        try {
852
            obj = field.get(cdmBase);
853
        } catch (IllegalAccessException e) {
854
            throw new IllegalArgumentException("Requested object is not accessible");
855
        }
856
        if(obj instanceof Collection || obj instanceof Map) {
857
            return obj;
858
        } else {
859
            throw new IllegalArgumentException("Field name provided does not correspond to a collection or map");
860
        }
861
    }
862

    
863
    @Override
864
    public Object initializeCollection(UUID ownerUuid, String fieldName)  {
865
        return initializeCollection(ownerUuid, fieldName, null);
866
    }
867

    
868
    @Override
869
    public boolean isEmpty(UUID ownerUuid, String fieldName) {
870
        Object col = initializeCollection(ownerUuid, fieldName);
871
        if(col instanceof Collection) {
872
            return ((Collection<?>)col).isEmpty();
873
        } else if(col instanceof Map){
874
            return ((Map<?,?>)col).isEmpty();
875
        }
876

    
877
        return false;
878
    }
879

    
880
    @Override
881
    public int size(UUID ownerUuid, String fieldName) {
882
        Object col = initializeCollection(ownerUuid, fieldName);
883
        if(col instanceof Collection) {
884
            return ((Collection<?>)col).size();
885
        } else if(col instanceof Map){
886
            return ((Map<?,?>)col).size();
887
        }
888
        return 0;
889
    }
890

    
891
    @Override
892
    public Object get(UUID ownerUuid, String fieldName, int index) {
893
        Object col = initializeCollection(ownerUuid, fieldName);
894
        if(col instanceof List) {
895
            return ((List<?>)col).get(index);
896
        } else {
897
            throw new IllegalArgumentException("Field name provided does not correspond to a list");
898
        }
899
    }
900

    
901
    @Override
902
    public boolean contains(UUID ownerUuid, String fieldName, Object element) {
903
        Object col = initializeCollection(ownerUuid, fieldName);
904
        if(col instanceof Collection) {
905
            return ((Collection<?>)col).contains(element);
906
        } else {
907
            throw new IllegalArgumentException("Field name provided does not correspond to a collection");
908
        }
909
    }
910

    
911
    @Override
912
    public boolean containsKey(UUID ownerUuid, String fieldName, Object key) {
913
        Object col = initializeCollection(ownerUuid, fieldName);
914
        if(col instanceof Map) {
915
            return ((Map<?,?>)col).containsKey(key);
916
        } else {
917
            throw new IllegalArgumentException("Field name provided does not correspond to a map");
918
        }
919
    }
920

    
921
    @Override
922
    public boolean containsValue(UUID ownerUuid, String fieldName, Object value) {
923
        Object col = initializeCollection(ownerUuid, fieldName);
924
        if(col instanceof Map) {
925
            return ((Map<?,?>)col).containsValue(value);
926
        } else {
927
            throw new IllegalArgumentException("Field name provided does not correspond to a map");
928
        }
929
    }
930

    
931
    @Override
932
	public void createFullSampleData() {
933
		FullCoverageDataGenerator dataGenerator = new FullCoverageDataGenerator();
934
		dataGenerator.fillWithData(getSession());
935
	}
936

    
937
    @Override
938
    public List<UUID> listUuid(Class<? extends CdmBase> clazz) {
939
        String queryString = "SELECT uuid FROM " + clazz.getSimpleName();
940
        Query query = getSession().createQuery(queryString);
941
        @SuppressWarnings("unchecked")
942
        List<UUID> list = query.list();
943
        return list;
944
    }
945
}
(4-4/18)