Project

General

Profile

Download (16.3 KB) Statistics
| Branch: | Tag: | Revision:
1
// $Id$
2
/**
3
* Copyright (C) 2007 EDIT
4
* European Distributed Institute of Taxonomy
5
* http://www.e-taxonomy.eu
6
*
7
* The contents of this file are subject to the Mozilla Public License Version 1.1
8
* See LICENSE.TXT at the top of this package for the full license terms.
9
*/
10

    
11
package eu.etaxonomy.cdm.strategy.match;
12

    
13
import java.lang.annotation.Annotation;
14
import java.lang.reflect.Field;
15
import java.lang.reflect.GenericDeclaration;
16
import java.lang.reflect.ParameterizedType;
17
import java.lang.reflect.Type;
18
import java.lang.reflect.TypeVariable;
19
import java.net.URI;
20
import java.util.ArrayList;
21
import java.util.Collection;
22
import java.util.HashMap;
23
import java.util.List;
24
import java.util.Map;
25
import java.util.Set;
26
import java.util.UUID;
27

    
28
import org.apache.commons.lang.StringUtils;
29
import org.apache.log4j.Logger;
30

    
31
import eu.etaxonomy.cdm.common.CdmUtils;
32
import eu.etaxonomy.cdm.common.DOI;
33
import eu.etaxonomy.cdm.common.DoubleResult;
34
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
35
import eu.etaxonomy.cdm.model.common.CdmBase;
36
import eu.etaxonomy.cdm.strategy.StrategyBase;
37
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
38

    
39
/**
40
 * @author a.mueller
41
 * @created 06.08.2009
42
 * @version 1.0
43
 */
