001 /*
002 GRANITE DATA SERVICES
003 Copyright (C) 2012 GRANITE DATA SERVICES S.A.S.
004
005 This file is part of Granite Data Services.
006
007 Granite Data Services is free software; you can redistribute it and/or modify
008 it under the terms of the GNU Library General Public License as published by
009 the Free Software Foundation; either version 2 of the License, or (at your
010 option) any later version.
011
012 Granite Data Services is distributed in the hope that it will be useful, but
013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015 for more details.
016
017 You should have received a copy of the GNU Library General Public License
018 along with this library; if not, see <http://www.gnu.org/licenses/>.
019 */
020
021 package org.granite.client.tide.data.impl;
022
023 import java.lang.reflect.Array;
024 import java.util.ArrayList;
025 import java.util.Collection;
026 import java.util.Collections;
027 import java.util.HashSet;
028 import java.util.IdentityHashMap;
029 import java.util.Iterator;
030 import java.util.List;
031 import java.util.Map;
032 import java.util.Map.Entry;
033 import java.util.Set;
034
035 import org.granite.client.persistence.LazyableCollection;
036 import org.granite.client.tide.Context;
037 import org.granite.client.tide.SyncMode;
038 import org.granite.client.tide.collections.ManagedPersistentAssociation;
039 import org.granite.client.tide.collections.ManagedPersistentCollection;
040 import org.granite.client.tide.collections.ManagedPersistentMap;
041 import org.granite.client.tide.data.Conflict;
042 import org.granite.client.tide.data.DataConflictListener;
043 import org.granite.client.tide.data.DataMerger;
044 import org.granite.client.tide.data.EntityManager;
045 import org.granite.client.tide.data.EntityProxy;
046 import org.granite.client.tide.data.Identifiable;
047 import org.granite.client.tide.data.Lazyable;
048 import org.granite.client.tide.data.PersistenceManager;
049 import org.granite.client.tide.data.RemoteInitializer;
050 import org.granite.client.tide.data.RemoteValidator;
051 import org.granite.client.tide.data.impl.UIDWeakSet.Matcher;
052 import org.granite.client.tide.data.impl.UIDWeakSet.Operation;
053 import org.granite.client.tide.data.spi.DataManager;
054 import org.granite.client.tide.data.spi.DataManager.ChangeKind;
055 import org.granite.client.tide.data.spi.DirtyCheckContext;
056 import org.granite.client.tide.data.spi.EntityDescriptor;
057 import org.granite.client.tide.data.spi.EntityRef;
058 import org.granite.client.tide.data.spi.ExpressionEvaluator;
059 import org.granite.client.tide.data.spi.ExpressionEvaluator.Value;
060 import org.granite.client.tide.data.spi.MergeContext;
061 import org.granite.client.tide.server.Component;
062 import org.granite.client.tide.server.ServerSession;
063 import org.granite.client.tide.server.TrackingContext;
064 import org.granite.client.util.WeakIdentityHashMap;
065 import org.granite.logging.Logger;
066 import org.granite.tide.Expression;
067 import org.granite.util.TypeUtil;
068 import org.granite.util.UUIDUtil;
069
070 /**
071 * @author William DRAI
072 */
073 public class EntityManagerImpl implements EntityManager {
074
075 private static final Logger log = Logger.getLogger(EntityManagerImpl.class);
076
077 private String id;
078 private boolean active = false;
079 private ExpressionEvaluator expressionEvaluator = null;
080 private DataManager dataManager = null;
081 private TrackingContext trackingContext = null;
082 private DirtyCheckContext dirtyCheckContext = null;
083 private UIDWeakSet entitiesByUid = new UIDWeakSet();
084 private WeakIdentityHashMap<Object, List<Object>> entityReferences = new WeakIdentityHashMap<Object, List<Object>>();
085
086 private DataMerger[] customMergers = null;
087
088
089 public EntityManagerImpl(String id, DataManager dataManager, TrackingContext trackingContext, ExpressionEvaluator expressionEvaluator) {
090 this.id = id;
091 this.active = true;
092 this.dataManager = dataManager != null ? dataManager : new JavaBeanDataManager();
093 this.dataManager.setTrackingHandler(new DefaultTrackingHandler());
094 this.trackingContext = trackingContext != null ? trackingContext : new TrackingContext();
095 this.dirtyCheckContext = new DirtyCheckContextImpl(this.dataManager, this.trackingContext);
096 this.expressionEvaluator = expressionEvaluator;
097 }
098
099
100 /**
101 * Return the entity manager id
102 *
103 * @return the entity manager id
104 */
105 public String getId() {
106 return id;
107 }
108
109 /**
110 * {@inheritdoc}
111 */
112 public boolean isActive() {
113 return active;
114 }
115
116 /**
117 * Clear the current context
118 * Destroys all components/context variables
119 */
120 public void clear() {
121 entitiesByUid.apply(new Operation() {
122 @Override
123 public void apply(Object o) {
124 PersistenceManager.setEntityManager(o, null);
125 }
126 });
127 entitiesByUid.clear();
128 entityReferences.clear();
129 dirtyCheckContext.clear(false);
130 dataManager.clear();
131 trackingContext.clear();
132 active = true;
133 }
134
135 /**
136 * Clears entity cache
137 */
138 public void clearCache() {
139 // _mergeContext.clear();
140 }
141
142
143 public DataManager getDataManager() {
144 return dataManager;
145 }
146
147
148 /**
149 * Setter for the array of custom mergers
150 *
151 * @param customMergers array of mergers
152 */
153 public void setCustomMergers(DataMerger[] customMergers) {
154 if (customMergers != null && customMergers.length > 0)
155 this.customMergers = customMergers;
156 else
157 this.customMergers = null;
158 }
159
160
161 private boolean uninitializeAllowed = true;
162
163 @Override
164 public void setUninitializeAllowed(boolean uninitializeAllowed) {
165 this.uninitializeAllowed = uninitializeAllowed;
166 }
167
168 @Override
169 public boolean isUninitializeAllowed() {
170 return uninitializeAllowed;
171 }
172
173
174 private Propagation entityManagerPropagation = null;
175
176 /**
177 * Setter for the propagation manager
178 *
179 * @param propagation propagation function that will visit child entity managers
180 */
181 public void setEntityManagerPropagation(Propagation propagation) {
182 this.entityManagerPropagation = propagation;
183 }
184
185 /**
186 * Setter for active flag
187 * When EntityManager is not active, dirty checking is disabled
188 *
189 * @param active state
190 */
191 public void setActive(boolean active) {
192 this.active = active;
193 }
194
195 /**
196 * Setter for dirty check context implementation
197 *
198 * @param dirtyCheckContext dirty check context implementation
199 */
200 public void setDirtyCheckContext(DirtyCheckContext dirtyCheckContext) {
201 if (dirtyCheckContext == null)
202 throw new IllegalArgumentException("Dirty check context cannot be null");
203
204 // if (dirtyCheckContext != null)
205 // dirtyCheckContext.removeEventListener(DIRTY_CHANGE, dirtyChangeHandler);
206
207 this.dirtyCheckContext = dirtyCheckContext;
208 this.dirtyCheckContext.setTrackingContext(trackingContext);
209 // this.dirtyCheckContext.addEventListener(DIRTY_CHANGE, dirtyChangeHandler, false, 0, true);
210
211 // _mergeContext = new MergeContext(this, dirtyCheckContext);
212 }
213
214
215 private static int tmpEntityManagerId = 1;
216
217 /**
218 * Create a new temporary entity manager
219 */
220 public EntityManager newTemporaryEntityManager() {
221 return new EntityManagerImpl("$$TMP$$" + (tmpEntityManagerId++), dataManager, trackingContext, expressionEvaluator);
222 }
223 //
224 // /**
225 // * @private
226 // * Allow uninitialize of persistent collections
227 // *
228 // * @param allowed allow uninitialize of collections
229 // */
230 // public void setUninitializeAllowed(boolean allowed) {
231 // _mergeContext.uninitializeAllowed = allowed;
232 // }
233 //
234 // /**
235 // * @private
236 // * @return allow uninitialize of collections
237 // */
238 // public function get uninitializeAllowed():Boolean {
239 // return _mergeContext.uninitializeAllowed;
240 // }
241 //
242 // /**
243 // * @private
244 // * Force uninitialize of persistent collections
245 // *
246 // * @param uninitializing force uninitializing of collections during merge
247 // */
248 // public function set uninitializing(uninitializing:Boolean):void {
249 // _mergeContext.uninitializing = uninitializing;
250 // }
251
252
253 // /**
254 // * Entity manager is dirty when any entity/collection/map has been modified
255 // *
256 // * @return is dirty
257 // */
258 // [Bindable(event="dirtyChange")]
259 // public function get dirty():Boolean {
260 // return _dirtyCheckContext.dirty;
261 // }
262 //
263 // /**
264 // * Internal handler for dirty flag changes. Redispaches event from the dirty check context
265 // *
266 // * @param event dirty change event
267 // */
268 // private function dirtyChangeHandler(event:PropertyChangeEvent):void {
269 // dispatchEvent(event);
270 // }
271 //
272 //
273 // /**
274 // * List of conflicts detected during last merge operation
275 // *
276 // * @return conflicts list
277 // */
278 // public Conflicts getMergeConflicts() {
279 // return _mergeContext.mergeConflicts;
280 // }
281
282
283 /**
284 * @private
285 * Attach an entity to this context
286 *
287 * @param entity an entity
288 */
289 public void attachEntity(Identifiable entity) {
290 attachEntity(entity, true);
291 }
292
293 /**
294 * @private
295 * Attach an entity to this context
296 *
297 * @param entity an entity
298 * @param putInCache put entity in cache
299 */
300 public void attachEntity(Identifiable entity, boolean putInCache) {
301 EntityManager em = PersistenceManager.getEntityManager(entity);
302 if (em != null && em != this && !em.isActive()) {
303 throw new Error("The entity instance " + entity
304 + " cannot be attached to two contexts (current: " + em.getId()
305 + ", new: " + id + ")");
306 }
307
308 PersistenceManager.setEntityManager(entity, this);
309 if (putInCache) {
310 getUid(entity);
311 if (entitiesByUid.put(entity) == null)
312 dirtyCheckContext.addUnsaved(entity);
313 }
314 }
315
316
317 /**
318 * @private
319 * Detach an entity from this context only if it's not persistent
320 *
321 * @param entity an entity
322 * @param removeFromCache remove entity from cache
323 */
324 public void detachEntity(Identifiable entity, boolean removeFromCache, boolean forceRemove) {
325 if (!forceRemove) {
326 String versionPropName = dataManager.getEntityDescriptor(entity).getVersionPropertyName();
327 if (versionPropName == null || dataManager.getProperty(entity, versionPropName) != null)
328 return;
329 }
330
331 dirtyCheckContext.markNotDirty(entity, entity);
332
333 PersistenceManager.setEntityManager(entity, null);
334 if (removeFromCache)
335 entitiesByUid.remove(entity.getClass().getName() + ":" + entity.getUid());
336 }
337
338
339 /**
340 * {@inheritdoc}
341 */
342 public boolean isSaved(Identifiable entity) {
343 EntityDescriptor desc = dataManager.getEntityDescriptor(entity);
344 if (desc.getVersionPropertyName() != null && dataManager.getProperty(entity, desc.getVersionPropertyName()) != null)
345 return true;
346 return false;
347 }
348
349
350 /**
351 * @private
352 * Internal implementation of object detach
353 *
354 * @param object object
355 * @param cache internal cache to avoid graph loops
356 */
357 public void detach(Object object, IdentityHashMap<Object, Object> cache, boolean forceRemove) {
358 if (object == null || ObjectUtil.isSimple(object))
359 return;
360
361 if (cache.containsKey(object))
362 return;
363 cache.put(object, object);
364
365 List<String> excludes = new ArrayList<String>();
366 excludes.add("uid");
367
368 if (object instanceof Identifiable) {
369 EntityDescriptor desc = dataManager.getEntityDescriptor(object);
370 if (desc.getIdPropertyName() != null)
371 excludes.add(desc.getIdPropertyName());
372 if (desc.getVersionPropertyName() != null)
373 excludes.add(desc.getVersionPropertyName());
374 }
375
376 Map<String, Object> values = dataManager.getPropertyValues(object, excludes, false, false);
377
378 if (object instanceof Identifiable && entityReferences.containsKey(object)) {
379 detachEntity((Identifiable)object, true, forceRemove);
380
381 for (Entry<String, Object> me : values.entrySet())
382 removeReference(me.getValue(), object, me.getKey(), null);
383 }
384
385 for (Entry<String, Object> me : values.entrySet()) {
386 Object val = me.getValue();
387
388 if (val instanceof Collection && !(val instanceof LazyableCollection && !((LazyableCollection)val).isInitialized())) {
389 Collection<?> coll = (Collection<?>)val;
390 for (Object o : coll)
391 detach(o, cache, forceRemove);
392 }
393 else if (val instanceof Map<?, ?> && !(val instanceof LazyableCollection && !((LazyableCollection)val).isInitialized())) {
394 Map<?, ?> map = (Map<?, ?>)val;
395 for (Entry<?, ?> entry : map.entrySet()) {
396 detach(entry.getKey(), cache, forceRemove);
397 detach(entry.getValue(), cache, forceRemove);
398 }
399 }
400 else if (val != null && !ObjectUtil.isSimple(val)) {
401 detach(val, cache, forceRemove);
402 }
403 }
404 }
405
406 /**
407 * @private
408 * Retrives an entity in the cache from its uid
409 *
410 * @param object an entity
411 * @param nullIfAbsent return null if entity not cached in context
412 */
413 public Object getCachedObject(Object object, boolean nullIfAbsent) {
414 Object entity = null;
415 if (object instanceof Identifiable) {
416 entity = entitiesByUid.get(object.getClass().getName() + ":" + getUid((Identifiable)object));
417 }
418 else if (object instanceof EntityRef) {
419 entity = entitiesByUid.get(((EntityRef)object).getClassName() + ":" + ((EntityRef)object).getUid());
420 }
421 else if (object instanceof String) {
422 entity = entitiesByUid.get((String)object);
423 }
424
425 if (entity != null)
426 return entity;
427 if (nullIfAbsent)
428 return null;
429
430 return object;
431 }
432
433 /**
434 * @private
435 * Retrives the owner entity of the provided object (collection/map/entity)
436 *
437 * @param object an entity
438 */
439 public Object[] getOwnerEntity(Object object) {
440 List<Object> refs = entityReferences.get(object);
441 if (refs == null)
442 return null;
443
444 for (int i = 0; i < refs.size(); i++) {
445 if (refs.get(i) instanceof Object[] && ((Object[])refs.get(i))[0] instanceof String)
446 return new Object[] { entitiesByUid.get((String)((Object[])refs.get(i))[0]), ((Object[])refs.get(i))[1] };
447 }
448 return null;
449 }
450
451 /**
452 * @private
453 * Retrives the owner entity of the provided object (collection/map/entity)
454 *
455 * @param object an entity
456 */
457 public List<Object[]> getOwnerEntities(Object object) {
458 List<Object> refs = entityReferences.get(object);
459 if (refs == null)
460 return null;
461
462 List<Object[]> owners = new ArrayList<Object[]>();
463 for (int i = 0; i < refs.size(); i++) {
464 if (refs.get(i) instanceof Object[] && ((Object[])refs.get(i))[0] instanceof String) {
465 Object owner = entitiesByUid.get((String)((Object[])refs.get(i))[0]);
466 if (owner != null) // May have been garbage collected
467 owners.add(new Object[] { owner, ((Object[])refs.get(i))[1] });
468 }
469 }
470 return owners;
471 }
472
473
474 /**
475 * {@inheritdoc}
476 */
477 public Expression getReference(Object obj, boolean recurse, Set<Object> cache) {
478 if (cache != null) {
479 if (cache.contains(obj)) // We are in a graph loop, no reference can be found from this path
480 return null;
481 cache.add(obj);
482 }
483 else if (recurse)
484 throw new Error("Cache must be provided to get reference recursively");
485
486 List<Object> refs = entityReferences.get(obj);
487 if (refs == null)
488 return null;
489
490 for (int i = 0; i < refs.size(); i++) {
491 // Return first context expression reference that is remote enabled
492 if (refs.get(i) instanceof Expression && expressionEvaluator != null && expressionEvaluator.getRemoteSync(refs.get(i)) != SyncMode.NONE)
493 return (Expression)refs.get(i);
494 }
495
496 if (recurse) {
497 Object ref = null;
498 for (int i = 0; i < refs.size(); i++) {
499 if (refs.get(i) instanceof Object[] && ((Object[])refs.get(i))[0] instanceof String) {
500 ref = entitiesByUid.get((String)((Object[])refs.get(i))[0]);
501 if (ref != null) {
502 ref = getReference(ref, recurse, cache);
503 if (ref != null)
504 return (Expression)ref;
505 }
506 }
507 else if (refs.get(i) instanceof Object[] && !(refs.get(i) instanceof Expression)) {
508 ref = ((Object[])refs.get(i))[0];
509 if (ref != null) {
510 ref = getReference(ref, recurse, cache);
511 if (ref != null)
512 return (Expression)ref;
513 }
514 }
515 }
516 }
517 return null;
518 }
519
520 /**
521 * @private
522 * Init references array for an object
523 *
524 * @param obj an entity
525 */
526 private List<Object> initRefs(Object obj) {
527 List<Object> refs = entityReferences.get(obj);
528 if (refs == null) {
529 refs = new ArrayList<Object>();
530 entityReferences.put(obj, refs);
531 }
532 return refs;
533 }
534
535
536 /**
537 * Registers a reference to the provided object with either a parent or res
538 *
539 * @param obj an entity
540 * @param parent the parent entity
541 * @param propName name of the parent entity property that references the entity
542 * @param res the context expression
543 */
544 public void addReference(Object obj, Object parent, String propName, Expression res) {
545 if (obj instanceof Identifiable)
546 attachEntity((Identifiable)obj);
547
548 dataManager.startTracking(obj, parent);
549
550 if (obj instanceof ManagedPersistentAssociation)
551 obj = ((ManagedPersistentAssociation)obj).getCollection();
552
553 List<Object> refs = entityReferences.get(obj);
554 if (!(obj instanceof LazyableCollection) && res != null) {
555 refs = initRefs(obj);
556 boolean found = false;
557 for (int i = 0; i < refs.size(); i++) {
558 if (!(refs.get(i) instanceof Expression))
559 continue;
560 Expression r = (Expression)refs.get(i);
561 if (r.getComponentName().equals(res.getComponentName())
562 && ((r.getExpression() == null && res.getExpression() == null) || (r.getExpression() != null && r.getExpression().equals(res.getExpression())))) {
563 found = true;
564 break;
565 }
566 }
567 if (!found)
568 refs.add(res);
569 }
570 boolean found = false;
571 if (parent instanceof Identifiable) {
572 String ref = parent.getClass().getName() + ":" + ((Identifiable)parent).getUid();
573 if (refs == null)
574 refs = initRefs(obj);
575 else {
576 for (int i = 0; i < refs.size(); i++) {
577 if (refs.get(i) instanceof Object[] && ((Object[])refs.get(i))[0].equals(ref)) {
578 found = true;
579 break;
580 }
581 }
582 }
583 if (!found)
584 refs.add(new Object[] { ref, propName });
585 }
586 else if (parent != null) {
587 if (refs == null)
588 refs = initRefs(obj);
589 else {
590 for (int i = 0; i < refs.size(); i++) {
591 if (refs.get(i) instanceof Object[] && ((Object[])refs.get(i))[0].equals(parent)) {
592 found = true;
593 break;
594 }
595 }
596 }
597 if (!found)
598 refs.add(new Object[] { parent, propName });
599 }
600 }
601
602 /**
603 * Removes a reference on the provided object
604 *
605 * @param obj an entity
606 * @param parent the parent entity to dereference
607 * @param propName name of the parent entity property that references the entity
608 * @param res expression to remove
609 */
610 public boolean removeReference(Object obj, Object parent, String propName, Expression res) {
611 if (obj instanceof ManagedPersistentAssociation)
612 obj = ((ManagedPersistentAssociation)obj).getCollection();
613
614 List<Object> refs = entityReferences.get(obj);
615 if (refs == null)
616 return true;
617
618 int idx = -1;
619 if (parent instanceof Identifiable) {
620 for (int i = 0; i < refs.size(); i++) {
621 if (refs.get(i) instanceof Object[] && ((Object[])refs.get(i))[0].equals(parent.getClass().getName() + ":" + ((Identifiable)parent).getUid())) {
622 idx = i;
623 break;
624 }
625 }
626 }
627 else if (parent != null) {
628 for (int i = 0; i < refs.size(); i++) {
629 if (refs.get(i) instanceof Object[] && ((Object[])refs.get(i))[0].equals(parent)) {
630 idx = i;
631 break;
632 }
633 }
634 }
635 else if (res != null) {
636 for (int i = 0; i < refs.size(); i++) {
637 if (refs.get(i) instanceof Expression && ((Expression)refs.get(i)).getPath().equals(res.getPath())) {
638 idx = i;
639 break;
640 }
641 }
642 }
643 if (idx >= 0)
644 refs.remove(idx);
645
646 boolean removed = false;
647 if (refs.size() == 0) {
648 entityReferences.remove(obj);
649 removed = true;
650
651 if (obj instanceof Identifiable)
652 detachEntity((Identifiable)obj, true, false);
653
654 dataManager.stopTracking(obj, parent);
655 }
656
657 if (obj instanceof LazyableCollection && !((LazyableCollection)obj).isInitialized())
658 return removed;
659
660 if (obj instanceof Iterable<?>) {
661 for (Object elt : (Iterable<?>)obj)
662 removeReference(elt, parent, propName, null);
663 }
664 else if (obj != null && obj.getClass().isArray()) {
665 for (int i = 0; i < Array.getLength(obj); i++)
666 removeReference(Array.get(obj, i), parent, propName, null);
667 }
668 else if (obj instanceof Map<?, ?>) {
669 for (Entry<?, ?> me : ((Map<?, ?>)obj).entrySet()) {
670 removeReference(me.getKey(), parent, propName, null);
671 removeReference(me.getValue(), parent, propName, null);
672 }
673 }
674
675 return removed;
676 }
677
678
679 public MergeContext initMerge() {
680 return new MergeContext(this, dirtyCheckContext, null);
681 }
682
683 /**
684 * Merge an object coming from the server in the context
685 *
686 * @param obj external object
687 * @param previous previously existing object in the context (null if no existing object)
688 * @param expr current path from the context
689 * @param parent parent object for collections
690 * @param propertyName property name of the current object in the parent object
691 * @param setter setter function to update the private property
692 * @param forceUpdate force update of property (used for externalized properties)
693 *
694 * @return merged object (should === previous when previous not null)
695 */
696 @SuppressWarnings("unchecked")
697 public Object mergeExternal(final MergeContext mergeContext, Object obj, Object previous, Expression expr, Object parent, String propertyName, String setter, boolean forceUpdate) {
698
699 mergeContext.initMerge();
700
701 boolean saveMergeUpdate = mergeContext.isMergeUpdate();
702 boolean saveMerging = mergeContext.isMerging();
703
704 try {
705 mergeContext.setMerging(true);
706 int stackSize = mergeContext.getMergeStackSize();
707
708 boolean addRef = false;
709 boolean fromCache = false;
710 Object prev = mergeContext.getFromCache(obj);
711 Object next = obj;
712 if (prev != null) {
713 next = prev;
714 fromCache = true;
715 }
716 else {
717 // Give a chance to intercept received value so we can apply changes on private values
718 Object currentMerge = mergeContext.getCurrentMerge();
719 if (currentMerge instanceof EntityProxy) {
720 if (!((EntityProxy)currentMerge).hasProperty(propertyName))
721 return previous;
722 next = obj = ((EntityProxy)currentMerge).getProperty(propertyName);
723 }
724
725 // Clear change tracking
726 dataManager.stopTracking(previous, parent);
727
728 if (obj == null) {
729 next = null;
730 }
731 else if (((obj instanceof LazyableCollection && !((LazyableCollection)obj).isInitialized())
732 || (obj instanceof LazyableCollection && !(previous instanceof LazyableCollection))) && parent instanceof Identifiable && propertyName != null) {
733 next = mergePersistentCollection(mergeContext, (LazyableCollection)obj, previous, null, (Identifiable)parent, propertyName);
734 addRef = true;
735 }
736 else if (obj instanceof List<?>) {
737 next = mergeCollection(mergeContext, (List<Object>)obj, previous, parent == null ? expr : null, parent, propertyName);
738 addRef = true;
739 }
740 else if (obj instanceof Map<?, ?>) {
741 next = mergeMap(mergeContext, (Map<Object, Object>)obj, previous, parent == null ? expr : null, parent, propertyName);
742 addRef = true;
743 }
744 // else if (obj instanceof Enum) {
745 // next = obj;
746 // }
747 else if (obj instanceof Identifiable) {
748 next = mergeEntity(mergeContext, obj, previous, expr, parent, propertyName);
749 addRef = true;
750 }
751 else {
752 boolean merged = false;
753 if (customMergers != null) {
754 for (DataMerger merger : customMergers) {
755 if (merger.accepts(obj)) {
756 next = merger.merge(mergeContext, obj, previous, parent == null ? expr : null, parent, propertyName);
757
758 // Keep notified of collection updates to notify the server at next remote call
759 dataManager.startTracking(previous, parent);
760 merged = true;
761 addRef = true;
762 }
763 }
764 }
765 if (!merged && !ObjectUtil.isSimple(obj) && !(obj instanceof Enum || obj instanceof Value || obj instanceof byte[])) {
766 next = mergeEntity(mergeContext, obj, previous, expr, parent, propertyName);
767 addRef = true;
768 }
769 }
770 }
771
772 if (next != null && !fromCache && addRef
773 && (expr != null || (prev == null && parent != null))) {
774 // Store reference from current object to its parent entity or root component expression
775 // If it comes from the cache, we are probably in a circular graph
776 addReference(next, parent, propertyName, expr);
777 }
778
779 mergeContext.setMergeUpdate(saveMergeUpdate);
780
781 if ((mergeContext.isMergeUpdate() || forceUpdate) && setter != null && parent != null && propertyName != null && parent instanceof Identifiable && next != previous) {
782 if (!mergeContext.isResolvingConflict() || !propertyName.equals(dataManager.getEntityDescriptor(parent).getVersionPropertyName())) {
783 // dataManager.setInternalProperty(parent, propertyName, next);
784 dataManager.setProperty(parent, propertyName, previous, next);
785 }
786 }
787
788 if (entityManagerPropagation != null && (mergeContext.isMergeUpdate() || forceUpdate) && !fromCache && obj instanceof Identifiable) {
789 // Propagate to existing conversation contexts where the entity is present
790 entityManagerPropagation.propagate((Identifiable)obj, new Function() {
791 public void execute(EntityManager entityManager, Identifiable entity) {
792 if (entityManager == mergeContext.getSourceEntityManager())
793 return;
794 if (entityManager.getCachedObject(entity, true) != null)
795 entityManager.mergeFromEntityManager(entityManager, entity, mergeContext.getExternalDataSessionId(), mergeContext.isUninitializing());
796 }
797 });
798 }
799
800 if (mergeContext.getMergeStackSize() > stackSize)
801 mergeContext.popMerge();
802
803 return next;
804 }
805 catch (Exception e) {
806 log.error(e, "Merge error");
807 return null;
808 }
809 finally {
810 mergeContext.setMerging(saveMerging);
811 }
812 }
813
814
815 /**
816 * @private
817 * Merge an entity coming from the server in the context
818 *
819 * @param obj external entity
820 * @param previous previously existing object in the context (null if no existing object)
821 * @param expr current path from the context
822 * @param parent parent object for collections
823 * @param propertyName propertyName from the owner object
824 *
825 * @return merged entity (=== previous when previous not null)
826 */
827 private Object mergeEntity(MergeContext mergeContext, final Object obj, Object previous, Expression expr, Object parent, String propertyName) {
828 if (obj != null || previous != null)
829 log.debug("mergeEntity: %s previous %s%s", ObjectUtil.toString(obj), ObjectUtil.toString(previous), obj == previous ? " (same)" : "");
830
831 Object dest = obj;
832 Object p = null;
833 if (obj instanceof Lazyable && !((Lazyable)obj).isInitialized()) {
834 // If entity is uninitialized, try to lookup the cached instance by its class name and id (only works with Hibernate proxies)
835 final EntityDescriptor desc = dataManager.getEntityDescriptor(obj);
836 if (desc.getIdPropertyName() != null) {
837 p = entitiesByUid.find(new Matcher() {
838 public boolean match(Object o) {
839 return o.getClass().getName().equals(obj.getClass().getName()) &&
840 ObjectUtil.objectEquals(dataManager, dataManager.getProperty(obj, desc.getIdPropertyName()), dataManager.getProperty(o, desc.getIdPropertyName()));
841 }
842 });
843
844 if (p != null) {
845 previous = p;
846 dest = previous;
847 }
848 }
849 }
850 else if (obj instanceof Identifiable) {
851 if (obj instanceof EntityProxy)
852 p = entitiesByUid.get(((EntityProxy)obj).getClassName() + ":" + getUid((Identifiable)obj));
853 else
854 p = entitiesByUid.get(obj.getClass().getName() + ":" + getUid((Identifiable)obj));
855 if (p != null) {
856 // Trying to merge an entity that is already cached with itself: stop now, this is not necessary to go deeper in the object graph
857 // it should be already instrumented and tracked
858 if (obj == p)
859 return obj;
860
861 previous = p;
862 dest = previous;
863 }
864 }
865
866 if (dest != previous && previous != null && (ObjectUtil.objectEquals(dataManager, previous, obj)
867 || (parent != null && !(previous instanceof Identifiable)))) // GDS-649 Case of embedded objects
868 dest = previous;
869
870 if (dest == obj && p == null && obj != null && mergeContext.getSourceEntityManager() != null) {
871 // When merging from another entity manager, ensure we create a new copy of the entity
872 // An instance can exist in only one entity manager at a time
873 try {
874 dest = TypeUtil.newInstance(obj.getClass(), Object.class);
875 if (obj instanceof Identifiable)
876 ((Identifiable)dest).setUid(((Identifiable)obj).getUid());
877 }
878 catch (Exception e) {
879 throw new RuntimeException("Could not create class " + obj.getClass(), e);
880 }
881 }
882
883 if (obj instanceof Lazyable && !((Lazyable)obj).isInitialized() && ObjectUtil.objectEquals(dataManager, previous, obj)) {
884 // Don't overwrite existing entity with an uninitialized proxy when optimistic locking is defined
885 log.debug("ignored received uninitialized proxy");
886 // Don't mark the object not dirty as we only received a proxy
887 // dirtyCheckContext.markNotDirty(previous, null);
888 return previous;
889 }
890
891 if (dest instanceof Lazyable && !((Lazyable)dest).isInitialized())
892 log.debug("initialize lazy entity: %s", dest.toString());
893
894 if (dest != null && dest instanceof Identifiable && dest == obj) {
895 log.debug("received entity %s used as destination (ctx: %s)", obj.toString(), this.id);
896 }
897
898 boolean fromCache = (p != null && dest == p);
899
900 if (!fromCache && dest instanceof Identifiable)
901 entitiesByUid.put((Identifiable)dest);
902
903 mergeContext.pushMerge(obj, dest);
904
905 boolean ignore = false;
906 if (dest instanceof Identifiable) {
907 EntityDescriptor desc = dataManager.getEntityDescriptor(dest);
908
909 // If we are in an uninitialing temporary entity manager, try to reproxy associations when possible
910 if (mergeContext.isUninitializing() && parent instanceof Identifiable && propertyName != null) {
911 if (desc.getVersionPropertyName() != null && dataManager.getProperty(obj, desc.getVersionPropertyName()) != null
912 && dataManager.getEntityDescriptor(parent).isLazy(propertyName)) {
913 if (defineProxy(desc, dest, obj)) // Only if entity can be proxied (has a detachedState)
914 return dest;
915 }
916 }
917
918 // Associate entity with the current context
919 attachEntity((Identifiable)dest, false);
920
921 if (previous != null && dest == previous) {
922 // Check version for optimistic locking
923 if (desc.getVersionPropertyName() != null && !mergeContext.isResolvingConflict()) {
924 Number newVersion = (Number)dataManager.getProperty(obj, desc.getVersionPropertyName());
925 Number oldVersion = (Number)dataManager.getProperty(dest, desc.getVersionPropertyName());
926 if ((newVersion != null && oldVersion != null && newVersion.longValue() < oldVersion.longValue()
927 || (newVersion == null && oldVersion != null))) {
928 log.warn("ignored merge of older version of %s (current: %d, received: %d)",
929 dest.toString(), oldVersion, newVersion);
930 ignore = true;
931 }
932 else if ((newVersion != null && oldVersion != null && newVersion.longValue() > oldVersion.longValue())
933 || (newVersion != null && oldVersion == null)) {
934 // Handle changes when version number is increased
935 mergeContext.markVersionChanged(dest);
936
937 boolean entityChanged = dirtyCheckContext.isEntityChanged((Identifiable)dest);
938 if (mergeContext.getExternalDataSessionId() != null && entityChanged) {
939 // Conflict between externally received data and local modifications
940 log.error("conflict with external data detected on %s (current: %d, received: %d)",
941 dest.toString(), oldVersion, newVersion);
942
943 // Check incoming values and local values
944 if (dirtyCheckContext.checkAndMarkNotDirty(mergeContext, dest, obj, null)) {
945 // Incoming data is different from local data
946 Map<String, Object> save = dirtyCheckContext.getSavedProperties(dest);
947 List<String> properties = new ArrayList<String>(save.keySet());
948 properties.remove(desc.getVersionPropertyName());
949 Collections.sort(properties);
950
951 mergeContext.addConflict((Identifiable)dest, (Identifiable)obj, properties);
952
953 ignore = true;
954 }
955 else
956 mergeContext.setMergeUpdate(true);
957 }
958 else
959 mergeContext.setMergeUpdate(true);
960 }
961 else {
962 // Data has been changed locally and not persisted, don't overwrite when version number is unchanged
963 if (dirtyCheckContext.isEntityChanged((Identifiable)dest))
964 mergeContext.setMergeUpdate(false);
965 else
966 mergeContext.setMergeUpdate(true);
967 }
968 }
969 else if (!mergeContext.isResolvingConflict())
970 mergeContext.markVersionChanged(dest);
971 }
972 else
973 mergeContext.markVersionChanged(dest);
974
975 if (!ignore) {
976 if (obj instanceof EntityProxy) {
977 mergeContext.setCurrentMerge(obj);
978 defaultMerge(mergeContext, ((EntityProxy)obj).getWrappedObject(), dest, expr, parent, propertyName);
979 }
980 else
981 defaultMerge(mergeContext, obj, dest, expr, parent, propertyName);
982 }
983 }
984 else
985 defaultMerge(mergeContext, obj, dest, expr, parent, propertyName);
986
987 if (dest != null && !ignore && !mergeContext.isSkipDirtyCheck() && !mergeContext.isResolvingConflict())
988 dirtyCheckContext.checkAndMarkNotDirty(mergeContext, dest, obj, (parent instanceof Identifiable && !(dest instanceof Identifiable)) ? parent : null);
989
990 if (dest != null)
991 log.debug("mergeEntity result: %s", dest.toString());
992
993 // Keep notified of collection updates to notify the server at next remote call
994 dataManager.startTracking(dest, parent);
995
996 return dest;
997 }
998
999
1000 private boolean defineProxy(EntityDescriptor desc, Object dest, Object obj) {
1001 if (desc.getDetachedStateField() == null)
1002 return false;
1003
1004 try {
1005 if (obj != null) {
1006 if (desc.getDetachedStateField().get(obj) == null)
1007 return false;
1008 dataManager.setProperty(dest, desc.getIdPropertyName(), null,
1009 dataManager.getProperty(obj, desc.getIdPropertyName()));
1010 desc.getDetachedStateField().set(dest, desc.getDetachedStateField().get(obj));
1011 }
1012 desc.getInitializedField().set(dest, false);
1013 return true;
1014 }
1015 catch (Exception e) {
1016 throw new RuntimeException("Could not proxy class " + obj.getClass());
1017 }
1018 }
1019
1020
1021 /**
1022 * @private
1023 * Merge a collection coming from the server in the context
1024 *
1025 * @param coll external collection
1026 * @param previous previously existing collection in the context (can be null if no existing collection)
1027 * @param expr current path from the context
1028 * @param parent owner object for collections
1029 * @param propertyName property name in owner object
1030 *
1031 * @return merged collection (=== previous when previous not null)
1032 */
1033 @SuppressWarnings("unchecked")
1034 private List<?> mergeCollection(MergeContext mergeContext, List<Object> coll, Object previous, Expression expr, Object parent, String propertyName) {
1035 log.debug("mergeCollection: %s previous %s", ObjectUtil.toString(coll), ObjectUtil.toString(previous));
1036
1037 if (mergeContext.isUninitializing() && parent instanceof Identifiable && propertyName != null) {
1038 EntityDescriptor desc = dataManager.getEntityDescriptor(parent);
1039 if (desc.getVersionPropertyName() != null && dataManager.getProperty(parent, desc.getVersionPropertyName()) != null
1040 && desc.isLazy(propertyName) && previous instanceof LazyableCollection && ((LazyableCollection)previous).isInitialized()) {
1041 log.debug("uninitialize lazy collection %s", ObjectUtil.toString(previous));
1042 mergeContext.pushMerge(coll, previous);
1043
1044 ((LazyableCollection)previous).uninitialize();
1045 return (List<?>)previous;
1046 }
1047 }
1048
1049 if (previous != null && previous instanceof LazyableCollection && !((LazyableCollection)previous).isInitialized()) {
1050 log.debug("initialize lazy collection %s", ObjectUtil.toString(previous));
1051 mergeContext.pushMerge(coll, previous);
1052
1053 ((LazyableCollection)previous).initializing();
1054
1055 List<Object> added = new ArrayList<Object>(coll.size());
1056 for (int i = 0; i < coll.size(); i++) {
1057 Object obj = coll.get(i);
1058
1059 obj = mergeExternal(mergeContext, obj, null, null, propertyName != null ? parent : null, propertyName, null, false);
1060 added.add(obj);
1061 }
1062
1063 ((LazyableCollection)previous).initialize();
1064 ((Collection<Object>)previous).addAll(added);
1065
1066 // Keep notified of collection updates to notify the server at next remote call
1067 dataManager.startTracking(previous, parent);
1068
1069 return (List<?>)previous;
1070 }
1071
1072 boolean tracking = false;
1073
1074 List<?> nextList = null;
1075 List<Object> list = null;
1076 if (previous != null && previous instanceof List<?>)
1077 list = (List<Object>)previous;
1078 else if (mergeContext.getSourceEntityManager() != null) {
1079 try {
1080 list = coll.getClass().newInstance();
1081 }
1082 catch (Exception e) {
1083 throw new RuntimeException("Could not create class " + coll.getClass());
1084 }
1085 }
1086 else
1087 list = (List<Object>)coll;
1088
1089 mergeContext.pushMerge(coll, list);
1090
1091 List<Object> prevColl = list != coll ? list : null;
1092 List<Object> destColl = prevColl;
1093
1094 if (prevColl != null && mergeContext.isMergeUpdate()) {
1095 // Enable tracking before modifying collection when resolving a conflict
1096 // so the dirty checking can save changes
1097 if (mergeContext.isResolvingConflict()) {
1098 dataManager.startTracking(prevColl, parent);
1099 tracking = true;
1100 }
1101
1102 for (int i = 0; i < destColl.size(); i++) {
1103 Object obj = destColl.get(i);
1104 boolean found = false;
1105 for (int j = 0; j < coll.size(); j++) {
1106 Object next = coll.get(j);
1107 if (ObjectUtil.objectEquals(dataManager, next, obj)) {
1108 found = true;
1109 break;
1110 }
1111 }
1112 if (!found) {
1113 destColl.remove(i);
1114 i--;
1115 }
1116 }
1117 }
1118 for (int i = 0; i < coll.size(); i++) {
1119 Object obj = coll.get(i);
1120 if (destColl != null) {
1121 boolean found = false;
1122 for (int j = i; j < destColl.size(); j++) {
1123 Object prev = destColl.get(j);
1124 if (i < destColl.size() && ObjectUtil.objectEquals(dataManager, prev, obj)) {
1125 obj = mergeExternal(mergeContext, obj, prev, propertyName != null ? expr : null, propertyName != null ? parent : null, propertyName, null, false);
1126
1127 if (j != i) {
1128 destColl.remove(j);
1129 if (i < destColl.size())
1130 destColl.add(i, obj);
1131 else
1132 destColl.add(obj);
1133 if (i > j)
1134 j--;
1135 }
1136 else if (obj != prev)
1137 destColl.set(i, obj);
1138
1139 found = true;
1140 }
1141 }
1142 if (!found) {
1143 obj = mergeExternal(mergeContext, obj, null, propertyName != null ? expr : null, propertyName != null ? parent : null, propertyName, null, false);
1144
1145 if (mergeContext.isMergeUpdate()) {
1146 if (i < prevColl.size())
1147 destColl.add(i, obj);
1148 else
1149 destColl.add(obj);
1150 }
1151 }
1152 }
1153 else {
1154 Object prev = obj;
1155 obj = mergeExternal(mergeContext, obj, null, propertyName != null ? expr : null, propertyName != null ? parent : null, propertyName, null, false);
1156 if (obj != prev)
1157 coll.set(i, obj);
1158 }
1159 }
1160 if (destColl != null && mergeContext.isMergeUpdate()) {
1161 if (!mergeContext.isResolvingConflict() && !mergeContext.isSkipDirtyCheck())
1162 dirtyCheckContext.markNotDirty(previous, (Identifiable)parent);
1163
1164 nextList = prevColl;
1165 }
1166 else if (prevColl instanceof LazyableCollection && !mergeContext.isMergeUpdate()) {
1167 nextList = prevColl;
1168 }
1169 else
1170 nextList = coll;
1171
1172 // Wrap persistent collections
1173 if (parent instanceof Identifiable && propertyName != null && nextList instanceof LazyableCollection && !(nextList instanceof ManagedPersistentCollection)) {
1174 log.debug("create initialized persistent collection from %s", ObjectUtil.toString(nextList));
1175
1176 nextList = dataManager.newPersistentCollection((Identifiable)parent, propertyName, (LazyableCollection)nextList);
1177 }
1178 else
1179 log.debug("mergeCollection result: %s", ObjectUtil.toString(nextList));
1180
1181 mergeContext.pushMerge(coll, nextList, false);
1182
1183 if (!tracking)
1184 dataManager.startTracking(nextList, parent);
1185
1186 return nextList;
1187 }
1188
1189 /**
1190 * @private
1191 * Merge a map coming from the server in the context
1192 *
1193 * @param map external map
1194 * @param previous previously existing map in the context (null if no existing map)
1195 * @param expr current path from the context
1196 * @param parent owner object for the map if applicable
1197 *
1198 * @return merged map (=== previous when previous not null)
1199 */
1200 @SuppressWarnings("unchecked")
1201 private Map<?, ?> mergeMap(MergeContext mergeContext, Map<Object, Object> map, Object previous, Expression expr, Object parent, String propertyName) {
1202 log.debug("mergeMap: %s previous %s", ObjectUtil.toString(map), ObjectUtil.toString(previous));
1203
1204 if (mergeContext.isUninitializing() && parent instanceof Identifiable && propertyName != null) {
1205 EntityDescriptor desc = dataManager.getEntityDescriptor(parent);
1206 if (desc.getVersionPropertyName() != null && dataManager.getProperty(parent, desc.getVersionPropertyName()) != null
1207 && desc.isLazy(propertyName) && previous instanceof LazyableCollection && ((LazyableCollection)previous).isInitialized()) {
1208 log.debug("uninitialize lazy map %s", ObjectUtil.toString(previous));
1209
1210 mergeContext.pushMerge(map, previous);
1211 ((LazyableCollection)previous).uninitialize();
1212 return (Map<?, ?>)previous;
1213 }
1214 }
1215
1216 if (previous != null && previous instanceof LazyableCollection && !((LazyableCollection)previous).isInitialized()) {
1217 log.debug("initialize lazy map %s", ObjectUtil.toString(previous));
1218 mergeContext.pushMerge(map, previous);
1219
1220 ((LazyableCollection)previous).initializing();
1221
1222 for (Entry<?, ?> me : map.entrySet()) {
1223 Object key = mergeExternal(mergeContext, me.getKey(), null, null, propertyName != null ? parent: null, propertyName, null, false);
1224 Object value = mergeExternal(mergeContext, me.getValue(), null, null, propertyName != null ? parent : null, propertyName, null, false);
1225 ((Map<Object, Object>)previous).put(key, value);
1226 }
1227
1228 ((LazyableCollection)previous).initialize();
1229
1230 // Keep notified of collection updates to notify the server at next remote call
1231 dataManager.startTracking(previous, parent);
1232
1233 return (Map<?, ?>)previous;
1234 }
1235
1236 boolean tracking = false;
1237
1238 Map<Object, Object> nextMap = null;
1239 Map<Object, Object> m = null;
1240 if (previous != null && previous instanceof Map<?, ?>)
1241 m = (Map<Object, Object>)previous;
1242 else if (mergeContext.getSourceEntityManager() != null) {
1243 try {
1244 m = (Map<Object, Object>)TypeUtil.newInstance(map.getClass(), Map.class);
1245 }
1246 catch (Exception e) {
1247 throw new RuntimeException("Could not create class " + map.getClass());
1248 }
1249 }
1250 else
1251 m = map;
1252 mergeContext.pushMerge(map, m);
1253
1254 Map<Object, Object> prevMap = m != map ? m : null;
1255
1256 if (prevMap != null) {
1257 if (mergeContext.isResolvingConflict()) {
1258 dataManager.startTracking(prevMap, parent);
1259 tracking = true;
1260 }
1261
1262 if (map != prevMap) {
1263 for (Entry<?, ?> me : map.entrySet()) {
1264 Object newKey = mergeExternal(mergeContext, me.getKey(), null, null, parent, propertyName, null, false);
1265 Object prevValue = prevMap.get(newKey);
1266 Object value = mergeExternal(mergeContext, me.getValue(), prevValue, null, parent, propertyName, null, false);
1267 if (mergeContext.isMergeUpdate() || prevMap.containsKey(newKey))
1268 prevMap.put(newKey, value);
1269 }
1270
1271 if (mergeContext.isMergeUpdate()) {
1272 Iterator<Object> imap = prevMap.keySet().iterator();
1273 while (imap.hasNext()) {
1274 Object key = imap.next();
1275 boolean found = false;
1276 for (Object k : map.keySet()) {
1277 if (ObjectUtil.objectEquals(dataManager, k, key)) {
1278 found = true;
1279 break;
1280 }
1281 }
1282 if (!found)
1283 imap.remove();
1284 }
1285 }
1286 }
1287
1288 if (mergeContext.isMergeUpdate() && !mergeContext.isResolvingConflict() && !mergeContext.isSkipDirtyCheck())
1289 dirtyCheckContext.markNotDirty(previous, (Identifiable)parent);
1290
1291 nextMap = prevMap;
1292 }
1293 else {
1294 List<Object[]> addedToMap = new ArrayList<Object[]>();
1295 for (Entry<?, ?> me : map.entrySet()) {
1296 Object value = mergeExternal(mergeContext, me.getValue(), null, null, parent, propertyName, null, false);
1297 Object key = mergeExternal(mergeContext, me.getKey(), null, null, parent, propertyName, null, false);
1298 addedToMap.add(new Object[] { key, value });
1299 }
1300 map.clear();
1301 for (Object[] obj : addedToMap)
1302 map.put(obj[0], obj[1]);
1303
1304 nextMap = map;
1305 }
1306
1307 if (parent instanceof Identifiable && propertyName != null && nextMap instanceof LazyableCollection && !(nextMap instanceof ManagedPersistentMap)) {
1308 log.debug("create initialized persistent map from %s", ObjectUtil.toString(nextMap));
1309
1310 nextMap = dataManager.newPersistentMap((Identifiable)parent, propertyName, (LazyableCollection)nextMap);
1311 }
1312 else
1313 log.debug("mergeMap result: %s", ObjectUtil.toString(nextMap));
1314
1315 mergeContext.pushMerge(map, nextMap, false);
1316
1317 if (!tracking)
1318 dataManager.startTracking(nextMap, parent);
1319
1320 return nextMap;
1321 }
1322
1323
1324 /**
1325 * @private
1326 * Wraps a persistent collection to manage lazy initialization
1327 *
1328 * @param coll the collection to wrap
1329 * @param previous the previous existing collection
1330 * @param expr the path expression from the context
1331 * @param parent the owner object
1332 * @param propertyName owner property
1333 *
1334 * @return the wrapped persistent collection
1335 */
1336 protected Object mergePersistentCollection(MergeContext mergeContext, LazyableCollection coll, Object previous, Expression expr, Identifiable parent, String propertyName) {
1337 if (previous instanceof ManagedPersistentCollection<?>) {
1338 mergeContext.pushMerge(coll, previous);
1339 if (((LazyableCollection)previous).isInitialized()) {
1340 if (mergeContext.isUninitializeAllowed() && mergeContext.hasVersionChanged(parent)) {
1341 log.debug("uninitialize lazy collection %s", ObjectUtil.toString(previous));
1342 ((LazyableCollection)previous).uninitialize();
1343 }
1344 else
1345 log.debug("keep initialized collection %s", ObjectUtil.toString(previous));
1346 }
1347 dataManager.startTracking(previous, parent);
1348 return previous;
1349 }
1350 else if (previous instanceof ManagedPersistentMap<?, ?>) {
1351 mergeContext.pushMerge(coll, previous);
1352 if (((LazyableCollection)previous).isInitialized()) {
1353 if (mergeContext.isUninitializeAllowed() && mergeContext.hasVersionChanged(parent)) {
1354 log.debug("uninitialize lazy map %s", ObjectUtil.toString(previous));
1355 ((LazyableCollection)previous).uninitialize();
1356 }
1357 else
1358 log.debug("keep initialized map %s", ObjectUtil.toString(previous));
1359 }
1360 dataManager.startTracking(previous, parent);
1361 return previous;
1362 }
1363
1364 if (coll instanceof Map<?, ?>) {
1365 LazyableCollection pm = (LazyableCollection)coll;
1366 if (previous instanceof LazyableCollection)
1367 pm = (LazyableCollection)previous;
1368 if (coll instanceof ManagedPersistentMap<?, ?>)
1369 pm = ((ManagedPersistentMap<?, ?>)coll).clone(mergeContext.isUninitializing());
1370 else if (mergeContext.getSourceEntityManager() != null)
1371 pm = pm.clone(mergeContext.isUninitializing());
1372
1373 ManagedPersistentMap<Object, Object> pmap = dataManager.newPersistentMap(parent, propertyName, pm);
1374 pmap.setServerSession(mergeContext.getServerSession());
1375 mergeContext.pushMerge(coll, pmap);
1376
1377 if (pmap.isInitialized()) {
1378 List<Object> keys = new ArrayList<Object>(pmap.keySet());
1379 for (Object key : keys) {
1380 Object value = pmap.remove(key);
1381 key = mergeExternal(mergeContext, key, null, null, parent, propertyName, null, false);
1382 value = mergeExternal(mergeContext, value, null, null, parent, propertyName, null, false);
1383 pmap.put(key, value);
1384 }
1385 dataManager.startTracking(pmap, parent);
1386 }
1387 else if (parent instanceof Identifiable && propertyName != null)
1388 dataManager.getEntityDescriptor(parent).setLazy(propertyName);
1389 return pmap;
1390 }
1391
1392 LazyableCollection pc = (LazyableCollection)coll;
1393 if (previous instanceof LazyableCollection)
1394 pc = (LazyableCollection)previous;
1395 if (coll instanceof ManagedPersistentCollection<?>)
1396 pc = duplicatePersistentCollection(mergeContext, ((ManagedPersistentCollection<?>)coll).getCollection(), parent, propertyName);
1397 else if (mergeContext.getSourceEntityManager() != null)
1398 pc = duplicatePersistentCollection(mergeContext, pc, parent, propertyName);
1399
1400 ManagedPersistentCollection<Object> pcoll = dataManager.newPersistentCollection(parent, propertyName, pc);
1401 pcoll.setServerSession(mergeContext.getServerSession());
1402 mergeContext.pushMerge(coll, pcoll);
1403
1404 if (pcoll.isInitialized()) {
1405 for (int i = 0; i < pcoll.size(); i++) {
1406 Object obj = mergeExternal(mergeContext, pcoll.get(i), null, null, parent, propertyName, null, false);
1407 if (obj != pcoll.get(i))
1408 pcoll.set(i, obj);
1409 }
1410 dataManager.startTracking(pcoll, parent);
1411 }
1412 else if (parent instanceof Identifiable && propertyName != null)
1413 dataManager.getEntityDescriptor(parent).setLazy(propertyName);
1414 return pcoll;
1415 }
1416
1417 private LazyableCollection duplicatePersistentCollection(MergeContext mergeContext, Object coll, Object parent, String propertyName) {
1418 if (!(coll instanceof LazyableCollection))
1419 throw new RuntimeException("Not a persistent collection/map " + ObjectUtil.toString(coll));
1420
1421 LazyableCollection ccoll = ((LazyableCollection)coll).clone(mergeContext.isUninitializing());
1422
1423 if (mergeContext.isUninitializing() && parent != null && propertyName != null) {
1424 EntityDescriptor desc = dataManager.getEntityDescriptor((Identifiable)parent);
1425 if (desc.getVersionPropertyName() != null && dataManager.getProperty(parent, desc.getVersionPropertyName()) != null && desc.isLazy(propertyName))
1426 ccoll.uninitialize();
1427 }
1428 return ccoll;
1429 }
1430
1431
1432 /**
1433 * @private
1434 * Merge an object coming from another entity manager (in general in the global context) in the local context
1435 *
1436 * @param sourceEntityManager source context of incoming data
1437 * @param obj external object
1438 * @param externalDataSessionId is merge from external data
1439 * @param uninitializing true to force folding of loaded lazy associations
1440 *
1441 * @return merged object
1442 */
1443 public Object mergeFromEntityManager(EntityManager sourceEntityManager, Object obj, String externalDataSessionId, boolean uninitializing) {
1444 try {
1445 MergeContext mergeContext = new MergeContext(this, dirtyCheckContext, null);
1446 mergeContext.setSourceEntityManager(sourceEntityManager);
1447 mergeContext.setUninitializing(uninitializing);
1448 mergeContext.setExternalDataSessionId(externalDataSessionId);
1449
1450 Object next = externalDataSessionId != null
1451 ? internalMergeExternalData(mergeContext, obj, null, null, null) // Force handling of external data
1452 : mergeExternal(mergeContext, obj, null, null, null, null, null, false);
1453
1454 return next;
1455 }
1456 finally {
1457 MergeContext.destroy(this);
1458 }
1459 }
1460
1461
1462 /**
1463 * @private
1464 * Merge an object coming from a remote location (in general from a service) in the local context
1465 *
1466 * @param obj external object
1467 *
1468 * @return merged object (should === previous when previous not null)
1469 */
1470
1471 public Object mergeExternalData(Object obj) {
1472 return mergeExternalData(null, obj, null, null, null, null);
1473 }
1474
1475 public Object mergeExternalData(ServerSession serverSession, Object obj) {
1476 return mergeExternalData(serverSession, obj, null, null, null, null);
1477 }
1478
1479 public Object mergeExternalData(Object obj, Object prev, String externalDataSessionId, List<Object> removals, List<Object> persists) {
1480 return mergeExternalData(null, obj, prev, externalDataSessionId, removals, persists);
1481 }
1482
1483 /**
1484 * @private
1485 * Merge an object coming from a remote location (in general from a service) in the local context
1486 *
1487 * @param obj external object
1488 * @param prev existing local object to merge with
1489 * @param externalDataSessionId sessionId from which the data is coming (other user/server), null if local or current user session
1490 * @param removals array of entities to remove from the entity manager cache
1491 *
1492 * @return merged object (should === previous when previous not null)
1493 */
1494 public Object mergeExternalData(ServerSession serverSession, Object obj, Object prev, String externalDataSessionId, List<Object> removals, List<Object> persists) {
1495 try {
1496 MergeContext mergeContext = new MergeContext(this, dirtyCheckContext, null);
1497 mergeContext.setServerSession(serverSession);
1498 mergeContext.setExternalDataSessionId(externalDataSessionId);
1499
1500 return internalMergeExternalData(mergeContext, obj, prev, removals, persists);
1501 }
1502 finally {
1503 MergeContext.destroy(this);
1504 }
1505 }
1506
1507 /**
1508 * @private
1509 * Merge an object coming from a remote location (in general from a service) in the local context
1510 *
1511 * @param obj external object
1512 * @param prev existing local object to merge with
1513 * @param externalDataSessionId sessionId from which the data is coming (other user/server), null if local or current user session
1514 * @param removals array of entities to remove from the entity manager cache
1515 *
1516 * @return merged object (should === previous when previous not null)
1517 */
1518 public Object internalMergeExternalData(MergeContext mergeContext, Object obj, Object prev, List<Object> removals, List<Object> persists) {
1519 Map<String, Object> savedContext = null;
1520
1521 try {
1522 if (mergeContext.getExternalDataSessionId() != null)
1523 savedContext = trackingContext.saveAndResetContext();
1524
1525 Object next = mergeExternal(mergeContext, obj, prev, null, null, null, null, false);
1526
1527 if (removals != null)
1528 handleRemovalsAndPersists(mergeContext, removals, persists);
1529
1530 if (mergeContext.getExternalDataSessionId() != null) {
1531 handleMergeConflicts(mergeContext);
1532 clearCache();
1533 }
1534
1535 return next;
1536 }
1537 finally {
1538 if (mergeContext.getExternalDataSessionId() != null)
1539 trackingContext.restoreContext(savedContext);
1540 }
1541 }
1542
1543
1544 /**
1545 * Merge conversation entity manager context variables in global entity manager
1546 * Only applicable to conversation contexts
1547 *
1548 * @param entityManager conversation entity manager
1549 */
1550 public void mergeInEntityManager(final EntityManager entityManager) {
1551 final Set<Object> cache = new HashSet<Object>();
1552 final EntityManager sourceEntityManager = this;
1553 entitiesByUid.apply(new UIDWeakSet.Operation() {
1554 public void apply(Object obj) {
1555 // Reset local dirty state, only server state can safely be merged in global context
1556 if (obj instanceof Identifiable)
1557 resetEntity((Identifiable)obj, cache);
1558 entityManager.mergeFromEntityManager(sourceEntityManager, obj, null, false);
1559 }
1560 });
1561 }
1562
1563
1564 @Override
1565 public boolean isDirty() {
1566 return dataManager.isDirty();
1567 }
1568
1569 public boolean isDeepDirtyEntity(Object entity) {
1570 return dirtyCheckContext.isEntityDeepChanged(entity);
1571 }
1572
1573 @Override
1574 public boolean isSaved(Object entity) {
1575 return dirtyCheckContext.getSavedProperties(entity) != null;
1576 }
1577
1578
1579 private String getUid(Identifiable uidObject) {
1580 String uid = uidObject.getUid();
1581 if (uid == null) {
1582 uid = UUIDUtil.randomUUID();
1583 uidObject.setUid(uid);
1584 }
1585 return uid;
1586 }
1587
1588
1589 /**
1590 * Remove elements from cache and managed collections
1591 *
1592 * @param removals array of entity instances to remove from the entity manager cache
1593 */
1594 public void handleRemovalsAndPersists(MergeContext mergeContext, List<Object> removals, List<Object> persists) {
1595 for (Object removal : removals) {
1596 Object entity = getCachedObject(removal, true);
1597 if (entity == null) // Not found in local cache, cannot remove
1598 continue;
1599
1600 if (mergeContext.getExternalDataSessionId() != null && !mergeContext.isResolvingConflict()
1601 && dirtyCheckContext.isEntityChanged(entity)) {
1602 // Conflict between externally received data and local modifications
1603 log.error("conflict with external data removal detected on %s", ObjectUtil.toString(entity));
1604
1605 mergeContext.addConflict((Identifiable)entity, null, null);
1606 }
1607 else {
1608 boolean saveMerging = mergeContext.isMerging();
1609 try {
1610 mergeContext.setMerging(true);
1611
1612 List<Object[]> owners = getOwnerEntities(entity);
1613 if (owners != null) {
1614 for (Object[] owner : owners) {
1615 Object val = dataManager.getProperty(owner[0], (String)owner[1]);
1616 if (val instanceof LazyableCollection && !((LazyableCollection)val).isInitialized())
1617 continue;
1618 if (val instanceof List<?>) {
1619 int idx = ((List<?>)val).indexOf(entity);
1620 if (idx >= 0)
1621 ((List<?>)val).remove(idx);
1622 }
1623 else if (val instanceof Map<?, ?>) {
1624 Map<?, ?> map = (Map<?, ?>)val;
1625 if (map.containsKey(entity))
1626 map.remove(entity);
1627
1628 for (Iterator<?> ikey = map.keySet().iterator(); ikey.hasNext(); ) {
1629 Object key = ikey.next();
1630 if (ObjectUtil.objectEquals(dataManager, map.get(key), entity))
1631 ikey.remove();
1632 }
1633 }
1634 }
1635 }
1636
1637 /* May not be necessary, should be cleaned up by weak reference */
1638 Map<String, Object> pvalues = dataManager.getPropertyValues(entity, false, true);
1639 for (Object val : pvalues.values()) {
1640 if (val instanceof List<?> || val instanceof Map<?, ?> || (val != null && val.getClass().isArray()))
1641 entityReferences.remove(val);
1642 }
1643 entityReferences.remove(entity);
1644
1645 detach((Identifiable)entity, new IdentityHashMap<Object, Object>(), true);
1646 }
1647 finally {
1648 mergeContext.setMerging(saveMerging);
1649 }
1650 }
1651 }
1652
1653 dirtyCheckContext.fixRemovalsAndPersists(mergeContext, removals, persists);
1654 }
1655
1656
1657 private List<DataConflictListener> dataConflictListeners = new ArrayList<DataConflictListener>();
1658
1659 public void addListener(DataConflictListener listener) {
1660 dataConflictListeners.add(listener);
1661 }
1662
1663 public void removeListener(DataConflictListener listener) {
1664 dataConflictListeners.remove(listener);
1665 }
1666
1667 /**
1668 * Dispatch an event when last merge generated conflicts
1669 */
1670 public void handleMergeConflicts(MergeContext mergeContext) {
1671 // Clear thread cache so acceptClient/acceptServer can work inside the conflicts handler
1672 // mergeContext.clearCache();
1673 mergeContext.initMergeConflicts();
1674
1675 if (mergeContext.getMergeConflicts() != null) {
1676 for (DataConflictListener listener : dataConflictListeners)
1677 listener.onConflict(this, mergeContext.getMergeConflicts());
1678 }
1679 }
1680
1681 /**
1682 * Resolve merge conflicts
1683 *
1684 * @param modifiedEntity the received entity
1685 * @param localEntity the locally cached entity
1686 * @param resolving true to keep client state
1687 */
1688 public void resolveMergeConflicts(MergeContext mergeContext, Object modifiedEntity, Object localEntity, boolean resolving) {
1689 try {
1690 mergeContext.setResolvingConflict(resolving);
1691
1692 if (modifiedEntity == null)
1693 handleRemovalsAndPersists(mergeContext, Collections.singletonList(localEntity), Collections.emptyList());
1694 else
1695 mergeExternal(mergeContext, modifiedEntity, localEntity, null, null, null, null, false);
1696
1697 mergeContext.checkConflictsResolved();
1698 }
1699 finally {
1700 mergeContext.setResolvingConflict(false);
1701 }
1702 }
1703
1704
1705 // /**
1706 // * Enables or disabled dirty checking in this context
1707 // *
1708 // * @param enabled
1709 // */
1710 // public void setDirtyCheckEnabled(boolean enabled) {
1711 // _mergeContext.merging = !enabled;
1712 // }
1713
1714
1715 /**
1716 * {@inheritDoc}
1717 */
1718 public Map<String, Object> getSavedProperties(Object entity) {
1719 Object localEntity = getCachedObject(entity, true);
1720 if (localEntity == null)
1721 return null;
1722 return dirtyCheckContext.getSavedProperties(localEntity);
1723 }
1724
1725
1726 /**
1727 * Default implementation of entity merge for simple ActionScript beans with public properties
1728 * Can be used to implement Tide managed entities with simple objects
1729 *
1730 * @param em the context
1731 * @param obj source object
1732 * @param dest destination object
1733 * @param expr current path of the entity in the context (mostly for internal use)
1734 * @param parent owning object
1735 * @param propertyName property name of the owning object
1736 */
1737 public void defaultMerge(MergeContext mergeContext, Object obj, Object dest, Expression expr, Object parent, String propertyName) {
1738 // Merge internal state
1739 try {
1740 EntityDescriptor desc = dataManager.getEntityDescriptor(obj);
1741 if (desc.getInitializedField() != null)
1742 desc.getInitializedField().set(dest, desc.getInitializedField().get(obj));
1743 if (desc.getDetachedStateField() != null)
1744 desc.getDetachedStateField().set(dest, desc.getDetachedStateField().get(obj));
1745 }
1746 catch (Exception e) {
1747 log.error(e, "Could not merge internal state of object " + ObjectUtil.toString(obj));
1748 }
1749
1750 Map<String, Object> pval = dataManager.getPropertyValues(obj, false, false);
1751 List<String> rw = new ArrayList<String>();
1752
1753 boolean isEmbedded = parent instanceof Identifiable && !(obj instanceof Identifiable);
1754 for (Entry<String, Object> mval : pval.entrySet()) {
1755 String propName = mval.getKey();
1756 Object o = mval.getValue();
1757 Object d = dataManager.getProperty(dest, propName);
1758 o = mergeExternal(mergeContext, o, d, expr, isEmbedded ? parent : dest, isEmbedded ? propertyName + "." + propName : propName, propName, false);
1759 if (o != d && mergeContext.isMergeUpdate())
1760 dataManager.setInternalProperty(dest, propName, o);
1761 rw.add(propName);
1762 }
1763
1764 pval = dataManager.getPropertyValues(obj, rw, true, false);
1765 for (Entry<String, Object> mval : pval.entrySet()) {
1766 String propName = mval.getKey();
1767 Object o = mval.getValue();
1768 Object d = dataManager.getProperty(dest, propName);
1769 if (o instanceof Identifiable || d instanceof Identifiable)
1770 throw new IllegalStateException("Cannot merge the read-only property " + propName + " on bean " + obj + " with an Identifiable value, this will break local unicity and caching. Change property access to read-write.");
1771
1772 mergeExternal(mergeContext, o, d, expr, parent != null ? parent : dest, propertyName != null ? propertyName + '.' + propName : propName, null, false);
1773 }
1774 }
1775
1776
1777 public boolean isEntityChanged(Identifiable entity) {
1778 return dirtyCheckContext.isEntityChanged(entity);
1779 }
1780
1781 public boolean isEntityDeepChanged(Identifiable entity) {
1782 return dirtyCheckContext.isEntityDeepChanged(entity);
1783 }
1784
1785 /**
1786 * Discard changes of entity from last version received from the server
1787 *
1788 * @param entity entity to restore
1789 */
1790 public void resetEntity(Identifiable entity) {
1791 if (entity == null)
1792 throw new IllegalArgumentException("Entity cannot be null");
1793
1794 EntityManager em = PersistenceManager.getEntityManager(entity);
1795 if (em == null)
1796 return;
1797
1798 if (em != this)
1799 throw new IllegalArgumentException("Cannot reset an entity attached to another entity manager " + entity);
1800
1801 Set<Object> cache = new HashSet<Object>();
1802 resetEntity(entity, cache);
1803 }
1804
1805 private void resetEntity(Identifiable entity, Set<Object> cache) {
1806 try {
1807 MergeContext mergeContext = new MergeContext(this, dirtyCheckContext, null);
1808 // Disable dirty check during reset of entity
1809 mergeContext.setMerging(true);
1810 dirtyCheckContext.resetEntity(mergeContext, entity, entity, cache);
1811 }
1812 finally {
1813 MergeContext.destroy(this);
1814 }
1815 }
1816
1817 /**
1818 * Discard changes of all cached entities from last version received from the server
1819 *
1820 * @param cache reset cache
1821 */
1822 public void resetAllEntities() {
1823 try {
1824 Set<Object> cache = new HashSet<Object>();
1825
1826 MergeContext mergeContext = new MergeContext(this, dirtyCheckContext, null);
1827 // Disable dirty check during reset of entity
1828 mergeContext.setMerging(true);
1829 dirtyCheckContext.resetAllEntities(mergeContext, cache);
1830 }
1831 finally {
1832 MergeContext.destroy(this);
1833 }
1834 }
1835
1836 /**
1837 * {@inheritdoc}
1838 */
1839 public void acceptConflict(Conflict conflict, boolean client) {
1840 boolean saveTracking = trackingContext.isEnabled();
1841 try {
1842 trackingContext.setEnabled(false);
1843
1844 Object modifiedEntity = null;
1845 if (client) {
1846 // Copy the local entity to save local changes
1847 EntityManager entityManager = PersistenceManager.getEntityManager(conflict.getLocalEntity());
1848 EntityManager tmp = entityManager.newTemporaryEntityManager();
1849 modifiedEntity = tmp.mergeFromEntityManager(entityManager, conflict.getLocalEntity(), null, false);
1850 tmp.clear();
1851 }
1852 else
1853 modifiedEntity = conflict.getReceivedEntity();
1854
1855 try {
1856 MergeContext mergeContext = new MergeContext(this, dirtyCheckContext, null);
1857
1858 // Reset the local entity to its last stable state
1859 resetEntity(conflict.getLocalEntity());
1860
1861 if (client) {
1862 // Merge with the incoming entity (to update version, id and all)
1863 if (conflict.getReceivedEntity() != null)
1864 mergeExternal(mergeContext, conflict.getReceivedEntity(), conflict.getLocalEntity(), null, null, null, null, false);
1865 }
1866
1867 // Finally reapply local changes on merged received result
1868 resolveMergeConflicts(mergeContext, modifiedEntity, conflict.getLocalEntity(), client);
1869 }
1870 finally {
1871 MergeContext.destroy(this);
1872 }
1873 }
1874 finally {
1875 trackingContext.setEnabled(saveTracking);
1876 }
1877 }
1878
1879
1880 private RemoteInitializer remoteInitializer = null;
1881
1882 @Override
1883 public void setRemoteInitializer(RemoteInitializer remoteInitializer) {
1884 this.remoteInitializer = remoteInitializer;
1885 }
1886
1887 /**
1888 * {@inheritdoc}
1889 */
1890 public boolean initializeObject(ServerSession serverSession, Object object) {
1891 boolean initialize = false;
1892 if (remoteInitializer != null) {
1893 boolean saveTracking = trackingContext.isEnabled();
1894 try {
1895 trackingContext.setEnabled(false);
1896 initialize = remoteInitializer.initializeObject(serverSession, object);
1897 }
1898 finally {
1899 trackingContext.setEnabled(saveTracking);
1900 }
1901 }
1902 return initialize;
1903 }
1904 //
1905 // /**
1906 // * {@inheritdoc}
1907 // */
1908 // public boolean validateObject(Object object, String property, Object value) {
1909 // boolean validate = false;
1910 // if (remoteValidator != null) {
1911 // boolean saveTracking = trackingContext.isEnabled();
1912 // try {
1913 // trackingContext.setEnabled(false);
1914 // validate = remoteValidator.validateObject(object, property, value);
1915 // }
1916 // finally {
1917 // trackingContext.setEnabled(saveTracking);
1918 // }
1919 // }
1920 // return validate;
1921 // }
1922
1923 /**
1924 * @private
1925 * Interceptor for managed entity setters
1926 *
1927 * @param entity entity to intercept
1928 * @param propName property name
1929 * @param oldValue old value
1930 * @param newValue new value
1931 */
1932 public void setEntityProperty(Identifiable entity, String propName, Object oldValue, Object newValue) {
1933 if (newValue != oldValue) {
1934 if (oldValue != null) {
1935 removeReference(oldValue, entity, propName, null);
1936 dataManager.stopTracking(oldValue, entity);
1937 }
1938
1939 if (newValue instanceof Identifiable || newValue instanceof List<?> || newValue instanceof Map<?, ?>) {
1940 addReference(newValue, entity, propName, null);
1941 dataManager.startTracking(newValue, entity);
1942 }
1943 }
1944
1945 MergeContext mergeContext = MergeContext.get(PersistenceManager.getEntityManager(entity));
1946 if (mergeContext == null)
1947 return;
1948
1949 if (!mergeContext.isMerging() || mergeContext.isResolvingConflict())
1950 dirtyCheckContext.entityPropertyChangeHandler(entity, entity, propName, oldValue, newValue);
1951
1952 addUpdates(entity);
1953 }
1954
1955
1956 /**
1957 * @private
1958 * Interceptor for managed entity getters
1959 *
1960 * @param entity entity to intercept
1961 * @param propName property name
1962 * @param value value
1963 *
1964 * @return value
1965 */
1966 public Object getEntityProperty(Identifiable entity, String propName, Object value) {
1967 if (value instanceof Identifiable || value instanceof List<?> || value instanceof Map<?, ?> || value instanceof ManagedPersistentAssociation)
1968 addResults(entity, propName);
1969
1970 EntityDescriptor desc = dataManager.getEntityDescriptor(entity);
1971 if (desc != null && propName.equals(desc.getDirtyPropertyName()))
1972 return dirtyCheckContext.isEntityChanged(entity);
1973
1974 return value;
1975 }
1976
1977
1978 public class DefaultTrackingHandler implements DataManager.TrackingHandler {
1979
1980 /**
1981 * @private
1982 * Property event handler to save changes on embedded objects
1983 *
1984 * @param event collection event
1985 */
1986 public void entityPropertyChangeHandler(Object target, String property, Object oldValue, Object newValue) {
1987 MergeContext mergeContext = MergeContext.get(PersistenceManager.getEntityManager(target));
1988 if ((mergeContext != null && mergeContext.getSourceEntityManager() == this) || !isActive())
1989 return;
1990
1991 if (newValue != oldValue) {
1992 if (oldValue instanceof Identifiable || oldValue instanceof List<?> || oldValue instanceof Map<?, ?>) {
1993 removeReference(oldValue, target, property, null);
1994 dataManager.stopTracking(oldValue, target);
1995 }
1996
1997 if (newValue instanceof Identifiable || newValue instanceof List<?> || newValue instanceof Map<?, ?>) {
1998 addReference(newValue, target, property, null);
1999 dataManager.startTracking(newValue, target);
2000 }
2001 }
2002
2003 log.debug("property changed: %s %s", ObjectUtil.toString(target), property);
2004
2005 if (mergeContext == null || !mergeContext.isMerging() || mergeContext.isResolvingConflict()) {
2006 Object owner = target instanceof Identifiable ? null : getOwnerEntity(target);
2007 if (owner == null)
2008 dirtyCheckContext.entityPropertyChangeHandler(target, target, property, oldValue, newValue);
2009 else if (owner instanceof Object[] && ((Object[])owner)[0] instanceof Identifiable)
2010 dirtyCheckContext.entityPropertyChangeHandler(((Object[])owner)[0], target, property, oldValue, newValue);
2011 }
2012
2013 // TODO: EntityManager embedded
2014 // PropertyChangeEvent pce = new PropertyChangeEvent("entityEmbeddedChange", event.property, event.oldValue, event.newValue, event.source);
2015 // dispatchEvent(pce);
2016 }
2017
2018 /**
2019 * @private
2020 * Collection event handler to save changes on collections
2021 *
2022 * @param event collection event
2023 */
2024 public void collectionChangeHandler(ChangeKind kind, Object target, int location, Object[] items) {
2025 MergeContext mergeContext = MergeContext.get(PersistenceManager.getEntityManager(target));
2026 if ((mergeContext != null && mergeContext.getSourceEntityManager() == this) || !isActive())
2027 return;
2028
2029 if (target instanceof Component)
2030 return;
2031
2032 if (kind == ChangeKind.ADD || kind == ChangeKind.REMOVE || kind == ChangeKind.REPLACE)
2033 addUpdates(target);
2034 }
2035
2036 /**
2037 * @private
2038 * Collection event handler to save changes on managed collections
2039 *
2040 * @param event collection event
2041 */
2042 public void entityCollectionChangeHandler(ChangeKind kind, Object target, int location, Object[] items) {
2043 MergeContext mergeContext = MergeContext.get(PersistenceManager.getEntityManager(target));
2044 if ((mergeContext != null && mergeContext.getSourceEntityManager() == this) || !isActive())
2045 return;
2046
2047 int i = 0;
2048
2049 Object[] parent = null;
2050 if (kind == ChangeKind.ADD && items != null && items.length > 0) {
2051 parent = getOwnerEntity(target);
2052 for (i = 0; i < items.length; i++) {
2053 if (items[i] instanceof Identifiable) {
2054 if (parent != null)
2055 addReference((Identifiable)items[i], parent[0], (String)parent[1], null);
2056 else
2057 attachEntity((Identifiable)items[i]);
2058 dataManager.startTracking((Identifiable)items[i], parent != null ? parent[0] : null);
2059 }
2060 }
2061 }
2062 else if (kind == ChangeKind.REMOVE && items != null && items.length > 0) {
2063 parent = getOwnerEntity(target);
2064 if (parent != null) {
2065 for (i = 0; i < items.length; i++) {
2066 if (items[i] instanceof Identifiable)
2067 removeReference((Identifiable)items[i], parent[0], (String)parent[1], null);
2068 }
2069 }
2070 }
2071 else if (kind == ChangeKind.REPLACE && items != null && items.length > 0) {
2072 parent = getOwnerEntity(target);
2073 for (i = 0; i < items.length; i++) {
2074 Object newValue = ((Object[])items[i])[1];
2075 if (newValue instanceof Identifiable) {
2076 if (parent != null)
2077 addReference((Identifiable)newValue, parent[0], (String)parent[1], null);
2078 else
2079 attachEntity((Identifiable)newValue);
2080 dataManager.startTracking((Identifiable)newValue, parent != null ? parent[0] : null);
2081 }
2082 }
2083 }
2084
2085 if (!(kind == ChangeKind.ADD || kind == ChangeKind.REMOVE || kind == ChangeKind.REPLACE))
2086 return;
2087
2088 log.debug("collection changed: %s %s", kind, ObjectUtil.toString(target));
2089
2090 if (mergeContext == null || !mergeContext.isMerging() || mergeContext.isResolvingConflict()) {
2091 if (parent == null)
2092 log.warn("Owner entity not found for collection %s, cannot process dirty checking", ObjectUtil.toString(target));
2093 else
2094 dirtyCheckContext.entityCollectionChangeHandler(parent[0], (String)parent[1], (Collection<?>)target, kind, location, items);
2095 }
2096
2097 if (items != null && items.length > 0 && items[0] instanceof Identifiable)
2098 addUpdates(target);
2099 else if (kind == ChangeKind.UPDATE && items != null && items.length > 0) {
2100 PropertyChange pc = (PropertyChange)items[0];
2101 if (pc.getObject() instanceof Identifiable)
2102 addUpdates(target);
2103 }
2104 }
2105
2106 /**
2107 * @private
2108 * Map event handler to save changes on maps
2109 *
2110 * @param event map event
2111 */
2112 public void mapChangeHandler(ChangeKind kind, Object target, int location, Object[] items) {
2113 MergeContext mergeContext = MergeContext.get(PersistenceManager.getEntityManager(target));
2114 if ((mergeContext != null && mergeContext.getSourceEntityManager() == this) || !isActive())
2115 return;
2116
2117 if (target instanceof Component)
2118 return;
2119
2120 if (kind == ChangeKind.ADD || kind == ChangeKind.REMOVE || kind == ChangeKind.REPLACE)
2121 addUpdates(target);
2122 }
2123
2124 /**
2125 * @private
2126 * Collection event handler to save changes on managed maps
2127 *
2128 * @param event map event
2129 */
2130 public void entityMapChangeHandler(ChangeKind kind, Object target, int location, Object[] items) {
2131 MergeContext mergeContext = MergeContext.get(PersistenceManager.getEntityManager(target));
2132 if ((mergeContext != null && mergeContext.getSourceEntityManager() == this) || !isActive())
2133 return;
2134
2135 Object[] parent = null;
2136 if (kind == ChangeKind.ADD && items != null && items.length > 0) {
2137 parent = getOwnerEntity(target);
2138 for (int i = 0; i < items.length; i++) {
2139 if (items[i] instanceof Identifiable) {
2140 if (parent != null)
2141 addReference((Identifiable)items[i], parent[0], (String)parent[1], null);
2142 else
2143 attachEntity((Identifiable)items[i]);
2144 dataManager.startTracking((Identifiable)items[i], parent != null ? parent[0] : null);
2145 }
2146 else if (items[i] instanceof Object[]) {
2147 Object[] obj = (Object[])items[i];
2148 if (obj[0] instanceof Identifiable) {
2149 if (parent != null)
2150 addReference((Identifiable)obj[0], parent[0], (String)parent[1], null);
2151 else
2152 attachEntity((Identifiable)obj[0]);
2153 dataManager.startTracking((Identifiable)obj[0], parent != null ? parent[0] : null);
2154 }
2155 if (obj[1] instanceof Identifiable) {
2156 if (parent != null)
2157 addReference((Identifiable)obj[1], parent[0], (String)parent[1], null);
2158 else
2159 attachEntity((Identifiable)obj[1]);
2160 dataManager.startTracking((Identifiable)obj[1], parent != null ? parent[0] : null);
2161 }
2162 }
2163 }
2164 }
2165 else if (kind == ChangeKind.REMOVE && items != null && items.length > 0) {
2166 parent = getOwnerEntity(target);
2167 if (parent != null) {
2168 for (int i = 0; i < items.length; i++) {
2169 if (items[i] instanceof Identifiable) {
2170 removeReference((Identifiable)items[i], parent[0], (String)parent[1], null);
2171 }
2172 else if (items[i] instanceof Object[]) {
2173 Object[] obj = (Object[])items[i];
2174 if (obj[0] instanceof Identifiable) {
2175 removeReference((Identifiable)obj[0], parent[0], (String)parent[1], null);
2176 }
2177 if (obj[1] instanceof Identifiable) {
2178 removeReference((Identifiable)obj[1], parent[0], (String)parent[1], null);
2179 }
2180 }
2181 }
2182 }
2183 }
2184 else if (kind == ChangeKind.REPLACE && items != null && items.length > 0) {
2185 parent = getOwnerEntity(target);
2186 for (int i = 0; i < items.length; i++) {
2187 Object[] item = (Object[])items[i];
2188 if (item[1] instanceof Identifiable) {
2189 if (parent != null)
2190 removeReference((Identifiable)item[1], parent[0], (String)parent[1], null);
2191 }
2192 if (item[2] instanceof Identifiable) {
2193 if (parent != null)
2194 addReference((Identifiable)item[2], parent[0], (String)parent[1], null);
2195 else
2196 attachEntity((Identifiable)item[2]);
2197 dataManager.startTracking((Identifiable)item[2], parent != null ? parent[0] : null);
2198 }
2199 }
2200 }
2201
2202 if (!(kind == ChangeKind.ADD || kind == ChangeKind.REMOVE || kind == ChangeKind.REPLACE))
2203 return;
2204
2205 log.debug("map changed: %s %s", kind, ObjectUtil.toString(target));
2206
2207 if (mergeContext == null || !mergeContext.isMerging() || mergeContext.isResolvingConflict()) {
2208 if (parent == null)
2209 log.warn("Owner entity not found for collection %s, cannot process dirty checking", ObjectUtil.toString(target));
2210 else
2211 dirtyCheckContext.entityMapChangeHandler(parent[0], (String)parent[1], (Map<?, ?>)target, kind, items);
2212 }
2213
2214 if (items != null && items.length > 0 && items[0] instanceof Object[] && ((Object[])items[0])[1] instanceof Identifiable) {
2215 addUpdates(target);
2216 }
2217 else if (kind == ChangeKind.UPDATE && items != null && items.length > 0) {
2218 if (((PropertyChange)items[0]).getObject() instanceof Identifiable)
2219 addUpdates(target);
2220 }
2221 }
2222 }
2223
2224 /**
2225 * @private
2226 * Track updates on target object
2227 *
2228 * @param object tracked object
2229 */
2230 private void addUpdates(Object object) {
2231 Expression ref = getReference(object, true, new HashSet<Object>());
2232 if (ref != null && expressionEvaluator != null) {
2233 Value value = expressionEvaluator.evaluate(ref);
2234 trackingContext.addUpdate(value.componentName, value.componentClassName, ref.getExpression(), value.value);
2235 }
2236 }
2237
2238 /**
2239 * @private
2240 * Track results on target object
2241 *
2242 * @param object tracked object
2243 * @param propName property name on tracked object
2244 */
2245 private void addResults(Object object, String propName) {
2246 Expression ref = getReference(object, true, new HashSet<Object>());
2247 if (ref != null && expressionEvaluator != null) {
2248 Value value = expressionEvaluator.getInstance(ref, object);
2249 trackingContext.addResult(value.componentName, value.componentClassName,
2250 ref.getExpression() != null ? ref.getExpression() + "." + propName : propName, value.instance);
2251 }
2252 }
2253
2254 /**
2255 * @private
2256 * Handle data updates
2257 *
2258 * @param sourceSessionId sessionId from which data updates come (null when from current session)
2259 * @param updates list of data updates
2260 */
2261 public void handleUpdates(MergeContext mergeContext, String sourceSessionId, List<Update> updates) {
2262 List<Object> merges = new ArrayList<Object>();
2263 List<Object> removals = new ArrayList<Object>();
2264 List<Object> persists = new ArrayList<Object>();
2265
2266 for (Update update : updates) {
2267 if (update.getKind() == UpdateKind.PERSIST || update.getKind() == UpdateKind.UPDATE)
2268 merges.add(update.getEntity());
2269 else if (update.getKind() == UpdateKind.REMOVE)
2270 removals.add(update.getEntity());
2271 if (update.getKind() == UpdateKind.PERSIST)
2272 persists.add(update.getEntity());
2273 }
2274
2275 mergeContext.setExternalDataSessionId(sourceSessionId);
2276 internalMergeExternalData(mergeContext, merges, null, removals, persists);
2277
2278 for (Update update : updates)
2279 update.setEntity(getCachedObject(update.getEntity(), update.getKind() != UpdateKind.REMOVE));
2280 }
2281
2282 public void raiseUpdateEvents(Context context, List<EntityManager.Update> updates) {
2283 List<String> refreshes = new ArrayList<String>();
2284
2285 for (EntityManager.Update update : updates) {
2286 Object entity = update.getEntity();
2287
2288 if (entity != null) {
2289 String entityName = entity instanceof EntityRef ? getUnqualifiedClassName(((EntityRef)entity).getClassName()) : entity.getClass().getSimpleName();
2290 String eventType = update.getKind().eventName() + "." + entityName;
2291 context.getEventBus().raiseEvent(context, eventType, entity);
2292
2293 if (UpdateKind.PERSIST.equals(update.getKind()) || UpdateKind.REMOVE.equals(update.getKind())) {
2294 if (!refreshes.contains(entityName))
2295 refreshes.add(entityName);
2296 }
2297 }
2298 }
2299
2300 for (String refresh : refreshes)
2301 context.getEventBus().raiseEvent(context, UpdateKind.REFRESH.eventName() + "." + refresh);
2302 }
2303
2304 private static String getUnqualifiedClassName(String className) {
2305 int idx = className.lastIndexOf(".");
2306 return idx >= 0 ? className.substring(idx+1) : className;
2307 }
2308
2309
2310 @Override
2311 public void setRemoteValidator(RemoteValidator remoteValidator) {
2312 }
2313
2314
2315 @Override
2316 public boolean validateObject(Object object, String property, Object value) {
2317 return false;
2318 }
2319 }