Project

General

Profile

« Previous | Next » 

Revision 4e201def

Added by Andreas Müller about 8 years ago

Fix deduplication problem with list order #5652

View differences:

cdmlib-model/src/main/java/eu/etaxonomy/cdm/strategy/merge/DefaultMergeStrategy.java
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.merge;
12

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

  
29
import org.apache.log4j.Logger;
30

  
31
import eu.etaxonomy.cdm.common.CdmUtils;
32
import eu.etaxonomy.cdm.model.common.CdmBase;
33
import eu.etaxonomy.cdm.model.common.ICdmBase;
34
import eu.etaxonomy.cdm.model.common.IRelated;
35
import eu.etaxonomy.cdm.model.common.RelationshipBase;
36
import eu.etaxonomy.cdm.strategy.StrategyBase;
37

  
38
/**
39
 * @author a.mueller
40
 * @created 31.07.2009
41
 */
42
public class DefaultMergeStrategy extends StrategyBase implements IMergeStrategy  {
43
	private static final long serialVersionUID = -8513956338156791995L;
44
	private static final Logger logger = Logger.getLogger(DefaultMergeStrategy.class);
45
	final static UUID uuid = UUID.fromString("d85cd6c3-0147-452c-8fed-bbfb82f392f6");
46

  
47
	public static DefaultMergeStrategy NewInstance(Class<? extends CdmBase> mergeClazz){
48
		return new DefaultMergeStrategy(mergeClazz);
49
	}
50

  
51
	private boolean onlyReallocateReferences = false;
52

  
53
	protected MergeMode defaultMergeMode = MergeMode.FIRST;
54
	protected MergeMode defaultCollectionMergeMode = MergeMode.ADD;
55

  
56
	protected Map<String, MergeMode> mergeModeMap = new HashMap<String, MergeMode>();
57
	protected Class<? extends CdmBase> mergeClass;
58
	protected Map<String, Field> mergeFields;
59

  
60
	protected DefaultMergeStrategy(Class<? extends CdmBase> mergeClazz) {
61
		super();
62
		if (mergeClazz == null){
63
			throw new IllegalArgumentException("Merge class must not be null");
64
		}
65
		this.mergeClass = mergeClazz;
66
		boolean includeStatic = false;
67
		boolean includeTransient = false;
68
		boolean makeAccessible = true;
69
		this.mergeFields = CdmUtils.getAllFields(mergeClass, CdmBase.class, includeStatic, includeTransient, makeAccessible, true);
70
		initMergeModeMap();
71
	}
72

  
73

  
74
	@Override
75
	public boolean isOnlyReallocateReferences() {
76
		return this.onlyReallocateReferences;
77
	}
78

  
79
//	@Override
80
//	public void setOnlyReallocateLinks(boolean onlyReallocateReferences) {
81
//		this.onlyReallocateReferences = onlyReallocateReferences;
82
//	}
83

  
84

  
85
	/**
86
	 *
87
	 */
88
	private void initMergeModeMap() {
89
		for (Field field: mergeFields.values()){
90
			for (Annotation annotation : field.getAnnotations()){
91
				if (annotation.annotationType() == Merge.class){
92
					MergeMode mergeMode = ((Merge)annotation).value();
93
					mergeModeMap.put(field.getName(), mergeMode);
94
				}
95
			}
96
		}
97
	}
98

  
99
	@Override
100
	protected UUID getUuid() {
101
		return uuid;
102
	}
103

  
104
	/**
105
	 * @return the merge class
106
	 */
107
	public Class<? extends CdmBase> getMergeClass() {
108
		return mergeClass;
109
	}
110

  
111

  
112

  
113
	/**
114
	 * @param mergeClazz the mergeClazz to set
115
	 */
116
	public void setMergeClazz(Class<? extends CdmBase> mergeClazz) {
117
		this.mergeClass = mergeClazz;
118
	}
119

  
120

  
121
	@Override
122
	public MergeMode getMergeMode(String propertyName){
123
		MergeMode result = mergeModeMap.get(propertyName);
124
		if (result == null){
125
			Field field = mergeFields.get(propertyName);
126
			if (isCollection(field.getType())){
127
				return defaultCollectionMergeMode;
128
			}else{
129
				return defaultMergeMode;
130
			}
131
		}else{
132
			return result;
133
		}
134
	}
135

  
136
	@Override
137
	public void setMergeMode(String propertyName, MergeMode mergeMode) throws MergeException{
138
		if (mergeFields.containsKey(propertyName)){
139
			checkIdentifier(propertyName, mergeMode);
140
			mergeModeMap.put(propertyName, mergeMode);
141
		}else{
142
			throw new MergeException("The class " + mergeClass.getName() + " does not contain a field with name " + propertyName);
143
		}
144
	}
145

  
146

  
147
	@Override
148
	public void setDefaultMergeMode(MergeMode defaultMergeMode) {
149
		this.defaultMergeMode = defaultMergeMode;
150
	}
151

  
152

  
153
	@Override
154
	public void setDefaultCollectionMergeMode(MergeMode defaultCollectionMergeMode) {
155
		this.defaultCollectionMergeMode = defaultCollectionMergeMode;
156
	}
157

  
158

  
159
	/**
160
	 * Tests if a property is an identifier property
161
	 * @param propertyName
162
	 * @param mergeMode
163
	 * @throws MergeException
164
	 */
165
	private void checkIdentifier(String propertyName, MergeMode mergeMode) throws MergeException {
166
		if (mergeMode != MergeMode.FIRST){
167
			if ("id".equalsIgnoreCase(propertyName) || "uuid".equalsIgnoreCase(propertyName)){
168
				throw new MergeException("Identifier must always have merge mode MergeMode.FIRST");
169
			}
170
		}
171
	}
172

  
173
	@Override
174
	public <T extends IMergable> Set<ICdmBase> invoke(T mergeFirst, T mergeSecond) throws MergeException {
175
		return this.invoke(mergeFirst, mergeSecond, null);
176
	}
177

  
178
	@Override
179
	public <T extends IMergable> Set<ICdmBase> invoke(T mergeFirst, T mergeSecond, Set<ICdmBase> clonedObjects) throws MergeException {
180
		Set<ICdmBase> deleteSet = new HashSet<ICdmBase>();
181
		if (clonedObjects == null){
182
			clonedObjects = new HashSet<ICdmBase>();
183
		}
184
		deleteSet.add(mergeSecond);
185
		try {
186
 			for (Field field : mergeFields.values()){
187
				Class<?> fieldType = field.getType();
188
				if (isIdentifier(field)){
189
					//do nothing (id and uuid stay with first object)
190
				}else if (isPrimitive(fieldType)){
191
					mergePrimitiveField(mergeFirst, mergeSecond, field);
192
				}else if (fieldType == String.class ){
193
					mergeStringField(mergeFirst, mergeSecond, field);
194
				}else if (isCollection(fieldType)){
195
					mergeCollectionField(mergeFirst, mergeSecond, field, deleteSet, clonedObjects);
196
				}else if(isUserType(fieldType)){
197
					mergeUserTypeField(mergeFirst, mergeSecond, field);
198
				}else if(isSingleCdmBaseObject(fieldType)){
199
					mergeSingleCdmBaseField(mergeFirst, mergeSecond, field, deleteSet);
200
				}else if(fieldType.isInterface()){
201
					mergeInterfaceField(mergeFirst, mergeSecond, field, deleteSet);
202
				}else if(fieldType.isEnum()){
203
					mergeEnumField(mergeFirst, mergeSecond, field, deleteSet);
204
				}else{
205
					throw new RuntimeException("Unknown Object type for merging: " + fieldType);
206
				}
207
			}
208
 			return deleteSet;
209
		} catch (Exception e) {
210
			throw new MergeException("Merge Exception in invoke", e);
211
		}
212
	}
213

  
214

  
215
	/**
216
	 * @throws Exception
217
	 *
218
	 */
219
	private <T extends IMergable> void mergeInterfaceField(T mergeFirst, T mergeSecond, Field field, Set<ICdmBase> deleteSet) throws Exception {
220
		String propertyName = field.getName();
221
		MergeMode mergeMode =  this.getMergeMode(propertyName);
222
		if (mergeMode != MergeMode.FIRST){
223
			mergeCdmBaseValue(mergeFirst, mergeSecond, field, deleteSet);
224
		}
225
		logger.debug(propertyName + ": " + mergeMode + ", " + field.getType().getName());
226

  
227
	}
228

  
229
	/**
230
	 * @throws Exception
231
	 *
232
	 */
233
	private <T extends IMergable> void mergeEnumField(T mergeFirst, T mergeSecond, Field field, Set<ICdmBase> deleteSet) throws Exception {
234
		String propertyName = field.getName();
235
		MergeMode mergeMode =  this.getMergeMode(propertyName);
236
		if (mergeMode != MergeMode.FIRST){
237
			mergeCdmBaseValue(mergeFirst, mergeSecond, field, deleteSet);
238
		}
239
		logger.debug(propertyName + ": " + mergeMode + ", " + field.getType().getName());
240

  
241
	}
242

  
243
	/**
244
	 * @throws Exception
245
	 *
246
	 */
247
	private <T extends IMergable> void mergeSingleCdmBaseField(T mergeFirst, T mergeSecond, Field field, Set<ICdmBase> deleteSet) throws Exception {
248
		String propertyName = field.getName();
249
		MergeMode mergeMode =  this.getMergeMode(propertyName);
250
		if (mergeMode != MergeMode.FIRST){
251
			mergeCdmBaseValue(mergeFirst, mergeSecond, field, deleteSet);
252
		}
253
		logger.debug(propertyName + ": " + mergeMode + ", " + field.getType().getName());
254

  
255
	}
256

  
257
	private <T extends IMergable> void mergeCdmBaseValue(T mergeFirst, T mergeSecond, Field field, Set<ICdmBase> deleteSet) throws Exception {
258
		if (true){
259
			Object value = getMergeValue(mergeFirst, mergeSecond, field);
260
			if (value instanceof ICdmBase || value == null){
261
				field.set(mergeFirst, value);
262
			}else{
263
				throw new MergeException("Merged value must be of type CdmBase but is not: " + value.getClass());
264
			}
265
		}else{
266
			throw new MergeException("Not supported mode");
267
		}
268
	}
269

  
270
	/**
271
	 * @throws Exception
272
	 *
273
	 */
274
	private <T extends IMergable> void mergeUserTypeField(T mergeFirst, T mergeSecond, Field field) throws Exception {
275
		String propertyName = field.getName();
276
		Class<?> fieldType = field.getType();
277
		MergeMode mergeMode =  this.getMergeMode(propertyName);
278
		if (mergeMode == MergeMode.MERGE){
279
			Method mergeMethod = getMergeMethod(fieldType);
280
			Object firstObject = field.get(mergeFirst);
281
			if (firstObject == null){
282
				firstObject = fieldType.newInstance();
283
			}
284
			Object secondObject = field.get(mergeSecond);
285
			mergeMethod.invoke(firstObject, secondObject);
286
		}else if (mergeMode != MergeMode.FIRST){
287
			Object value = getMergeValue(mergeFirst, mergeSecond, field);
288
			field.set(mergeFirst, value);
289
		}
290
		logger.debug(propertyName + ": " + mergeMode + ", " + fieldType.getName());
291
	}
292

  
293
	/**
294
	 * @return
295
	 * @throws NoSuchMethodException
296
	 * @throws SecurityException
297
	 */
298
	private Method getMergeMethod(Class<?> fieldType) throws SecurityException, NoSuchMethodException {
299
		Method mergeMethod = fieldType.getDeclaredMethod("merge", fieldType);
300
		return mergeMethod;
301
	}
302

  
303

  
304

  
305
	/**
306
	 * @throws Exception
307
	 *
308
	 */
309
	private <T extends IMergable> void mergeCollectionField(T mergeFirst, T mergeSecond, Field field, Set<ICdmBase> deleteSet, Set<ICdmBase> clonedObjects) throws Exception {
310
		String propertyName = field.getName();
311
		Class<?> fieldType = field.getType();
312
		MergeMode mergeMode =  this.getMergeMode(propertyName);
313
		if (mergeMode != MergeMode.FIRST){
314
			mergeCollectionFieldNoFirst(mergeFirst, mergeSecond, field, mergeMode, deleteSet, clonedObjects);
315
		}
316
		logger.debug(propertyName + ": " + mergeMode + ", " + fieldType.getName());
317
	}
318

  
319
	protected <T extends IMergable> void mergeCollectionFieldNoFirst(T mergeFirst, T mergeSecond, Field field, MergeMode mergeMode, Set<ICdmBase> deleteSet, Set<ICdmBase> clonedObjects) throws Exception{
320
		Class<?> fieldType = field.getType();
321
		if (mergeMode == MergeMode.ADD || mergeMode == MergeMode.ADD_CLONE){
322
			//FIXME
323
			Method addMethod = getAddMethod(field);
324
			Method removeMethod = getAddMethod(field, true);
325

  
326
			if (Set.class.isAssignableFrom(fieldType) || List.class.isAssignableFrom(fieldType)){
327
				Collection<ICdmBase> secondCollection = (Collection<ICdmBase>)field.get(mergeSecond);
328
				List<ICdmBase> removeList = new ArrayList<ICdmBase>();
329
				if(secondCollection != null) {
330
				for (ICdmBase obj : secondCollection){
331
					Object objectToAdd;
332
					if (mergeMode == MergeMode.ADD){
333
						objectToAdd = obj;
334
					}else if(mergeMode == MergeMode.ADD_CLONE){
335
						Method cloneMethod = obj.getClass().getDeclaredMethod("clone");
336
						objectToAdd = cloneMethod.invoke(obj);
337
						clonedObjects.add(obj);
338
					}else{
339
						throw new MergeException("Unknown collection merge mode: " + mergeMode);
340
					}
341
					addMethod.invoke(mergeFirst, objectToAdd);
342
					removeList.add(obj);
343
				}
344
				}
345
				for (ICdmBase removeObj : removeList ){
346
					//removeMethod.invoke(mergeSecond, removeObj);
347
					if ((removeObj instanceof CdmBase)&& mergeMode == MergeMode.ADD_CLONE) {
348
						deleteSet.add(removeObj);
349
					}
350
				}
351
			}else{
352
				throw new MergeException("Merge for collections other than sets and lists not yet implemented");
353
			}
354
		}else if (mergeMode == MergeMode.RELATION){
355
			if (Set.class.isAssignableFrom(fieldType) || List.class.isAssignableFrom(fieldType)){
356
				Collection<RelationshipBase<?,?,?>> secondCollection = (Collection<RelationshipBase<?,?,?>>)field.get(mergeSecond);
357
				List<ICdmBase> removeList = new ArrayList<ICdmBase>();
358
				for (RelationshipBase<?,?,?> relation : secondCollection){
359
					Method relatedFromMethod = RelationshipBase.class.getDeclaredMethod("getRelatedFrom");
360
					relatedFromMethod.setAccessible(true);
361
					Object relatedFrom = relatedFromMethod.invoke(relation);
362

  
363
					Method relatedToMethod = RelationshipBase.class.getDeclaredMethod("getRelatedTo");
364
					relatedToMethod.setAccessible(true);
365
					Object relatedTo = relatedToMethod.invoke(relation);
366

  
367
					if (relatedFrom.equals(mergeSecond)){
368
						Method setRelatedMethod = RelationshipBase.class.getDeclaredMethod("setRelatedFrom", IRelated.class);
369
						setRelatedMethod.setAccessible(true);
370
						setRelatedMethod.invoke(relation, mergeFirst);
371
					}
372
					if (relatedTo.equals(mergeSecond)){
373
						Method setRelatedMethod = RelationshipBase.class.getDeclaredMethod("setRelatedTo", IRelated.class);
374
						setRelatedMethod.setAccessible(true);
375
						setRelatedMethod.invoke(relation, mergeFirst);
376
					}
377
					((IRelated)mergeFirst).addRelationship(relation);
378
					removeList.add(relation);
379
				}
380
				for (ICdmBase removeObj : removeList){
381
					//removeMethod.invoke(mergeSecond, removeObj);
382
					if (removeObj instanceof CdmBase){
383
						deleteSet.add(removeObj);
384
					}
385
				}
386
			}else{
387
				throw new MergeException("Merge for collections other than sets and lists not yet implemented");
388
			}
389
		}else{
390
			throw new MergeException("Other merge modes for collections not yet implemented");
391
		}
392
	}
393

  
394

  
395
	private Method getAddMethod(Field field) throws MergeException{
396
		return getAddMethod(field, false);
397
	}
398

  
399
	public static Method getAddMethod(Field field, boolean remove) throws MergeException{
400
		Method result;
401
		Class<?> parameterClass = getCollectionType(field);
402
		String fieldName = field.getName();
403
		String firstCapital = fieldName.substring(0, 1).toUpperCase();
404
		String rest = fieldName.substring(1);
405
		String prefix = remove? "remove": "add";
406
		String methodName = prefix + firstCapital + rest;
407
		boolean endsWithS = parameterClass.getSimpleName().endsWith("s");
408
		if (! endsWithS && ! fieldName.equals("media")){
409
			methodName = methodName.substring(0, methodName.length() -1); //remove 's' at end
410
		}
411
		Class<?> methodClass = field.getDeclaringClass();
412
		try {
413
			result = methodClass.getMethod(methodName, parameterClass);
414
		}catch (NoSuchMethodException e1) {
415
			try {
416
				result = methodClass.getDeclaredMethod(methodName, parameterClass);
417
				result.setAccessible(true);
418
			} catch (NoSuchMethodException e) {
419
				logger.warn(methodName);
420
				throw new IllegalArgumentException("Default adding method for collection field ("+field.getName()+") does not exist");
421
			}
422
		} catch (SecurityException e) {
423
			throw e;
424
		}
425
		return result;
426
	}
427

  
428
	/**
429
	 * @throws Exception
430
	 *
431
	 */
432
	private <T extends IMergable> void mergePrimitiveField(T mergeFirst, T mergeSecond, Field field) throws Exception {
433
		String propertyName = field.getName();
434
		Class<?> fieldType = field.getType();
435
		MergeMode mergeMode =  this.getMergeMode(propertyName);
436
		if (mergeMode != MergeMode.FIRST){
437
			Object value = getMergeValue(mergeFirst, mergeSecond, field);
438
			field.set(mergeFirst, value);
439
		}
440
		logger.debug(propertyName + ": " + mergeMode + ", " + fieldType.getName());
441

  
442
	}
443

  
444
	/**
445
	 * @throws Exception
446
	 *
447
	 */
448
	private <T extends IMergable> void mergeStringField(T mergeFirst, T mergeSecond, Field field) throws Exception {
449
		String propertyName = field.getName();
450
		Class<?> fieldType = field.getType();
451
		MergeMode mergeMode =  this.getMergeMode(propertyName);
452
		if (mergeMode != MergeMode.FIRST){
453
			Object value = getMergeValue(mergeFirst, mergeSecond, field);
454
			field.set(mergeFirst, value);
455
		}
456
		logger.debug(propertyName + ": " + mergeMode + ", " + fieldType.getName());
457

  
458
	}
459

  
460
	/**
461
	 * @param fieldType
462
	 * @return
463
	 */
464
	private boolean isIdentifier(Field field) {
465
		Class<?> fieldType = field.getType();
466
		if ("id".equals(field.getName()) && fieldType == int.class ){
467
			return true;
468
		}else if ("uuid".equals(field.getName()) && fieldType == UUID.class ){
469
			return true;
470
		}else{
471
			return false;
472
		}
473
	}
474

  
475

  
476
	/**
477
	 * @param cdmBase
478
	 * @param toMerge
479
	 * @param field
480
	 * @param mergeMode
481
	 * @throws Exception
482
	 */
483
	protected <T extends IMergable> Object getMergeValue(T mergeFirst, T mergeSecond,
484
			Field field) throws Exception {
485
		MergeMode mergeMode =  this.getMergeMode(field.getName());
486
		try {
487
			if (mergeMode == MergeMode.FIRST){
488
				return field.get(mergeFirst);
489
			}else if (mergeMode == MergeMode.SECOND){
490
				return field.get(mergeSecond);
491
			}else if (mergeMode == MergeMode.NULL){
492
				return null;
493
			}else if (mergeMode == MergeMode.CONCAT){
494
				return ((String)field.get(mergeFirst) + (String)field.get(mergeSecond));
495
			}else if (mergeMode == MergeMode.AND){
496
				return ((Boolean)field.get(mergeFirst) && (Boolean)field.get(mergeSecond));
497
			}else if (mergeMode == MergeMode.OR){
498
				return ((Boolean)field.get(mergeFirst) || (Boolean)field.get(mergeSecond));
499
			}else{
500
				throw new IllegalStateException("Unknown MergeMode");
501
			}
502
		} catch (IllegalArgumentException e) {
503
			throw new Exception(e);
504
		}
505
	}
506

  
507

  
508
	private static Class getCollectionType(Field field) throws MergeException{
509
		Type genericType = field.getGenericType();
510
		if (genericType instanceof ParameterizedType/*Impl*/){
511
			ParameterizedType paraType = (ParameterizedType)genericType;
512
			Type rawType = paraType.getRawType();
513
			Type[] arguments = paraType.getActualTypeArguments();
514

  
515
			if (arguments.length == 1){
516
				Class collectionClass;
517
				if (arguments[0] instanceof Class){
518
					collectionClass = (Class)arguments[0];
519
				}else if(arguments[0] instanceof TypeVariable/*Impl*/){
520
					TypeVariable typeVariable = (TypeVariable)arguments[0];
521
					GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
522
					collectionClass = (Class)genericDeclaration;
523
				}else{
524
					throw new MergeException("Collection with other types than TypeVariableImpl are not yet supported");
525
				}
526
				return collectionClass;
527
			}else{
528
				throw new MergeException("Collection with multiple types not supported");
529
			}
530
		}else{
531
			throw new MergeException("Collection has no generic type of type ParameterizedTypeImpl. Unsupport case.");
532
		}
533
	}
534

  
535
}
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.merge;
12

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

  
29
import org.apache.log4j.Logger;
30

  
31
import eu.etaxonomy.cdm.common.CdmUtils;
32
import eu.etaxonomy.cdm.model.common.CdmBase;
33
import eu.etaxonomy.cdm.model.common.ICdmBase;
34
import eu.etaxonomy.cdm.model.common.IRelated;
35
import eu.etaxonomy.cdm.model.common.RelationshipBase;
36
import eu.etaxonomy.cdm.strategy.StrategyBase;
37

  
38
/**
39
 * @author a.mueller
40
 * @created 31.07.2009
41
 */
