View Javadoc

1   /*
2    * EL4J, the Extension Library for the J2EE, adds incremental enhancements to
3    * the spring framework, http://el4j.sf.net
4    * Copyright (C) 2006 by ELCA Informatique SA, Av. de la Harpe 22-24,
5    * 1000 Lausanne, Switzerland, http://www.elca.ch
6    *
7    * EL4J is published under the GNU Lesser General Public License (LGPL)
8    * Version 2.1. See http://www.gnu.org/licenses/
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13   * GNU Lesser General Public License for more details.
14   *
15   * For alternative licensing, please contact info@elca.ch
16   */
17  package ch.elca.el4j.services.persistence.generic.dao;
18  
19  import java.lang.reflect.AccessibleObject;
20  import java.lang.reflect.Array;
21  import java.lang.reflect.Field;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Modifier;
24  import java.security.AccessController;
25  import java.security.PrivilegedAction;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.HashMap;
29  import java.util.IdentityHashMap;
30  import java.util.List;
31  import java.util.Map;
32  
33  import org.aopalliance.intercept.MethodInvocation;
34  import org.apache.commons.collections.map.AbstractReferenceMap;
35  import org.apache.commons.collections.map.ReferenceMap;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  import org.springframework.aop.IntroductionInterceptor;
39  import org.springframework.aop.support.IntroductionInfoSupport;
40  
41  import ch.elca.el4j.services.persistence.generic.dao.DaoChangeNotifier.NewEntityState;
42  import ch.elca.el4j.services.persistence.generic.dao.IdentityFixerMergePolicy.UpdatePolicy;
43  import ch.elca.el4j.services.persistence.generic.dao.annotations.ReturnsUnchangedParameter;
44  import ch.elca.el4j.services.persistence.generic.dao.impl.DefaultDaoChangeNotifier;
45  import ch.elca.el4j.util.codingsupport.AopHelper;
46  
47  /**
48   * Fixes object identities mangled by loosing ORM context or by remoting.
49   *
50   * <h4>Motivation</h4>
51   *
52   * Object Identity is an important concept in OOP, but is not always
53   * guaranteed or maintained. For instance, sending an object back and
54   * forth over the wire (using any standard remoting protocol) will create new
55   * objects, causing changes applied to the object not to propagate properly -
56   * even within a single VM. Similarly, loosing an OR-Mapper's context between
57   * load invocations typically results in creating multiple proxies to the same
58   * persisted object. Accepting that loss of identity complicates the programming
59   * model, and contradicts OO methodology.
60   *
61   * <p> Given a definition of logical identity and a means to recognize
62   * immutable value types, an instance of this class fixes the identities of the
63   * objects passing through it while propagating state updates to the logical
64   * object's unique representative.
65   *
66   * <h4>Terminology</h4>
67   * A <i>representative</i> is an object that has a logical identity. A given
68   * identity fixer will always return the same representative for every logical
69   * identity, different identity fixers may return different ones. Usually,
70   * you will therefore use a singleton identity fixer, but you may allocate as
71   * many as you please ;-)
72   *
73   * <h4>Configuration</h4>
74   * To get a working identity fixer, you must write a subclass and override the
75   * three abstract methods, {@link #id(Object)}, {@link #immutableValue(Object)}
76   * and {@link #prepareObject(Object)}.
77   *
78   * <h4>Use</h4>
79   * All objects received from an identity-mangling source should pass through an
80   * identity fixer. {@link GenericInterceptor} provides a generic Spring AOP
81   * interceptor to be wrapped around the identity-mangling objects. As an
82   * alternative, manual
83   * access to identity translation is granted by {@link #merge(Object, Object)}.
84   *
85   * <h4>Guarantees</h4>
86   * During its lifetime, an identity fixer will always return the same object
87   * for every logical identity. It will update the shared instance with the state
88   * of the new copies - according to a specified {@link IdentityFixerMergePolicy} or by its
89   * default policy -, and notify registered observers about every such update.
90   * These guarantees extend to objects (directly or indirectly) referenced by the
91   * translated object unless they are recognized as immutable values by
92   * {@link #immutableValue(Object)}.
93   * 
94   * <h4>2-way merging of Collections</h4>
95   * Since some identity-mangling sources are replacing collections by own implementations containing
96   * also metadata, this class offers a mechanism to work on normal java collections while the source
97   * still gets to work on its own versions of the collections.<br>
98   * To set up a working 2-way merging, you need to:
99   * <ul>
100  * 	<li>implement {@link #needsAdditionalProcessing(Object)} to identify the replaced collections.</li>
101  * 	<li>call {@link #reverseMerge} on every object you pass to the source</li>
102  *  <li>call {@link #merge} as usual on the objects coming from the source</li>
103  * </ul>
104  *
105  * <h4>Requirements</h4>
106  * <p> This class needs {@link java.lang.reflect.ReflectPermission}
107  * "suppressAccessChecks" if a security manager is present and an object
108  * requiring fixing has non-public fields.
109  *
110  * @svnLink $Revision: 4068 $;$Date: 2010-01-05 09:38:21 +0100 (Di, 05. Jan 2010) $;$Author: jonasha $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/core/src/main/java/ch/elca/el4j/services/persistence/generic/dao/AbstractIdentityFixer.java $
111  *
112  * @author Adrian Moos (AMS)
113  */
114 public abstract class AbstractIdentityFixer {
115 	/**
116 	 * Id for objects of anonymous types (= value types).
117 	 */
118 	protected static final Object ANONYMOUS = new Object();
119 	
120 	/** Cache for {@link #fields(Class)}. */
121 	static Map<Class<?>, List<Field>> s_cachedFields
122 		= new HashMap<Class<?>, List<Field>>();
123 
124 	/** The logger. */
125 	static Logger s_logger = LoggerFactory.getLogger(AbstractIdentityFixer.class);
126 	
127 	/** ID generator for logging. */
128 	static ObjectIdentifier s_oi = new ObjectIdentifier();
129 	
130 	
131 	/** Indentation for tracing. */
132 	int m_traceIndentation = 0;
133 	
134 	/** The notifier for broadcasting changes. */
135 	DaoChangeNotifier m_changeNotifier;
136 	
137 	/**
138 	 * The representatives, keyed by their id.
139 	 * @see #id(Object)
140 	 */
141 	Map<Object, Object> m_representatives;
142 	
143 	/**
144 	 * The collection mapping for a 2-way merging [Object world -> Persistence world].
145 	 */
146 	IdentityHashMap<Collection<?>, Collection<?>> m_collectionMapping
147 		= new IdentityHashMap<Collection<?>, Collection<?>>();
148 	
149 	/**
150 	 * The temporary reverse collection mapping for a 2-way merging [Persistence world -> Object world].
151 	 * This is used for efficiency reasons.
152 	 */
153 	IdentityHashMap<Collection<?>, Collection<?>> m_reverseCollectionMapping
154 		= new IdentityHashMap<Collection<?>, Collection<?>>();
155 	
156 	/**
157 	 * The temporary mapping of all the collections which has to be re-fixed when merging back
158 	 * in the 2-way merging process [Persistence world -> Object world] keyed by their parent object
159 	 * and the field to access the collection.
160 	 */
161 	HashMap<IdentityFixerCollectionField, Collection<?>> m_collectionsToBeReplaced
162 		= new HashMap<IdentityFixerCollectionField, Collection<?>>();
163 
164 	/**
165 	 * Constructs a new IdentityFixer. You'd never have guessed that, would you?
166 	 * ;-)
167 	 */
168 	public AbstractIdentityFixer() {
169 		this(new DefaultDaoChangeNotifier());
170 	}
171 	
172 	/**
173 	 * Constructor.
174 	 * @param changeNotifier The notifier for broadcasting changes.
175 	 */
176 	@SuppressWarnings("unchecked")
177 	public AbstractIdentityFixer(DaoChangeNotifier changeNotifier) {
178 		m_changeNotifier = changeNotifier;
179 		m_representatives = new ReferenceMap(
180 			AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
181 	}
182 	
183 	/**
184 	 * Returns the notifier used to announce changes. Chiefly useful because
185 	 * you can subscribe to change messages there.
186 	 * @return see above
187 	 */
188 	public DaoChangeNotifier getChangeNotifier() {
189 		return m_changeNotifier;
190 	}
191 	
192 	
193 	/**
194 	 * Returns a list of all non-static fields of class {@code c}.
195 	 * @param c The concerned class
196 	 * @return A list of all non-static fields of the given class
197 	 */
198 	protected static List<Field> instanceFields(Class<?> c) {
199 		List<Field> fs = new ArrayList<Field>();
200 		for (Class<?> sc = c; sc != null; sc = sc.getSuperclass()) {
201 			for (Field f : sc.getDeclaredFields()) {
202 				if (!Modifier.isStatic(f.getModifiers())) {
203 					fs.add(f);
204 				}
205 			}
206 		}
207 		return fs;
208 	}
209 	
210 	
211 	/**
212 	 * Returns a list of all non-static AccessibleObjects
213 	 * (fields and methods, no constructors) of class {@code c}.
214 	 * Also searches in superclasses.
215 	 *
216 	 * @param c the concerned class
217 	 * @return The accessible objects of this class.
218 	 */
219 	protected static List<AccessibleObject>
220 	instanceAccessibleObjects(Class<?> c) {
221 		List<AccessibleObject> fs = new ArrayList<AccessibleObject>();
222 		
223 		//TODO: also search interfaces?
224 		for (Class<?> sc = c; sc != null; sc = sc.getSuperclass()) {
225 			
226 			//first, get fields
227 			for (Field f : sc.getDeclaredFields()) {
228 				if (!Modifier.isStatic(f.getModifiers())) {
229 					fs.add(f);
230 				}
231 			}
232 			
233 			//next, get methods
234 			for (Method m : sc.getDeclaredMethods()) {
235 				if (!Modifier.isStatic(m.getModifiers())) {
236 					fs.add(m);
237 				}
238 			}
239 		}
240 		return fs;
241 	}
242 	
243 	
244 	
245 	/**
246 	 * Returns the Fields corresponding to fields in class {@code c}. The fields
247 	 * can be read from / written into (regardless of access modifiers).
248 	 * @param c the class whose fields are desired
249 	 * @return see above.
250 	 */
251 	private static List<Field> fields(Class<?> c) {
252 		List<Field> fields = s_cachedFields.get(c);
253 		if (fields == null) {
254 			fields = instanceFields(c);
255 			s_cachedFields.put(c, fields);
256 			
257 			for (Field f : fields) {
258 				if (!Modifier.isPublic(f.getModifiers())) {
259 					final Field[] FIELDS
260 						= fields.toArray(new Field[fields.size()]);
261 					AccessController.doPrivileged(
262 						new PrivilegedAction<Object>() {
263 							public Object run() {
264 								Field.setAccessible(FIELDS, true);
265 								return null;
266 							}
267 						}
268 					);
269 				}
270 			}
271 		}
272 		return fields;
273 	}
274 	
275 	/**
276 	 * Actually performs the merge.
277 	 *
278 	 * @param anchor
279 	 *              the (presumed) representative, or null
280 	 * @param updated
281 	 *              the new version of the object
282 	 * @param isIdentical
283 	 *              whether anchor is known to be transitively identical with
284 	 *              updated.
285 	 * @param reached
286 	 *              the set of objects in the updated object graph that have
287 	 *              been (or are being) merged. Used to avoid merging an
288 	 *              object more than once.
289 	 * @param objectsToUpdate
290 	 *              A list of anchor objects that should updated, all other objects are not touched. If objectsToUpdate
291 	 *              is <code>null</code> then all reachable objects get updated.
292 	 * @param hintMapping
293 	 *              A map of [updated -> anchor] used to correctly merge collections. If no collections have to be
294 	 *              merged this parameter can be <code>null</code>.
295 	 * @return
296 	 *              the representative.
297 	 */
298 	@Deprecated
299 	@SuppressWarnings("unchecked")
300 	protected <T> T merge(T anchor, T updated,
301 		boolean isIdentical,
302 		IdentityHashMap<Object, Object> reached,
303 		List<Object> objectsToUpdate, IdentityHashMap<Object, Object> hintMapping) {
304 		
305 		// Prepare the updated object
306 		T prepUpdated = (T) prepareObject(updated);
307 		
308 		if (immutableValue(prepUpdated)) {
309 			trace("", prepUpdated, " is an immutable value");
310 			return prepUpdated;
311 		}
312 				
313 		T attached = (T) reached.get(prepUpdated);
314 		if (attached != null) {
315 			trace("",  prepUpdated, " was already merged to ", attached);
316 			return attached;
317 		}
318 
319 		boolean isNew = true;
320 		
321 		// choose representative
322 		Object id = id(prepUpdated);
323 		if (isIdentical) {
324 			// anchor is the guaranteed representative
325 			attached = anchor;
326 			assert id(anchor) == null
327 				|| id(anchor) == ANONYMOUS
328 				|| id(anchor).equals(id);
329 			isNew = false;
330 		} else {
331 			if (id == ANONYMOUS) {
332 				attached = anchor;
333 				isNew = attached == null;
334 			} else {
335 				// check for a corresponding representative
336 				if (id != null) {
337 					attached = (T) m_representatives.get(id);
338 					isNew = (attached != null) ? false : true;
339 				}
340 				// we don't have to merge if attached == updated, meaning that it equals the representative
341 				if (attached == prepUpdated) {
342 					reached.put(prepUpdated, attached);
343 					return attached;
344 				}
345 			}
346 			
347 			
348 			// if no representative found, go through graph and insert the new objects
349 			if (attached == null) {
350 				attached = prepUpdated;
351 			}
352 		}
353 		assert attached != null;
354 		if (id != ANONYMOUS && id != null) {
355 			m_representatives.put(id, attached);
356 		}
357 		
358 		// register representative
359 		reached.put(prepUpdated, attached);
360 		
361 		trace("merging ", prepUpdated, " to ", attached);
362 		m_traceIndentation++;
363 		if (prepUpdated.getClass().isArray()) {
364 			
365 			int l = Array.getLength(prepUpdated);
366 			assert Array.getLength(attached) == Array.getLength(prepUpdated);
367 			for (int i = 0; i < l; i++) {
368 				Array.set(
369 					attached, i,
370 					merge(
371 						attached != null ? Array.get(attached, i) : null,
372 						Array.get(prepUpdated, i),
373 						attached != null && isIdentical,
374 						reached,
375 						objectsToUpdate,
376 						hintMapping
377 					)
378 				);
379 			}
380 		} else if (attached instanceof Collection) {
381 			Collection attachedCollection = (Collection) attached;
382 			Collection updatedCollection = (Collection) prepUpdated;
383 			
384 			List mergedEntries = new ArrayList(attachedCollection.size());
385 			
386 			for (Object updatedObject : updatedCollection) {
387 				Object anchorObject = null;
388 				if (hintMapping != null) {
389 					anchorObject = hintMapping.get(updatedObject);
390 				}
391 				mergedEntries.add(merge(anchorObject,
392 					updatedObject, anchorObject != null && isIdentical, reached, objectsToUpdate, hintMapping));
393 			}
394 			
395 			
396 			// Only refill the collection if object has to be updated
397 			if (objectsToUpdate == null || objectsToUpdate.contains(attachedCollection) || isNew) {
398 				// write collection to be sure that it contains "exactly the same" (in the sense of identity) objects
399 				// that are present in the updated collection (might be less or more objects or another ordering,
400 				// if collection supports it)
401 				updatedCollection.clear();
402 				updatedCollection.addAll(mergedEntries);
403 			} else {
404 				// refill the new collection with the old values
405 				updatedCollection.clear();
406 				updatedCollection.addAll(attachedCollection);
407 			}
408 			attached = (T) updatedCollection;
409 		} else {
410 			boolean isUpdateNeeded = objectsToUpdate == null || objectsToUpdate.contains(attached) || isNew;
411 			for (Field f : fields(prepUpdated.getClass())) {
412 				try {
413 					Object fieldValue = f.get(prepUpdated);
414 					Object fieldValue2 = (attached != null) ? f.get(attached) : null;
415 					boolean collectionUpdate = fieldValue2 != null && fieldValue2 instanceof Collection 
416 						&& id(fieldValue2) == ANONYMOUS;
417 					if (objectsToUpdate != null && isUpdateNeeded && collectionUpdate) {
418 						objectsToUpdate.add(fieldValue2);
419 					}
420 					Object merged = merge(
421 						(isUpdateNeeded || collectionUpdate) ? fieldValue2 : null,
422 						fieldValue,
423 						fieldValue2 != null && isIdentical,
424 						reached,
425 						objectsToUpdate,
426 						hintMapping
427 					);
428 					// If objectsToUpdate is specified, perform overwrite only on entities
429 					// and objects stored in objectsToUpdate, not on values.
430 					// This is important when we reload an entity from database where not all
431 					// references are loaded (lazy loading). Then we don't want to overwrite
432 					// valid local references with not loaded (=null) references
433 					if (isUpdateNeeded || collectionUpdate) {
434 						
435 						f.set(attached, merged);
436 					}
437 				} catch (IllegalAccessException e) { assert false : e; }
438 			}
439 		}
440 		m_traceIndentation--;
441 
442 		// notify state change
443 		NewEntityState e = new NewEntityState();
444 		e.setChangee(attached);
445 		m_changeNotifier.announce(e);
446 		
447 		return attached;
448 	}
449 	
450 	/**
451 	 * Actually performs the merge.
452 	 *
453 	 * @param anchor
454 	 *              the (presumed) representative, or null
455 	 * @param updated
456 	 *              the new version of the object
457 	 * @param policy
458 	 *              the policy to use.
459 	 * @param isIdentical
460 	 *              whether anchor is known to be transitively identical with
461 	 *              updated.
462 	 * @param reached
463 	 *              the set of objects in the updated object graph that have
464 	 *              been (or are being) merged. Used to avoid merging an
465 	 *              object more than once.
466 	 * @param locked
467 	 *              the set of id's that are locked for updating unless the new version
468 	 *              is the specified object. This map should be used to prevent updating a representative
469 	 *              multiple times which might result in a update to an old or non fully loaded object.
470 	 * @return
471 	 *              the representative.
472 	 */
473 	@SuppressWarnings("unchecked")
474 	protected <T> T merge(T anchor, T updated, IdentityFixerMergePolicy policy, boolean isIdentical,
475 		IdentityHashMap<Object, Object> reached, HashMap<Object, Object> locked) {
476 		
477 		// create local variables because one should not modify arguments directly (checkstyle)
478 		T referenceHolder = null;
479 		T valueHolder = updated;
480 		boolean identical = isIdentical;
481 		
482 		if (policy.needsPreparation()) {
483 			// Prepare the updated object (e.g. unproxy)
484 			valueHolder = (T) prepareObject(valueHolder);
485 		}
486 		
487 		// immutable?
488 		if (immutableValue(valueHolder)) {
489 			trace("", valueHolder, " is an immutable value");
490 			return valueHolder;
491 		}
492 		
493 		// already merged?
494 		T savedState = (T) reached.get(valueHolder);
495 		if (savedState != null) {
496 			trace("",  valueHolder, " was already merged to ", savedState);
497 			return savedState;
498 		}
499 
500 		if (identical) {
501 			// check if anchor would overwrite an already stored representative
502 			if (id(anchor) != null && m_representatives.get(id(anchor)) != null) {
503 				// anchor already present as representative,
504 				s_logger.debug("Anchor was given for a representative already present, anchor is discarded!");
505 				identical = false;
506 				assert anchor == m_representatives.get(id(anchor));
507 			}
508 		}
509 		
510 		boolean isNew = true;
511 		
512 		// choose representative
513 		Object valueHolderId = id(valueHolder);
514 		
515 		if (identical) {
516 			// anchor is the guaranteed representative
517 			referenceHolder = anchor;
518 			assert id(referenceHolder) == null
519 				|| id(referenceHolder) == ANONYMOUS
520 				|| id(referenceHolder).equals(valueHolderId);
521 			isNew = false;
522 		} else {
523 			if (valueHolderId == ANONYMOUS) {
524 				// saved state of anonymous object might be given as anchor
525 				referenceHolder = anchor;
526 				isNew = (referenceHolder == null);
527 			} else {
528 				// check for a corresponding representative
529 				if (valueHolderId != null) {
530 					referenceHolder = (T) m_representatives.get(valueHolderId);
531 					isNew = (referenceHolder == null);
532 				}
533 				// we have to merge even if attached == updated, meaning that it equals the representative already
534 				// in case we are in a 2-way merging scenario and supposed to fix the collections again!
535 				// we are only safe if the object was reached already, i.e. it is stored in the reached map.
536 				if (referenceHolder == valueHolder && reached.containsValue(referenceHolder)) {
537 					reached.put(valueHolder, referenceHolder);
538 					return referenceHolder;
539 				}
540 			}
541 			
542 			// if no representative found, go through graph and insert the new objects
543 			if (referenceHolder == null) {
544 				referenceHolder = valueHolder;
545 			}
546 		}
547 		assert referenceHolder != null;
548 		
549 		if (valueHolderId != null && valueHolderId != ANONYMOUS) {
550 			// if id of updateState is locked, return
551 			if (locked.get(valueHolderId) != null && locked.get(valueHolderId) != valueHolder) {
552 				return referenceHolder;
553 			}
554 			if (isNew || identical) {
555 				m_representatives.put(valueHolderId, referenceHolder);
556 			}
557 		}
558 		
559 		// register representative
560 		reached.put(valueHolder, referenceHolder);
561 		
562 		trace("merging ", valueHolder, " to ", referenceHolder);
563 		m_traceIndentation++;
564 		if (valueHolder.getClass().isArray()) {
565 			
566 			assert Array.getLength(referenceHolder) == Array.getLength(valueHolder);
567 			for (int i = 0; i < Array.getLength(valueHolder); i++) {
568 				Array.set(
569 					referenceHolder, i,
570 					merge(
571 						Array.get(referenceHolder, i),
572 						Array.get(valueHolder, i),
573 						policy,
574 						identical,
575 						reached,
576 						locked
577 					)
578 				);
579 			}
580 		} else if (valueHolder instanceof Collection) {
581 			Collection savedCollection = (Collection) referenceHolder;
582 			Collection updateCollection = (Collection) valueHolder;
583 			
584 			List mergedEntries;
585 			
586 			// Check the update policy, or take new list if new or old is immutable
587 			boolean isInPolicy = false;
588 			try {
589 				isInPolicy = policy.getObjectsToUpdate().contains(savedCollection);
590 			} catch (Exception e) {
591 				isInPolicy = false;
592 			}
593 			if (policy.getUpdatePolicy() == UpdatePolicy.UPDATE_ALL 
594 				|| (policy.getUpdatePolicy() == UpdatePolicy.UPDATE_CHOSEN 
595 					&& isInPolicy) 
596 				|| isNew || immutableValue(savedCollection)) {
597 				
598 				// First merge the entries of the new list
599 				mergedEntries = new ArrayList(updateCollection.size());
600 				for (Object updatedObject : updateCollection) {
601 					Object anchorObject = null;
602 					if (policy.getCollectionEntryMapping() != null) {
603 						anchorObject = policy.getCollectionEntryMapping().get(updatedObject);
604 					}
605 					mergedEntries.add(merge(anchorObject, updatedObject, policy, 
606 						anchorObject != null && identical, reached, locked));
607 				}
608 				if (needsAdditionalProcessing(updateCollection)) {
609 					// check if this collection was already reverseMerged
610 					Collection<?> restoreCollection = m_reverseCollectionMapping.get(savedCollection);
611 					if (restoreCollection != null) {
612 						m_reverseCollectionMapping.remove(savedCollection);
613 						referenceHolder = (T) restoreCollection;
614 						savedCollection = restoreCollection;
615 						
616 					}
617 					if (savedCollection != updateCollection) {
618 						// new collection pair to store mapping of
619 						m_collectionMapping.put(savedCollection, updateCollection);
620 					}
621 					
622 				}
623 				if (immutableValue(savedCollection)) {
624 					// take the new collection as container, since the old is not usable
625 					referenceHolder = (T) updateCollection;
626 					savedCollection = updateCollection;
627 				}
628 				savedCollection.clear();
629 				savedCollection.addAll(mergedEntries);
630 				
631 			} else {
632 				mergedEntries = new ArrayList(savedCollection.size());
633 				for (Object updatedObject : savedCollection) {
634 					Object anchorObject = null;
635 					if (policy.getCollectionEntryMapping() != null) {
636 						anchorObject = policy.getCollectionEntryMapping().get(updatedObject);
637 					}
638 					mergedEntries.add(merge(anchorObject, updatedObject, policy, 
639 						anchorObject != null && identical, reached, locked));
640 				}
641 				savedCollection.clear();
642 				savedCollection.addAll(mergedEntries);
643 			}
644 			
645 		} else {
646 			boolean isUpdateNeeded = policy.getUpdatePolicy() == UpdatePolicy.UPDATE_ALL 
647 				|| (policy.getUpdatePolicy() == UpdatePolicy.UPDATE_CHOSEN 
648 					&& policy.getObjectsToUpdate().contains(referenceHolder))
649 				|| isNew;
650 			for (Field f : fields(valueHolder.getClass())) {
651 				try {
652 					Object fieldValueNew = f.get(valueHolder);
653 					Object fieldValueOld = f.get(referenceHolder);
654 					boolean collectionUpdate = fieldValueOld != null && fieldValueOld instanceof Collection 
655 						&& id(fieldValueOld) == ANONYMOUS;
656 					if (policy.getUpdatePolicy() == UpdatePolicy.UPDATE_CHOSEN && collectionUpdate) {
657 						policy.getObjectsToUpdate().add(fieldValueOld);
658 					}
659 					boolean updateAnyway = false;
660 					if (collectionUpdate && needsAdditionalProcessing(fieldValueNew)) {
661 						// check for a hint object to replace list again
662 						IdentityFixerCollectionField idcf = new IdentityFixerCollectionField(referenceHolder, f);
663 						Collection<?> replaceCollection = m_collectionsToBeReplaced.get(idcf);
664 						if (replaceCollection != null) {
665 							updateAnyway = true;
666 							fieldValueOld = replaceCollection;
667 							m_collectionsToBeReplaced.remove(idcf);
668 						}
669 					}
670 					if (collectionUpdate && immutableValue(fieldValueOld)) {
671 						updateAnyway = true;
672 					}
673 					Object merged = merge(
674 						fieldValueOld,
675 						fieldValueNew,
676 						policy,
677 						fieldValueOld != null && identical,
678 						reached,
679 						locked
680 					);
681 					// If objectsToUpdate is specified, perform overwrite only on entities
682 					// and objects stored in objectsToUpdate, not on values.
683 					// This is important when we reload an entity from database where not all
684 					// references are loaded (lazy loading). Then we don't want to overwrite
685 					// valid local references with not loaded (=null) references
686 					if (isUpdateNeeded || updateAnyway) {
687 						
688 						f.set(referenceHolder, merged);
689 					}
690 				} catch (IllegalAccessException e) { assert false : e; }
691 			}
692 		}
693 		m_traceIndentation--;
694 
695 		// notify state change
696 		NewEntityState e = new NewEntityState();
697 		e.setChangee(referenceHolder);
698 		m_changeNotifier.announce(e);
699 		
700 		return referenceHolder;
701 	}
702 	
703 	/**
704 	 * Updates the unique representative by duplicating the state in
705 	 * {@code updated}. If no representative exists so far, one is created.
706 	 *
707 	 * <p>For every potentially modified entity, {@link NewEntityState}
708 	 * notification are sent using the configured change notifier.
709 	 *
710 	 * @param anchor
711 	 *              the representative known to be transitively identical
712 	 *              with {@code updated}, or null, if the representative's
713 	 *              logical identity is already defined.
714 	 * @param updated
715 	 *              The object holding the new state.
716 	 * @return The representative.
717 	 */
718 	public <T> T merge(T anchor, T updated) {
719 		HashMap<Object, Object> locked = new HashMap<Object, Object>();
720 		if (updated instanceof Collection) {
721 			for (Object o : (Collection<?>) updated) {
722 				Object id = id(o);
723 				if (id != null && id != ANONYMOUS) {
724 					locked.put(id, o);
725 				}
726 			}
727 		}
728 		T result = merge(
729 			anchor,
730 			updated,
731 			IdentityFixerMergePolicy.reloadAllPolicy(),
732 			anchor != null,
733 			new IdentityHashMap<Object, Object>(),
734 			locked
735 		);
736 		return result;
737 	}
738 	
739 	/**
740 	 * Updates the set of unique representatives according to the {@link IdentityFixerMergePolicy}.
741 	 * If no representative exists of an object contained by the graph of objects in {@code updated}
742 	 * so far, one is created.
743 	 *
744 	 * <p>For every potentially modified entity, {@link NewEntityState}
745 	 * notification are sent using the configured change notifier.
746 	 *
747 	 * @param anchor
748 	 *              the representative known to be transitively identical
749 	 *              with {@code updated}, or null, if the representative's
750 	 *              logical identity is already defined.
751 	 * @param updated
752 	 *              The object holding the new state.
753 	 * @param policy
754 	 *              The policy how to merge the representatives.
755 	 * @return The representative.
756 	 * 
757 	 * @see IdentityFixerMergePolicy
758 	 */
759 	public <T> T merge(T anchor, T updated, IdentityFixerMergePolicy policy) {
760 		HashMap<Object, Object> locked = new HashMap<Object, Object>();
761 		if (updated instanceof Collection) {
762 			for (Object o : (Collection<?>) updated) {
763 				Object id = id(o);
764 				if (id != null && id != ANONYMOUS) {
765 					locked.put(id, o);
766 				}
767 			}
768 		}
769 		T result = merge(
770 			anchor,
771 			updated,
772 			policy,
773 			anchor != null,
774 			new IdentityHashMap<Object, Object>(),
775 			locked
776 		);
777 		return result;
778 	}
779 	
780 	/**
781 	 * Prepare an object to be passed to an identity-mangling source.
782 	 * Using this method along with merge for incoming objects from the source,
783 	 * a 2-way merging for collections is guaranteed.
784 	 * 
785 	 * @param object
786 	 *              the object to be prepared.
787 	 * @param reached
788 	 *              the set of objects that have
789 	 *              been (or are being) reverseMerged. Used to avoid reverseMerging an
790 	 *              object more than once.
791 	 * @param mergeRecursive
792 	 *              if the graph of objects should be traversed recursively and prepare all objects.
793 	 * @return the prepared representative
794 	 */
795 	@SuppressWarnings("unchecked")
796 	protected Object reverseMerge(Object object, IdentityHashMap<Object, Object> reached, boolean mergeRecursive) {
797 		
798 		if (immutableValue(object)) {
799 			return object;
800 		}
801 		
802 		if (reached.get(object) == object) {
803 			return object;
804 		}
805 		
806 		if (mergeRecursive) {
807 			reached.put(object, object);
808 		}
809 		
810 		// check for collections in the fields
811 		for (Field f : fields(object.getClass())) {
812 			try {
813 				Object fieldValue = f.get(object);
814 				if (fieldValue instanceof Collection) {
815 					Collection fieldCollection = (Collection) fieldValue;
816 					Collection mappedCollection = m_collectionMapping.get(fieldCollection);
817 					if (mappedCollection != null && fieldCollection != mappedCollection) {
818 						// there exists already a mapping, fix it
819 						f.set(object, mappedCollection);
820 						List<Object> tmpList = new ArrayList<Object>(fieldCollection);
821 						mappedCollection.clear();
822 						mappedCollection.addAll(tmpList);
823 						m_reverseCollectionMapping.put(mappedCollection, fieldCollection);
824 					} else {
825 						// no mapping, add a hint object
826 						m_collectionsToBeReplaced.put(new IdentityFixerCollectionField(object, f), fieldCollection);
827 					}
828 					if (mergeRecursive) {
829 						for (Object o : fieldCollection) {
830 							reverseMerge(o, reached, mergeRecursive);
831 						}
832 					}
833 				} else if (mergeRecursive && fieldValue != null) {
834 					if (fieldValue.getClass().isArray()) {
835 						int l = Array.getLength(fieldValue);
836 						for (int i = 0; i < l; i++) {
837 							reverseMerge(Array.get(fieldValue, i), reached, mergeRecursive);
838 						}
839 					} else {
840 						reverseMerge(fieldValue, reached, mergeRecursive);
841 					}
842 				}
843 			} catch (IllegalAccessException e) { assert false : e; }
844 		}
845 		
846 		
847 		return object;
848 	}
849 	
850 	/**
851 	 * Prepare an object to be passed to an identity-mangling source.
852 	 * Using this method along with merge for incoming objects from the source,
853 	 * a 2-way merging for collections is guaranteed.
854 	 * 
855 	 * @param object           the object to be prepared.
856 	 * @param mergeRecursive   if the graph of objects should be traversed recursively and prepare all objects.
857 	 * @return the prepared representative
858 	 */
859 	public Object reverseMerge(Object object, boolean mergeRecursive) {
860 		return reverseMerge(object,
861 			new IdentityHashMap<Object, Object>(),
862 			mergeRecursive
863 		);
864 	}
865 	
866 	/**
867 	 * Prepare a list of objects to be passed to an identity-mangling source.
868 	 * Using this method along with merge for incoming objects from the source,
869 	 * a 2-way merging for collections is guaranteed.
870 	 * 
871 	 * @param objects           the list of objects to be prepared.
872 	 * @return the prepared representatives list.
873 	 */
874 	public List<Object> reverseMerge(List<Object> objects) {
875 		List<Object> returnList = new ArrayList<Object>(objects.size());
876 		for (Object o : objects) {
877 			returnList.add(reverseMerge(o, false));
878 		}
879 		return returnList;
880 	}
881 	
882 	/**
883 	 * @param object    the object to test
884 	 * @return          <code>true</code> if object is a representative.
885 	 */
886 	public boolean isRepresentative(Object object) {
887 		return m_representatives.containsValue(object);
888 	}
889 	
890 	/**
891 	 * @return a collection of all the representatives held by the identity fixer.	 
892 	 */	
893 	public Collection<?> getRepresentatives() {
894 		return m_representatives.values();
895 	}
896 	
897 	/**
898 	 * Remove an object from the representatives.
899 	 * @param object  the object to remove
900 	 */
901 	public void removeRepresentative(Object object) {
902 		Object id = id(object);
903 		if (m_representatives.get(id) != null && m_representatives.get(id).equals(object)) {
904 			// TODO: remove collection mappings from this representative, elegant solution??
905 			m_collectionsToBeReplaced.remove(id);
906 			m_representatives.remove(id);
907 		}
908 	}
909 	
910 	/**
911 	 * Returns the globally unique, logical id for the provided object, or
912 	 * {@code null}, if it has no id (yet), or {@link #ANONYMOUS} is this
913 	 * object is of value type. {@code o} may be null, point to an ordinary
914 	 * object or to an array.
915 	 * <p>
916 	 * The ID objects returned by this method must be value-comparable using
917 	 * {@code equals} (which implies that hashCode must be overridden as well).
918 	 * To permit garbage-collection, ids referring to the object they identify
919 	 * should do so with weak references.
920 	 *
921 	 * @param o
922 	 *            The object for which a globally unique, logical id will be
923 	 *            returned
924 	 * @return A globally unique, logical ID object for the given object
925 	 */
926 	// Keys become eligible for collection shortly after the object they
927 	// identify becomes is weakly reachable (because the latter triggers removal
928 	// from m_representatives.
929 	protected abstract Object id(Object o);
930 	
931 	/**
932 	 * Returns whether the given reference represents an immutable value, either
933 	 * because it really is a value ({@code null}) or because the referenced
934 	 * object's identity is not accessed and its state is not modified.
935 	 * {@code o} may be null, point to an ordinary object or to an array.
936 	 *
937 	 * @param o
938 	 *            The concerned object
939 	 * @return <code>True</code> if the given object represents an immutable
940 	 *         value, <code>false</code> otherwise
941 	 */
942 	protected abstract boolean immutableValue(Object o);
943 	
944 	/**
945 	 * Returns the prepared Object, is called before checked for immutability
946 	 * to give the id fixer the chance to convert immutable values to usable ones.
947 	 *
948 	 * @param o
949 	 *            The concerned object
950 	 * @return The prepared object.
951 	 */
952 	protected abstract Object prepareObject(Object o);
953 	
954 	/**
955 	 * @param o
956 	 *            The concerned object.
957 	 * @return if the object needs additional processing during a {@link AbstractIdentityFixer#merge}.
958 	 */
959 	protected boolean needsAdditionalProcessing(Object o) {
960 		return false;
961 	}
962 	
963 	/*
964 	 * Warning: The ReturnsUnchangedParameter annotation is not always found
965 	 * if a DAO is wrapped or proxied and the wrapper does not declare the 
966 	 * annotation again. This can cause bugs in the identity fixer algorithm.
967 	 * 
968 	 * As a guideline, all DAO save* methods must have the annotation present. 
969 	 */
970 	
971 	/**
972 	 * A generic "around advice" (as defined in AOP terminology) for remote
973 	 * objects. This interceptor works "out of the box" unless incoming objects
974 	 * have unknown logical identity. If this occurs, it attempts to infer
975 	 * logical identity from {@link ReturnsUnchangedParameter} annotations.
976 	 */
977 	public class GenericInterceptor
978 			extends IntroductionInfoSupport
979 			implements IntroductionInterceptor {
980 
981 		/**
982 		 * Constructor.
983 		 * @param fixedInterface The marker-interface to be "introduced".
984 		 */
985 		@SuppressWarnings("unchecked")
986 		public GenericInterceptor(Class<?> fixedInterface) {
987 			publishedInterfaces.add(fixedInterface);
988 		}
989 		
990 		/** {@inheritDoc} */
991 		@SuppressWarnings("unchecked")
992 		public Object invoke(MethodInvocation invocation) throws Throwable {
993 			ReturnsUnchangedParameter rp
994 				= invocation.getMethod().getAnnotation(
995 					ReturnsUnchangedParameter.class
996 				);
997 			if (rp != null) {
998 				Object arg = invocation.getArguments()[rp.value()];
999 				if (arg instanceof List) {
1000 					reverseMerge((List) arg);
1001 				} else {
1002 					reverseMerge(arg, true);
1003 				}
1004 				Object result = invocation.proceed();
1005 				return merge(arg, result);
1006 			} else {
1007 				Object result = invocation.proceed();
1008 				return merge(null, result);
1009 			}
1010 		}
1011 
1012 		/**
1013 		 * Convenience method returning a proxy to the supplied object
1014 		 * that implements this "advice".
1015 		 * @param o .
1016 		 * @return .
1017 		 */
1018 		public Object decorate(Object o) {
1019 			return AopHelper.addAdvice(o, this);
1020 		}
1021 	}
1022 	
1023 	
1024 	////////////
1025 	// Tracing
1026 	////////////
1027 	
1028 	/**
1029 	 * Logs a trace message. The arguments are assembled on a single line. It
1030 	 * is assumed that arguments with even index are strings to be printed, and
1031 	 * arguments with odd index are objects whose class and identity should be
1032 	 * inserted.
1033 	 * @param os .
1034 	 */
1035 	void trace(Object... os) {
1036 		if (s_logger.isDebugEnabled()) {
1037 			StringBuilder sb = new StringBuilder();
1038 			for (int i = 0; i < m_traceIndentation; i++) {
1039 				sb.append("    ");
1040 			}
1041 			boolean isLiteral = false;
1042 			for (Object o : os) {
1043 				isLiteral = o instanceof String && !isLiteral;
1044 				if (isLiteral) {
1045 					sb.append(o);
1046 				} else {
1047 					s_oi.format(o, sb);
1048 				}
1049 			}
1050 			s_logger.debug(sb.toString());
1051 		}
1052 	}
1053 
1054 	/**
1055 	 * For tracing. Assigns names to traced objects to identify them in
1056 	 * trace output.
1057 	 */
1058 	private static class ObjectIdentifier {
1059 		/** The next id to be assigned. */
1060 		int m_maxid = 0;
1061 		
1062 		/** Maps every already encountered object to its id. */
1063 		IdentityHashMap<Object, Integer> m_seen
1064 			= new IdentityHashMap<Object, Integer>();
1065 		
1066 		/**
1067 		 * Prints {code o}'s class and id to {@code toAppendTo}.
1068 		 *
1069 		 * @param obj
1070 		 *            The concerned object
1071 		 * @param toAppendTo
1072 		 *            The StringBuilder to which the object's class and id will
1073 		 *            be printed
1074 		 * @return {@code toAppendTo} (for call chaining)
1075 		 */
1076 		public StringBuilder format(Object obj, StringBuilder toAppendTo) {
1077 			if (obj == null) {
1078 				return toAppendTo.append("null");
1079 			} else {
1080 				Integer id = m_seen.get(obj);
1081 				if (id == null) {
1082 					id = m_maxid++;
1083 					m_seen.put(obj, id);
1084 				}
1085 				return toAppendTo.append(obj.getClass().getSimpleName())
1086 					.append(id.toString());
1087 			}
1088 		}
1089 	}
1090 }