44
public class DefaultMatchStrategy extends StrategyBase implements IMatchStrategy {
45
	private static final long serialVersionUID = 5045874493910155162L;
46
	private static final Logger logger = Logger.getLogger(DefaultMatchStrategy.class);
47

    
48
	final static UUID uuid = UUID.fromString("69467b70-07ec-43a6-b779-3ec8d013837b");
49

    
50
	public static DefaultMatchStrategy NewInstance(Class<? extends IMatchable> matchClazz){
51
		return new DefaultMatchStrategy(matchClazz);
52
	}
53

    
54
//	protected Map<String, MatchMode> matchModeMap = new HashMap<String, MatchMode>();
55
	protected MatchMode defaultMatchMode = MatchMode.EQUAL;
56
	protected MatchMode defaultCollectionMatchMode = MatchMode.IGNORE;
57
	protected MatchMode defaultMatchMatchMode = MatchMode.MATCH;
58

    
59
	protected Class<? extends IMatchable> matchClass;
60
	protected Map<String, Field> matchFields;
61
	protected Matching matching = new Matching();
62

    
63
	protected DefaultMatchStrategy(Class<? extends IMatchable> matchClazz) {
64
		super();
65
		if (matchClazz == null){
66
			throw new IllegalArgumentException("Match class must not be null");
67
		}
68
		this.matchClass = matchClazz;
69
		initMapping();
70
	}
71

    
72
	/**
73
	 * @return the merge class
74
	 */
75
	@Override
76
    public Class<? extends IMatchable> getMatchClass() {
77
		return matchClass;
78
	}
79

    
80
	/* (non-Javadoc)
81
	 * @see eu.etaxonomy.cdm.strategy.StrategyBase#getUuid()
82
	 */
83
	@Override
84
	protected UUID getUuid() {
85
		return uuid;
86
	}
87

    
88
	/* (non-Javadoc)
89
	 * @see eu.etaxonomy.cdm.strategy.match.IMatchStrategy#getMatching()
90
	 */
91
	@Override
92
    public Matching getMatching() {
93
		return matching;
94
	}
95

    
96
	/* (non-Javadoc)
97
	 * @see eu.etaxonomy.cdm.strategy.match.IMatchStrategy#setMatchMode(java.lang.String, eu.etaxonomy.cdm.strategy.match.MatchMode)
98
	 */
99
	@Override
100
    public void setMatchMode(String propertyName, MatchMode matchMode)
101
			throws MatchException {
102
		if (matchFields.containsKey(propertyName)){
103
			FieldMatcher fieldMatcher = FieldMatcher.NewInstance(matchFields.get(propertyName), matchMode);
104
			matching.setFieldMatcher(fieldMatcher);
105
		}else{
106
			throw new MatchException("The class " + matchClass.getName() + " does not contain a field named " + propertyName);
107
		}
108
	}
109

    
110
	/* (non-Javadoc)
111
	 * @see eu.etaxonomy.cdm.strategy.match.IMatchStrategy#getMatchMode(java.lang.String)
112
	 */
113
	@Override
114
    public MatchMode getMatchMode(String propertyName) {
115
		FieldMatcher fieldMatcher = matching.getFieldMatcher(propertyName);
116
		return fieldMatcher == null ? defaultMatchMode : fieldMatcher.getMatchMode();
117
	}
118

    
119
	/* (non-Javadoc)
120
	 * @see eu.etaxonomy.cdm.strategy.match.IMatchStrategy#invoke(eu.etaxonomy.cdm.strategy.match.IMatchable, eu.etaxonomy.cdm.strategy.match.IMatchable)
121
	 */
122
	@Override
123
    public <T extends IMatchable> boolean invoke(T matchFirst, T matchSecond)
124
			throws MatchException {
125
		boolean result = true;
126
		if (matchFirst == null || matchSecond == null){
127
			return false;
128
		}else if (matchFirst == matchSecond){
129
			return true;
130
		}else if (matchFirst.getClass() != matchSecond.getClass()){
131
			matchFirst = HibernateProxyHelper.deproxy(matchFirst);
132
			matchSecond = HibernateProxyHelper.deproxy(matchSecond);
133
			if (matchFirst.getClass() != matchSecond.getClass()){
134
				return false;
135
			}
136
		}
137
		matching.deleteTemporaryMatchers(); //just in case they are not yet deleted during last invoke
138
		if (! matchClass.isAssignableFrom(matchFirst.getClass()) ){
139
			throw new MatchException("Match object are of different type than the match class (" + matchClass + ") this match strategy was created with");
140
		}else if (matchClass != matchFirst.getClass()){
141
			initializeSubclass(matchFirst.getClass());
142
		}
143
		try {
144
			result = invokeChecked(matchFirst, matchSecond, result);
145
		}catch (MatchException e) {
146
			throw e;
147
		}finally{
148
			matching.deleteTemporaryMatchers();
149
		}
150
		return result;
151
	}
152

    
153
	/**
154
	 * @param class1
155
	 */
156
	private void initializeSubclass(Class<? extends IMatchable> instanceClass) {
157
		Map<String, Field> subClassFields = CdmUtils.getAllFields(instanceClass, matchClass, false, false, true, false);
158
		for (Field field: subClassFields.values()){
159
			initField(field, true);
160
		}
161
	}
162

    
163
	/**
164
	 * @param <T>
165
	 * @param matchFirst
166
	 * @param matchSecond
167
	 * @param result
168
	 * @return
169
	 * @throws MatchException
170
	 */
171
	private <T extends IMatchable> boolean invokeChecked(T matchFirst, T matchSecond,
172
			boolean result) throws MatchException {
173
		//matchFirst != matchSecond != null
174
		try {
175
			Map<String, List<MatchMode>> replaceMatchers = new HashMap<String, List<MatchMode>>();
176
			for (CacheMatcher cacheMatcher: matching.getCacheMatchers()){
177
				Field protectedField = cacheMatcher.getProtectedField(matching);
178
				boolean protected1 = protectedField.getBoolean(matchFirst);
179
				boolean protected2 = protectedField.getBoolean(matchFirst);
180
				if (protected1 != protected2){
181
					return false;
182
				}else if (protected1 == false){
183
					//ignore
184
				}else{
185
					String cache1 = (String)cacheMatcher.getField().get(matchFirst);
186
					String cache2 = (String)cacheMatcher.getField().get(matchSecond);
187
					result = cacheMatcher.getMatchMode().matches(cache1, cache2, null);
188
					if (result == false){
189
						return false;
190
					}
191
					List<DoubleResult<String, MatchMode>> replacementModes = cacheMatcher.getReplaceMatchModes(matching);
192
					for (DoubleResult<String, MatchMode> replacementMode: replacementModes ){
193
						String propertyName = replacementMode.getFirstResult();
194
						List<MatchMode> replaceMatcherList = replaceMatchers.get(propertyName);
195
						if (replaceMatcherList == null){
196
							replaceMatcherList = new ArrayList<MatchMode>();
197
							replaceMatchers.put(propertyName, replaceMatcherList);
198
						}
199
						replaceMatcherList.add(replacementMode.getSecondResult());
200
					}
201
				}
202
			}
203
			for (FieldMatcher fieldMatcher : matching.getFieldMatchers(true)){
204
				Field field = fieldMatcher.getField();
205
				List<MatchMode> replaceModeList = replaceMatchers.get(fieldMatcher.getPropertyName());
206
				if (replaceModeList == null){
207
					replaceModeList = new ArrayList<MatchMode>();
208
				}
209
				Class<?> fieldType = field.getType();
210
				logger.debug(field.getName() + ": ");
211
				if (isPrimitive(fieldType)){
212
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
213
				}else if (fieldType == String.class ){
214
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
215
				}else if (fieldType == Integer.class ){
216
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
217
				}else if(isUserType(fieldType)){
218
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
219
				}else if(fieldType == UUID.class){
220
					//result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
221
				}else if(fieldType == URI.class){
222
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
223
				}else if(fieldType == DOI.class){
224
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
225
				}else if(isSingleCdmBaseObject(fieldType)){
226
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
227
				}else if (isCollection(fieldType)){
228
					result &= matchCollectionField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
229
				}else if(fieldType.isInterface()){
230
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
231
				}else if(fieldType.isEnum()){
232
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
233
				}else{
234
					throw new RuntimeException("Unknown Object type for matching: " + fieldType);
235
				}
236
//				if (result == false){
237
//					return result;
238
//				}
239
			}
240
		} catch (Exception e) {
241
			throw new MatchException("Match Exception in invoke", e);
242
		}
243
		return result;
244
	}
245

    
246

    
247
	/**
248
	 * @throws Exception
249
	 *
250
	 */
251
	private <T extends IMatchable> boolean matchPrimitiveField(T matchFirst, T matchSecond, FieldMatcher fieldMatcher, List<MatchMode> replaceModeList) throws Exception {
252
		Field field = fieldMatcher.getField();
253
		Object value1 = checkEmpty(field.get(matchFirst));
254
		Object value2 = checkEmpty(field.get(matchSecond));
255
		IMatchStrategy matchStrategy = fieldMatcher.getMatchStrategy();
256
		boolean result = fieldMatcher.getMatchMode().matches(value1, value2, matchStrategy);
257
		for (MatchMode replaceMode : replaceModeList){
258
			if (result == true){
259
				break;
260
			}
261
			result |= replaceMode.matches(value1, value2, null);
262
		}
263
		logger.debug(fieldMatcher.getMatchMode() + ", " + field.getType().getName()+ ": " + result);
264
		return result;
265
	}
266

    
267
	/**
268
	 * @throws Exception
269
	 *
270
	 */
271
	private <T extends IMatchable> boolean matchCollectionField(T matchFirst, T matchSecond, FieldMatcher fieldMatcher, List<MatchMode> replaceModeList) throws Exception {
272
		boolean result;
273
		Field field = fieldMatcher.getField();
274
		Collection<?> value1 = (Collection)field.get(matchFirst);
275
		Collection<?> value2 = (Collection)field.get(matchSecond);
276
		MatchMode matchMode = fieldMatcher.getMatchMode();
277
		Class<?> fieldType = fieldMatcher.getField().getType();
278
		IMatchStrategy matchStrategy = fieldMatcher.getMatchStrategy();
279
		if (matchMode.isMatch()){
280
			Class collectionType = getTypeOfSet(field);
281
			if (! IMatchable.class.isAssignableFrom(collectionType)){
282
				//TODO
283
				return fieldMatcher.getMatchMode().matches(value1, value2, matchStrategy);
284
			}
285
			if (matchStrategy == null){
286
				matchStrategy = DefaultMatchStrategy.NewInstance(collectionType);
287
			}
288
			if (Set.class.isAssignableFrom(fieldType)){
289
				result = matchSet(value1, value2, matchStrategy);
290
			}else if (List.class.isAssignableFrom(fieldType)){
291
				result = matchList(value1, value2, matchStrategy);
292
			}else{
293
				throw new MatchException("Collection type not yet supported: " + fieldType);
294
			}
295
		}else{
296
			result = fieldMatcher.getMatchMode().matches(value1, value2, matchStrategy);
297
		}
298
		//cache replace modes
299
		for (MatchMode replaceMode : replaceModeList){
300
			if (result == true){
301
				break;
302
			}
303
			result |= replaceMode.matches(value1, value2, null);
304
		}
305
		logger.debug(fieldMatcher.getMatchMode() + ", " + field.getType().getName()+ ": " + result);
306
		return result;
307
	}
308

    
309

    
310
	/**
311
	 * @param object
312
	 * @return
313
	 */
314
	private Object checkEmpty(Object object) {
315
		if (object instanceof String){
316
			if (StringUtils.isBlank((String)object)){
317
				return null;
318
			}
319
		}
320
		return HibernateProxyHelper.deproxy(object);
321
	}
322

    
323
	/**
324
	 * @param value1
325
	 * @param matchStrategy
326
	 * @return
327
	 * @throws MatchException
328
	 */
329
	private boolean matchSet(Collection value1, Collection value2, IMatchStrategy matchStrategy)
330
			throws MatchException {
331
		boolean result;
332
		Set<IMatchable> set1 = (Set<IMatchable>)value1;
333
		Set<IMatchable> set2 = (Set<IMatchable>)value2;
334
		if (set1.size()!= set2.size()){
335
			return false;
336
		}
337
		result = true;
338
		for (IMatchable setItem1: set1){
339
			boolean matches = false;
340
			for (IMatchable setItem2: set2){
341
				matches |= matchStrategy.invoke(setItem1, setItem2);
342
			}
343
			if (matches == false){
344
				return false;
345
			}
346
		}
347
		return result;
348
	}
349

    
350
	/**
351
	 * @param value1
352
	 * @param matchStrategy
353
	 * @return
354
	 * @throws MatchException
355
	 */
356
	private boolean matchList(Collection value1, Collection value2, IMatchStrategy matchStrategy)
357
			throws MatchException {
358
		boolean result;
359
		List<IMatchable> list1 = (List<IMatchable>)value1;
360
		List<IMatchable> list2 = (List<IMatchable>)value2;
361
		if(list1 == null && list2 == null) {
362
			return true;
363
		}
364

    
365
		if ((list1 != null && list2 == null) || (list1 == null && list2 != null) || (list1.size()!= list2.size())){
366
			return false;
367
		}
368

    
369
		result = true;
370
		for (int i = 0; i < list1.size(); i++){
371
			IMatchable listObject1 = list1.get(i);
372
			IMatchable listObject2 = list2.get(i);
373
			if (! matchStrategy.invoke(listObject1, listObject2)){
374
				return false;
375
			}
376
		}
377
		return result;
378
	}
379

    
380
	private Class<?> getTypeOfSet(Field field) throws MatchException{
381
		Type genericType = field.getGenericType();
382
		if (genericType instanceof ParameterizedType/*Impl*/){
383
			ParameterizedType paraType = (ParameterizedType)genericType;
384
			paraType.getRawType();
385
			Type[] arguments = paraType.getActualTypeArguments();
386
			if (arguments.length == 1){
387
				Class<?> collectionClass;
388
				try {
389
					if (arguments[0] instanceof Class){
390
						return (Class)arguments[0];
391
					}else if(arguments[0] instanceof TypeVariable/*Impl*/){
392
						TypeVariable typeVariable = (TypeVariable)arguments[0];
393
						GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
394
						return (Class)genericDeclaration;
395
					}else{
396
						logger.warn("Unknown Type");
397
						throw new MatchException("");
398
					}
399
				} catch (Exception e) {
400
					logger.warn(e.getMessage());
401
					throw new MatchException("");
402
				}
403
			}else{
404
				logger.warn("Length of arguments <> 1");
405
				throw new MatchException("");
406
			}
407
		}else{
408
			logger.warn("Not a generic type of type ParameterizedTypeImpl");
409
			throw new MatchException("Collection type could not be determined. Generic type is not of type ParamterizedTypeImpl");
410
		}
411
	}
412

    
413

    
414
	/**
415
	 *
416
	 */
417
	private void initMapping() {
418
		boolean includeStatic = false;
419
		boolean includeTransient = false;
420
		boolean makeAccessible = true;
421
		matchFields = CdmUtils.getAllFields(matchClass, CdmBase.class, includeStatic, includeTransient, makeAccessible, true);
422
		for (Field field: matchFields.values()){
423
			initField(field, false);
424
		}
425
	}
426

    
427
	/**
428
	 * Initializes the matching for a single field
429
	 * @param field
430
	 */
431
	private void initField(Field field, boolean temporary) {
432
		MatchMode matchMode = null;
433
		IMatchStrategy matchStrategy = null;
434
		for (Annotation annotation : field.getAnnotations()){
435
			if (annotation.annotationType() == Match.class){
436
				Match match = ((Match)annotation);
437
				matchMode = match.value();
438
				if (matchMode == MatchMode.CACHE){
439
					ReplaceMode replaceMode = match.cacheReplaceMode();
440
					String[] cachePropertyReplaces = match.cacheReplacedProperties();
441
					MatchMode replaceMatchMode = match.replaceMatchMode();
442
					matching.addCacheMatcher(CacheMatcher.NewInstance(field, replaceMode, cachePropertyReplaces, replaceMatchMode));
443

    
444
				}else{
445
					matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode), temporary);
446
				}
447
			}
448
		}
