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