42
public class DefaultMergeStrategy extends StrategyBase implements IMergeStrategy  {
43
	private static final long serialVersionUID = -8513956338156791995L;
44
	private static final Logger logger = Logger.getLogger(DefaultMergeStrategy.class);
45
	final static UUID uuid = UUID.fromString("d85cd6c3-0147-452c-8fed-bbfb82f392f6");
46

  
47
	public static DefaultMergeStrategy NewInstance(Class<? extends CdmBase> mergeClazz){
48
		return new DefaultMergeStrategy(mergeClazz);
49
	}
50

  
51
	private final boolean onlyReallocateReferences = false;
52

  
53
	protected MergeMode defaultMergeMode = MergeMode.FIRST;
54
	protected MergeMode defaultCollectionMergeMode = MergeMode.ADD;
55

  
56
	protected Map<String, MergeMode> mergeModeMap = new HashMap<String, MergeMode>();
57
	protected Class<? extends CdmBase> mergeClass;
58
	protected Map<String, Field> mergeFields;
59

  
60
	protected DefaultMergeStrategy(Class<? extends CdmBase> mergeClazz) {
61
		super();
62
		if (mergeClazz == null){
63
			throw new IllegalArgumentException("Merge class must not be null");
64
		}
65
		this.mergeClass = mergeClazz;
66
		boolean includeStatic = false;
67
		boolean includeTransient = false;
68
		boolean makeAccessible = true;
69
		this.mergeFields = CdmUtils.getAllFields(mergeClass, CdmBase.class, includeStatic, includeTransient, makeAccessible, true);
70
		initMergeModeMap();
71
	}
72

  
73

  
74
	@Override
75
	public boolean isOnlyReallocateReferences() {
76
		return this.onlyReallocateReferences;
77
	}
78

  
79
//	@Override
80
//	public void setOnlyReallocateLinks(boolean onlyReallocateReferences) {
81
//		this.onlyReallocateReferences = onlyReallocateReferences;
82
//	}
83

  
84

  
85
	/**
86
	 *
87
	 */
88
	private void initMergeModeMap() {
89
		for (Field field: mergeFields.values()){
90
			for (Annotation annotation : field.getAnnotations()){
91
				if (annotation.annotationType() == Merge.class){
92
					MergeMode mergeMode = ((Merge)annotation).value();
93
					mergeModeMap.put(field.getName(), mergeMode);
94
				}
95
			}
96
		}
97
	}
98

  
99
	@Override
100
	protected UUID getUuid() {
101
		return uuid;
102
	}
103

  
104
	/**
105
	 * @return the merge class
106
	 */
107
	public Class<? extends CdmBase> getMergeClass() {
108
		return mergeClass;
109
	}
110

  
111

  
112

  
113
	/**
114
	 * @param mergeClazz the mergeClazz to set
115
	 */
116
	public void setMergeClazz(Class<? extends CdmBase> mergeClazz) {
117
		this.mergeClass = mergeClazz;
118
	}
119

  
120

  
121
	@Override
122
	public MergeMode getMergeMode(String propertyName){
123
		MergeMode result = mergeModeMap.get(propertyName);
124
		if (result == null){
125
			Field field = mergeFields.get(propertyName);
126
			if (isCollection(field.getType())){
127
				return defaultCollectionMergeMode;
128
			}else{
129
				return defaultMergeMode;
130
			}
131
		}else{
132
			return result;
133
		}
134
	}
135

  
136
	@Override
137
	public void setMergeMode(String propertyName, MergeMode mergeMode) throws MergeException{
138
		if (mergeFields.containsKey(propertyName)){
139
			checkIdentifier(propertyName, mergeMode);
140
			mergeModeMap.put(propertyName, mergeMode);
141
		}else{
142
			throw new MergeException("The class " + mergeClass.getName() + " does not contain a field with name " + propertyName);
143
		}
144
	}
145

  
146

  
147
	@Override
148
	public void setDefaultMergeMode(MergeMode defaultMergeMode) {
149
		this.defaultMergeMode = defaultMergeMode;
150
	}
151

  
152

  
153
	@Override
154
	public void setDefaultCollectionMergeMode(MergeMode defaultCollectionMergeMode) {
155
		this.defaultCollectionMergeMode = defaultCollectionMergeMode;
156
	}
157

  
158

  
159
	/**
160
	 * Tests if a property is an identifier property
161
	 * @param propertyName
162
	 * @param mergeMode
163
	 * @throws MergeException
164
	 */
165
	private void checkIdentifier(String propertyName, MergeMode mergeMode) throws MergeException {
166
		if (mergeMode != MergeMode.FIRST){
167
			if ("id".equalsIgnoreCase(propertyName) || "uuid".equalsIgnoreCase(propertyName)){
168
				throw new MergeException("Identifier must always have merge mode MergeMode.FIRST");
169
			}
170
		}
171
	}
172

  
173
	@Override
174
	public <T extends IMergable> Set<ICdmBase> invoke(T mergeFirst, T mergeSecond) throws MergeException {
175
		return this.invoke(mergeFirst, mergeSecond, null);
176
	}
177

  
178
	@Override
179
	public <T extends IMergable> Set<ICdmBase> invoke(T mergeFirst, T mergeSecond, Set<ICdmBase> clonedObjects) throws MergeException {
180
		Set<ICdmBase> deleteSet = new HashSet<ICdmBase>();
181
		if (clonedObjects == null){
182
			clonedObjects = new HashSet<ICdmBase>();
183
		}
184
		deleteSet.add(mergeSecond);
185
		try {
186
 			for (Field field : mergeFields.values()){
187
				Class<?> fieldType = field.getType();
188
				if (isIdentifier(field)){
189
					//do nothing (id and uuid stay with first object)
190
				}else if (isPrimitive(fieldType)){
191
					mergePrimitiveField(mergeFirst, mergeSecond, field);
192
				}else if (fieldType == String.class ){
193
					mergeStringField(mergeFirst, mergeSecond, field);
194
				}else if (isCollection(fieldType)){
195
					mergeCollectionField(mergeFirst, mergeSecond, field, deleteSet, clonedObjects);
196
				}else if(isUserType(fieldType)){
197
					mergeUserTypeField(mergeFirst, mergeSecond, field);
198
				}else if(isSingleCdmBaseObject(fieldType)){
199
					mergeSingleCdmBaseField(mergeFirst, mergeSecond, field, deleteSet);
200
				}else if(fieldType.isInterface()){
201
					mergeInterfaceField(mergeFirst, mergeSecond, field, deleteSet);
202
				}else if(fieldType.isEnum()){
203
					mergeEnumField(mergeFirst, mergeSecond, field, deleteSet);
204
				}else{
205
					throw new RuntimeException("Unknown Object type for merging: " + fieldType);
206
				}
207
			}
208
 			return deleteSet;
209
		} catch (Exception e) {
210
			throw new MergeException("Merge Exception in invoke", e);
211
		}
212
	}
213

  
214

  
215
	/**
216
	 * @throws Exception
217
	 *
218
	 */
219
	private <T extends IMergable> void mergeInterfaceField(T mergeFirst, T mergeSecond, Field field, Set<ICdmBase> deleteSet) throws Exception {
220
		String propertyName = field.getName();
221
		MergeMode mergeMode =  this.getMergeMode(propertyName);
222
		if (mergeMode != MergeMode.FIRST){
223
			mergeCdmBaseValue(mergeFirst, mergeSecond, field, deleteSet);
224
		}
225
		logger.debug(propertyName + ": " + mergeMode + ", " + field.getType().getName());
226

  
227
	}
228

  
229
	/**
230
	 * @throws Exception
231
	 *
232
	 */
233
	private <T extends IMergable> void mergeEnumField(T mergeFirst, T mergeSecond, Field field, Set<ICdmBase> deleteSet) throws Exception {
234
		String propertyName = field.getName();
235
		MergeMode mergeMode =  this.getMergeMode(propertyName);
236
		if (mergeMode != MergeMode.FIRST){
237
			mergeCdmBaseValue(mergeFirst, mergeSecond, field, deleteSet);
238
		}
239
		logger.debug(propertyName + ": " + mergeMode + ", " + field.getType().getName());
240

  
241
	}
242

  
243
	/**
244
	 * @throws Exception
245
	 *
246
	 */
247
	private <T extends IMergable> void mergeSingleCdmBaseField(T mergeFirst, T mergeSecond, Field field, Set<ICdmBase> deleteSet) throws Exception {
248
		String propertyName = field.getName();
249
		MergeMode mergeMode =  this.getMergeMode(propertyName);
250
		if (mergeMode != MergeMode.FIRST){
251
			mergeCdmBaseValue(mergeFirst, mergeSecond, field, deleteSet);
252
		}
253
		logger.debug(propertyName + ": " + mergeMode + ", " + field.getType().getName());
254

  
255
	}
256

  
257
	private <T extends IMergable> void mergeCdmBaseValue(T mergeFirst, T mergeSecond, Field field, Set<ICdmBase> deleteSet) throws Exception {
258
		if (true){
259
			Object value = getMergeValue(mergeFirst, mergeSecond, field);
260
			if (value instanceof ICdmBase || value == null){
261
				field.set(mergeFirst, value);
262
			}else{
263
				throw new MergeException("Merged value must be of type CdmBase but is not: " + value.getClass());
264
			}
265
		}else{
266
			throw new MergeException("Not supported mode");
267
		}
268
	}
269

  
270
	/**
271
	 * @throws Exception
272
	 *
273
	 */
274
	private <T extends IMergable> void mergeUserTypeField(T mergeFirst, T mergeSecond, Field field) throws Exception {
275
		String propertyName = field.getName();
276
		Class<?> fieldType = field.getType();
277
		MergeMode mergeMode =  this.getMergeMode(propertyName);
278
		if (mergeMode == MergeMode.MERGE){
279
			Method mergeMethod = getMergeMethod(fieldType);
280
			Object firstObject = field.get(mergeFirst);
281
			if (firstObject == null){
282
				firstObject = fieldType.newInstance();
283
			}
284
			Object secondObject = field.get(mergeSecond);
285
			mergeMethod.invoke(firstObject, secondObject);
286
		}else if (mergeMode != MergeMode.FIRST){
287
			Object value = getMergeValue(mergeFirst, mergeSecond, field);
288
			field.set(mergeFirst, value);
289
		}
290
		logger.debug(propertyName + ": " + mergeMode + ", " + fieldType.getName());
291
	}
292

  
293
	/**
294
	 * @return
295
	 * @throws NoSuchMethodException
296
	 * @throws SecurityException
297
	 */
298
	private Method getMergeMethod(Class<?> fieldType) throws SecurityException, NoSuchMethodException {
299
		Method mergeMethod = fieldType.getDeclaredMethod("merge", fieldType);
300
		return mergeMethod;
301
	}
302

  
303

  
304

  
305
	/**
306
	 * @throws Exception
307
	 *
308
	 */
309
	private <T extends IMergable> void mergeCollectionField(T mergeFirst, T mergeSecond, Field field, Set<ICdmBase> deleteSet, Set<ICdmBase> clonedObjects) throws Exception {
310
		String propertyName = field.getName();
311
		Class<?> fieldType = field.getType();
312
		MergeMode mergeMode =  this.getMergeMode(propertyName);
313
		if (mergeMode != MergeMode.FIRST){
314
			mergeCollectionFieldNoFirst(mergeFirst, mergeSecond, field, mergeMode, deleteSet, clonedObjects);
315
		}
316
		logger.debug(propertyName + ": " + mergeMode + ", " + fieldType.getName());
317
	}
318

  
319
	protected <T extends IMergable> void mergeCollectionFieldNoFirst(T mergeFirst, T mergeSecond, Field field, MergeMode mergeMode, Set<ICdmBase> deleteSet, Set<ICdmBase> clonedObjects) throws Exception{
320
		Class<?> fieldType = field.getType();
321
		if (mergeMode == MergeMode.ADD || mergeMode == MergeMode.ADD_CLONE){
322
			//FIXME
323
			Method addMethod = getAddMethod(field);
324
			Method removeMethod = getAddMethod(field, true);
325

  
326
			if (Set.class.isAssignableFrom(fieldType) || List.class.isAssignableFrom(fieldType)){
327
				Collection<ICdmBase> secondCollection = (Collection<ICdmBase>)field.get(mergeSecond);
328
				List<ICdmBase> removeList = new ArrayList<ICdmBase>();
329
				if(secondCollection != null) {
330
    				for (ICdmBase obj : secondCollection){
331
    					Object objectToAdd;
332
    					if (mergeMode == MergeMode.ADD){
333
    						objectToAdd = obj;
334
    					}else if(mergeMode == MergeMode.ADD_CLONE){
335
    						Method cloneMethod = obj.getClass().getDeclaredMethod("clone");
336
    						objectToAdd = cloneMethod.invoke(obj);
337
    						clonedObjects.add(obj);
338
    					}else{
339
    						throw new MergeException("Unknown collection merge mode: " + mergeMode);
340
    					}
341
    					addMethod.invoke(mergeFirst, objectToAdd);
342
    					removeList.add(obj);
343
    				}
344
				}
345
				for (ICdmBase removeObj : removeList ){
346
					//removeMethod.invoke(mergeSecond, removeObj);
347
					if ((removeObj instanceof CdmBase)&& mergeMode == MergeMode.ADD_CLONE) {
348
						deleteSet.add(removeObj);
349
					}
350
				}
351
			}else{
352
				throw new MergeException("Merge for collections other than sets and lists not yet implemented");
353
			}
354
		}else if (mergeMode == MergeMode.RELATION){
355
			if (Set.class.isAssignableFrom(fieldType) || List.class.isAssignableFrom(fieldType)){
356
				Collection<RelationshipBase<?,?,?>> secondCollection = (Collection<RelationshipBase<?,?,?>>)field.get(mergeSecond);
357
				List<ICdmBase> removeList = new ArrayList<ICdmBase>();
358
				for (RelationshipBase<?,?,?> relation : secondCollection){
359
					Method relatedFromMethod = RelationshipBase.class.getDeclaredMethod("getRelatedFrom");
360
					relatedFromMethod.setAccessible(true);
361
					Object relatedFrom = relatedFromMethod.invoke(relation);
362

  
363
					Method relatedToMethod = RelationshipBase.class.getDeclaredMethod("getRelatedTo");
364
					relatedToMethod.setAccessible(true);
365
					Object relatedTo = relatedToMethod.invoke(relation);
366

  
367
					if (relatedFrom.equals(mergeSecond)){
368
						Method setRelatedMethod = RelationshipBase.class.getDeclaredMethod("setRelatedFrom", IRelated.class);
369
						setRelatedMethod.setAccessible(true);
370
						setRelatedMethod.invoke(relation, mergeFirst);
371
					}
372
					if (relatedTo.equals(mergeSecond)){
373
						Method setRelatedMethod = RelationshipBase.class.getDeclaredMethod("setRelatedTo", IRelated.class);
374
						setRelatedMethod.setAccessible(true);
375
						setRelatedMethod.invoke(relation, mergeFirst);
376
					}
377
					((IRelated)mergeFirst).addRelationship(relation);
378
					removeList.add(relation);
379
				}
380
				for (ICdmBase removeObj : removeList){
381
					//removeMethod.invoke(mergeSecond, removeObj);
382
					if (removeObj instanceof CdmBase){
383
						deleteSet.add(removeObj);
384
					}
385
				}
386
			}else{
387
				throw new MergeException("Merge for collections other than sets and lists not yet implemented");
388
			}
389
		}else{
390
			throw new MergeException("Other merge modes for collections not yet implemented");
391
		}
392
	}
393

  
394
	public static Method getReplaceMethod(Field field) throws MergeException{
395
	    return getAddMethod(field, false, true);
396
	}
397

  
398
	private Method getAddMethod(Field field) throws MergeException{
399
		return getAddMethod(field, false, false);
400
	}
401

  
402
	public static Method getAddMethod(Field field, boolean remove) throws MergeException{
403
	    return getAddMethod(field, remove, false);
404
	}
405

  
406
	private static Method getAddMethod(Field field, boolean remove, boolean replace) throws MergeException{
407
		Method result;
408
		Class<?> parameterClass = getCollectionType(field);
409
		Class<?>[] parameterClasses = replace ?
410
		        new Class[]{parameterClass,parameterClass}:
411
		        new Class[]{parameterClass};
412
		String fieldName = field.getName();
413
		String firstCapital = fieldName.substring(0, 1).toUpperCase();
414
		String rest = fieldName.substring(1);
415
		String prefix = replace? "replace" : remove? "remove": "add";
416
		String methodName = prefix + firstCapital + rest;
417
		boolean endsWithS = parameterClass.getSimpleName().endsWith("s");
418
		if (! endsWithS && ! fieldName.equals("media")){
419
			methodName = methodName.substring(0, methodName.length() -1); //remove 's' at end
420
		}
421
		Class<?> methodClass = field.getDeclaringClass();
422
		try {
423
			result = methodClass.getMethod(methodName, parameterClasses);
424
		}catch (NoSuchMethodException e1) {
425
			try {
426
				result = methodClass.getDeclaredMethod(methodName, parameterClasses);
427
				result.setAccessible(true);
428
			} catch (NoSuchMethodException e) {
429
				logger.warn(methodName);
430
				throw new IllegalArgumentException("Default '" +prefix+"' method for collection field ("+field.getName()+") does not exist");
431
			}
432
		} catch (SecurityException e) {
433
			throw e;
434
		}
435
		return result;
436
	}
437

  
438
	/**
439
	 * @throws Exception
440
	 *
441
	 */
442
	private <T extends IMergable> void mergePrimitiveField(T mergeFirst, T mergeSecond, Field field) throws Exception {
443
		String propertyName = field.getName();
444
		Class<?> fieldType = field.getType();
445
		MergeMode mergeMode =  this.getMergeMode(propertyName);
446
		if (mergeMode != MergeMode.FIRST){
447
			Object value = getMergeValue(mergeFirst, mergeSecond, field);
448
			field.set(mergeFirst, value);
449
		}
450
		logger.debug(propertyName + ": " + mergeMode + ", " + fieldType.getName());
451

  
452
	}
453

  
454
	/**
455
	 * @throws Exception
456
	 *
457
	 */
458
	private <T extends IMergable> void mergeStringField(T mergeFirst, T mergeSecond, Field field) throws Exception {
459
		String propertyName = field.getName();
460
		Class<?> fieldType = field.getType();
461
		MergeMode mergeMode =  this.getMergeMode(propertyName);
462
		if (mergeMode != MergeMode.FIRST){
463
			Object value = getMergeValue(mergeFirst, mergeSecond, field);
464
			field.set(mergeFirst, value);
465
		}
466
		logger.debug(propertyName + ": " + mergeMode + ", " + fieldType.getName());
467

  
468
	}
469

  
470
	/**
471
	 * @param fieldType
472
	 * @return
473
	 */
474
	private boolean isIdentifier(Field field) {
475
		Class<?> fieldType = field.getType();
476
		if ("id".equals(field.getName()) && fieldType == int.class ){
477
			return true;
478
		}else if ("uuid".equals(field.getName()) && fieldType == UUID.class ){
479
			return true;
480
		}else{
481
			return false;
482
		}
483
	}
484

  
485

  
486
	/**
487
	 * @param cdmBase
488
	 * @param toMerge
489
	 * @param field
490
	 * @param mergeMode
491
	 * @throws Exception
492
	 */
493
	protected <T extends IMergable> Object getMergeValue(T mergeFirst, T mergeSecond,
494
			Field field) throws Exception {
495
		MergeMode mergeMode =  this.getMergeMode(field.getName());
496
		try {
497
			if (mergeMode == MergeMode.FIRST){
498
				return field.get(mergeFirst);
499
			}else if (mergeMode == MergeMode.SECOND){
500
				return field.get(mergeSecond);
501
			}else if (mergeMode == MergeMode.NULL){
502
				return null;
503
			}else if (mergeMode == MergeMode.CONCAT){
504
				return ((String)field.get(mergeFirst) + (String)field.get(mergeSecond));
505
			}else if (mergeMode == MergeMode.AND){
506
				return ((Boolean)field.get(mergeFirst) && (Boolean)field.get(mergeSecond));
507
			}else if (mergeMode == MergeMode.OR){
508
				return ((Boolean)field.get(mergeFirst) || (Boolean)field.get(mergeSecond));
509
			}else{
510
				throw new IllegalStateException("Unknown MergeMode");
511
			}
512
		} catch (IllegalArgumentException e) {
513
			throw new Exception(e);
514
		}
515
	}
516

  
517

  
518
	private static Class getCollectionType(Field field) throws MergeException{
519
		Type genericType = field.getGenericType();
520
		if (genericType instanceof ParameterizedType/*Impl*/){
521
			ParameterizedType paraType = (ParameterizedType)genericType;
522
			Type rawType = paraType.getRawType();
523
			Type[] arguments = paraType.getActualTypeArguments();
524

  
525
			if (arguments.length == 1){
526
				Class collectionClass;
527
				if (arguments[0] instanceof Class){
528
					collectionClass = (Class)arguments[0];
529
				}else if(arguments[0] instanceof TypeVariable/*Impl*/){
530
					TypeVariable typeVariable = (TypeVariable)arguments[0];
531
					GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
532
					collectionClass = (Class)genericDeclaration;
533
				}else{
534
					throw new MergeException("Collection with other types than TypeVariableImpl are not yet supported");
535
				}
536
				return collectionClass;
537
			}else{
538
				throw new MergeException("Collection with multiple types not supported");
539
			}
540
		}else{
541
			throw new MergeException("Collection has no generic type of type ParameterizedTypeImpl. Unsupport case.");
542
		}
543
	}
544

  
545
}

Also available in: Unified diff