449
		Class fieldType = field.getType();
450
		if (matchMode == null){
451
			if (isCollection(fieldType)){
452
				matchMode = defaultCollectionMatchMode;
453
				matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode), temporary);
454
			}else if (fieldType.isInterface()){
455
				//TODO could be handled more sophisticated
456
				matchMode = defaultMatchMatchMode;
457
				matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode, matchStrategy), temporary);
458
			}else if (isSingleCdmBaseObject(fieldType)){
459
				if (IMatchable.class.isAssignableFrom(fieldType)){
460
					matchMode = defaultMatchMatchMode;
461
					if (matchStrategy == null){
462
						if (fieldType == this.matchClass){
463
							matchStrategy = this;
464
						}else{
465
							matchStrategy = DefaultMatchStrategy.NewInstance(fieldType);
466
						}
467
					}
468
					matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode, matchStrategy), temporary);
469
				}else{
470
					matchMode = defaultMatchMode;
471
					matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode), temporary);
472
				}
473
			}else{
474
				matchMode = defaultMatchMode;
475
				matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode), temporary);
476
			}
477
		}
478
	}
479

    
480

    
481
	@Override
482
	public Set<String> getMatchFieldPropertyNames() {
483
		return matchFields.keySet();
484
	}
485

    
486

    
487
}
(2-2/11)