Project

General

Profile

Download (16.7 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
	public Class<? extends IMatchable> getMatchClass() {
76
		return matchClass;
77
	}
78

    
79
	/* (non-Javadoc)
80
	 * @see eu.etaxonomy.cdm.strategy.StrategyBase#getUuid()
81
	 */
82
	@Override
83
	protected UUID getUuid() {
84
		return uuid;
85
	}
86
	
87
	/* (non-Javadoc)
88
	 * @see eu.etaxonomy.cdm.strategy.match.IMatchStrategy#getMatching()
89
	 */
90
	public Matching getMatching() {
91
		return matching;
92
	}
93

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

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

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

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

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

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

    
304

    
305
	/**
306
	 * @param object
307
	 * @return
308
	 */
309
	private Object checkEmpty(Object object) {
310
		if (object instanceof String){
311
			if (StringUtils.isBlank((String)object)){
312
				return null;
313
			}
314
		}
315
		return HibernateProxyHelper.deproxy(object);
316
	}
317

    
318
	/**
319
	 * @param value1
320
	 * @param matchStrategy
321
	 * @return
322
	 * @throws MatchException
323
	 */
324
	private boolean matchSet(Collection value1, Collection value2, IMatchStrategy matchStrategy)
325
			throws MatchException {
326
		boolean result;
327
		Set<IMatchable> set1 = (Set<IMatchable>)value1;
328
		Set<IMatchable> set2 = (Set<IMatchable>)value2;
329
		if (set1.size()!= set2.size()){
330
			return false;
331
		}
332
		result = true;
333
		for (IMatchable setItem1: set1){
334
			boolean matches = false;
335
			for (IMatchable setItem2: set2){
336
				matches |= matchStrategy.invoke(setItem1, setItem2);
337
			}
338
			if (matches == false){
339
				return false;
340
			}
341
		}
342
		return result;
343
	}
344
	
345
	/**
346
	 * @param value1
347
	 * @param matchStrategy
348
	 * @return
349
	 * @throws MatchException
350
	 */
351
	private boolean matchList(Collection value1, Collection value2, IMatchStrategy matchStrategy)
352
			throws MatchException {
353
		boolean result;
354
		List<IMatchable> list1 = (List<IMatchable>)value1;
355
		List<IMatchable> list2 = (List<IMatchable>)value2;
356
		if(list1 == null && list2 == null) {
357
			return true;
358
		}
359
		
360
		if ((list1 != null && list2 == null) || (list1 == null && list2 != null) || (list1.size()!= list2.size())){
361
			return false;
362
		}
363
		
364
		result = true;
365
		for (int i = 0; i < list1.size(); i++){
366
			IMatchable listObject1 = list1.get(i);
367
			IMatchable listObject2 = list2.get(i);
368
			if (! matchStrategy.invoke(listObject1, listObject2)){
369
				return false;
370
			}
371
		}
372
		return result;
373
	}
374
	
375
	private Class<?> getTypeOfSet(Field field) throws MatchException{
376
		Type genericType = (ParameterizedType)field.getGenericType();
377
		if (genericType instanceof ParameterizedType/*Impl*/){
378
			ParameterizedType paraType = (ParameterizedType)genericType;
379
			paraType.getRawType();
380
			Type[] arguments = paraType.getActualTypeArguments();
381
			if (arguments.length == 1){
382
				Class<?> collectionClass;
383
				try {
384
					if (arguments[0] instanceof Class){
385
						return (Class)arguments[0];
386
					}else if(arguments[0] instanceof TypeVariable/*Impl*/){
387
						TypeVariable typeVariable = (TypeVariable)arguments[0];
388
						GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
389
						return (Class)genericDeclaration;
390
					}else{
391
						logger.warn("Unknown Type");
392
						throw new MatchException("");
393
					}
394
				} catch (Exception e) {
395
					logger.warn(e.getMessage());
396
					throw new MatchException("");
397
				}
398
			}else{
399
				logger.warn("Length of arguments <> 1");
400
				throw new MatchException("");
401
			}
402
		}else{
403
			logger.warn("Not a generic type of type ParameterizedTypeImpl");
404
			throw new MatchException("Collection type could not be determined. Generic type is not of type ParamterizedTypeImpl");
405
		}
406
	}
407
	
408
	
409
	/**
410
	 * 
411
	 */
412
	private void initMapping() {
413
		boolean includeStatic = false;
414
		boolean includeTransient = false;
415
		boolean makeAccessible = true;
416
		matchFields = CdmUtils.getAllFields(matchClass, CdmBase.class, includeStatic, includeTransient, makeAccessible, true);
417
		for (Field field: matchFields.values()){
418
			initField(field, false);
419
		}
420
	}
421

    
422
	/**
423
	 * Initializes the matching for a single field
424
	 * @param field
425
	 */
426
	private void initField(Field field, boolean temporary) {
427
		MatchMode matchMode = null;
428
		IMatchStrategy matchStrategy = null;
429
		for (Annotation annotation : field.getAnnotations()){
430
			if (annotation.annotationType() == Match.class){
431
				Match match = ((Match)annotation);
432
				matchMode = match.value();
433
				if (matchMode == MatchMode.CACHE){
434
					ReplaceMode replaceMode = match.cacheReplaceMode();
435
					String[] cachePropertyReplaces = match.cacheReplacedProperties();
436
					MatchMode replaceMatchMode = match.replaceMatchMode();
437
					matching.addCacheMatcher(CacheMatcher.NewInstance(field, replaceMode, cachePropertyReplaces, replaceMatchMode));
438
					
439
				}else{
440
					matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode), temporary);
441
				}
442
			}
443
		}
444
		Class fieldType = field.getType();
445
		if (matchMode == null){
446
			if (isCollection(fieldType)){
447
				matchMode = defaultCollectionMatchMode;
448
				matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode), temporary);
449
			}else if (fieldType.isInterface()){
450
				//TODO could be handled more sophisticated
451
				matchMode = defaultMatchMatchMode;
452
				matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode, matchStrategy), temporary);
453
			}else if (isSingleCdmBaseObject(fieldType)){
454
				if (IMatchable.class.isAssignableFrom(fieldType)){
455
					matchMode = defaultMatchMatchMode;
456
					if (matchStrategy == null){
457
						if (fieldType == this.matchClass){
458
							matchStrategy = this;
459
						}else{
460
							matchStrategy = DefaultMatchStrategy.NewInstance(fieldType);
461
						}
462
					}
463
					matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode, matchStrategy), temporary);
464
				}else{
465
					matchMode = defaultMatchMode;
466
					matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode), temporary);
467
				}
468
			}else{
469
				matchMode = defaultMatchMode;
470
				matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode), temporary);
471
			}	
472
		}
473
	}
474

    
475

    
476
	@Override
477
	public Set<String> getMatchFieldPropertyNames() {
478
		return matchFields.keySet();
479
	}
480

    
481
	
482
}
(2-2/11)