| 1 56 57 package org.objectstyle.cayenne.access; 58 59 import java.io.IOException ; 60 import java.io.ObjectInputStream ; 61 import java.io.ObjectOutputStream ; 62 import java.io.Serializable ; 63 import java.util.ArrayList ; 64 import java.util.Collection ; 65 import java.util.Collections ; 66 import java.util.HashMap ; 67 import java.util.Iterator ; 68 import java.util.List ; 69 import java.util.Map ; 70 71 import org.apache.log4j.Level; 72 import org.objectstyle.cayenne.CayenneException; 73 import org.objectstyle.cayenne.CayenneRuntimeException; 74 import org.objectstyle.cayenne.DataObject; 75 import org.objectstyle.cayenne.DataRow; 76 import org.objectstyle.cayenne.Fault; 77 import org.objectstyle.cayenne.ObjectId; 78 import org.objectstyle.cayenne.PersistenceState; 79 import org.objectstyle.cayenne.TempObjectId; 80 import org.objectstyle.cayenne.access.event.DataContextEvent; 81 import org.objectstyle.cayenne.access.util.IteratedSelectObserver; 82 import org.objectstyle.cayenne.access.util.PrefetchHelper; 83 import org.objectstyle.cayenne.access.util.QueryUtils; 84 import org.objectstyle.cayenne.conf.Configuration; 85 import org.objectstyle.cayenne.event.EventManager; 86 import org.objectstyle.cayenne.event.EventSubject; 87 import org.objectstyle.cayenne.map.DataMap; 88 import org.objectstyle.cayenne.map.DbJoin; 89 import org.objectstyle.cayenne.map.DbRelationship; 90 import org.objectstyle.cayenne.map.Entity; 91 import org.objectstyle.cayenne.map.EntityResolver; 92 import org.objectstyle.cayenne.map.ObjAttribute; 93 import org.objectstyle.cayenne.map.ObjEntity; 94 import org.objectstyle.cayenne.map.ObjRelationship; 95 import org.objectstyle.cayenne.query.GenericSelectQuery; 96 import org.objectstyle.cayenne.query.ParameterizedQuery; 97 import org.objectstyle.cayenne.query.Query; 98 import org.objectstyle.cayenne.query.SelectQuery; 99 import org.objectstyle.cayenne.util.Util; 100 101 128 public class DataContext implements QueryEngine, Serializable { 129 130 private static final DataContextDelegate defaultDelegate = new DataContextDelegate() { 132 133 public GenericSelectQuery willPerformSelect( 134 DataContext context, 135 GenericSelectQuery query) { 136 return query; 137 } 138 139 public boolean shouldMergeChanges(DataObject object, DataRow snapshotInStore) { 140 return true; 141 } 142 143 public boolean shouldProcessDelete(DataObject object) { 144 return true; 145 } 146 147 public void finishedMergeChanges(DataObject object) { 148 149 } 150 151 public void finishedProcessDelete(DataObject object) { 152 153 } 154 }; 155 156 public static final EventSubject WILL_COMMIT = EventSubject.getSubject( 158 DataContext.class, 159 "DataContextWillCommit"); 160 public static final EventSubject DID_COMMIT = EventSubject.getSubject( 161 DataContext.class, 162 "DataContextDidCommit"); 163 public static final EventSubject DID_ROLLBACK = EventSubject.getSubject( 164 DataContext.class, 165 "DataContextDidRollback"); 166 167 168 169 174 protected static final ThreadLocal threadDataContext = new ThreadLocal (); 175 176 private static boolean transactionEventsEnabledDefault; 178 179 private boolean transactionEventsEnabled; 181 182 private DataContextDelegate delegate; 184 185 protected boolean usingSharedSnaphsotCache; 186 protected boolean validatingObjectsOnCommit; 187 protected ObjectStore objectStore; 188 189 protected transient QueryEngine parent; 190 191 196 protected Map userProperties; 197 198 204 protected transient String lazyInitParentDomainName; 205 206 210 private static final DataObject newDataObject(String className) throws Exception { 211 return (DataObject) Configuration 212 .getResourceLoader() 213 .loadClass(className) 214 .newInstance(); 215 } 216 217 226 public static DataContext getThreadDataContext() throws IllegalStateException { 227 DataContext dc = (DataContext) threadDataContext.get(); 228 if (dc == null) { 229 throw new IllegalStateException ("Current thread has no bound DataContext."); 230 } 231 232 return dc; 233 } 234 235 242 public static void bindThreadDataContext(DataContext context) { 243 threadDataContext.set(context); 244 } 245 246 252 public static DataContext createDataContext() { 253 return Configuration.getSharedConfiguration().getDomain().createDataContext(); 254 } 255 256 265 public static DataContext createDataContext(boolean useSharedCache) { 266 return Configuration.getSharedConfiguration().getDomain().createDataContext( 267 useSharedCache); 268 } 269 270 275 public static DataContext createDataContext(String domainName) { 276 DataDomain domain = Configuration.getSharedConfiguration().getDomain(domainName); 277 if (domain == null) { 278 throw new IllegalArgumentException ("Non-existent domain: " + domainName); 279 } 280 return domain.createDataContext(); 281 } 282 283 291 public static DataContext createDataContext(String domainName, boolean useSharedCache) { 292 293 DataDomain domain = Configuration.getSharedConfiguration().getDomain(domainName); 294 if (domain == null) { 295 throw new IllegalArgumentException ("Non-existent domain: " + domainName); 296 } 297 return domain.createDataContext(useSharedCache); 298 } 299 300 304 public DataContext() { 305 this(null, null); 306 } 307 308 316 public DataContext(QueryEngine parent, ObjectStore objectStore) { 317 setParent(parent); 318 319 this.objectStore = objectStore; 320 this.setTransactionEventsEnabled(transactionEventsEnabledDefault); 321 this.usingSharedSnaphsotCache = getParentDataDomain() != null 322 && objectStore.getDataRowCache() == getParentDataDomain() 323 .getSharedSnapshotCache(); 324 } 325 326 329 private final void awakeFromDeserialization() { 330 if (parent == null && lazyInitParentDomainName != null) { 331 332 DataDomain domain = Configuration.getSharedConfiguration().getDomain( 333 lazyInitParentDomainName); 334 335 this.parent = domain; 336 337 if (isUsingSharedSnapshotCache() && domain != null) { 338 this.objectStore.setDataRowCache(domain.getSharedSnapshotCache()); 339 } 340 } 341 } 342 343 348 protected Map getUserProperties() { 349 if (userProperties == null) { 353 userProperties = new HashMap (); 354 } 355 356 return userProperties; 357 } 358 359 365 public Object getUserProperty(String key) { 366 return getUserProperties().get(key); 367 } 368 369 375 public void setUserProperty(String key, Object value) { 376 getUserProperties().put(key, value); 377 } 378 379 380 384 public QueryEngine getParent() { 385 awakeFromDeserialization(); 386 return parent; 387 } 388 389 398 public DataDomain getParentDataDomain() { 399 return (DataDomain) getParent(); 400 } 401 402 405 public void setParent(QueryEngine parent) { 406 this.parent = parent; 407 } 408 409 415 public void setDelegate(DataContextDelegate delegate) { 416 this.delegate = delegate; 417 } 418 419 424 public DataContextDelegate getDelegate() { 425 return delegate; 426 } 427 428 434 DataContextDelegate nonNullDelegate() { 435 return (delegate != null) ? delegate : DataContext.defaultDelegate; 436 } 437 438 441 public ObjectStore getObjectStore() { 442 awakeFromDeserialization(); 443 return objectStore; 444 } 445 446 450 public boolean hasChanges() { 451 return getObjectStore().hasChanges(); 452 } 453 454 458 public Collection newObjects() { 459 return getObjectStore().objectsInState(PersistenceState.NEW); 460 } 461 462 466 public Collection deletedObjects() { 467 return getObjectStore().objectsInState(PersistenceState.DELETED); 468 } 469 470 474 public Collection modifiedObjects() { 475 return getObjectStore().objectsInState(PersistenceState.MODIFIED); 476 } 477 478 483 public DataObject registeredObject(ObjectId oid) { 484 synchronized (getObjectStore()) { 486 DataObject obj = objectStore.getObject(oid); 487 if (obj == null) { 488 try { 489 obj = DataContext.newDataObject(oid.getObjectClass().getName()); 491 } 492 catch (Exception ex) { 493 String entity = (oid != null) ? getEntityResolver().lookupObjEntity( 494 oid.getObjectClass()).getName() : null; 495 throw new CayenneRuntimeException( 496 "Error creating object for entity '" + entity + "'.", 497 ex); 498 } 499 500 obj.setObjectId(oid); 501 obj.setPersistenceState(PersistenceState.HOLLOW); 502 obj.setDataContext(this); 503 objectStore.addObject(obj); 504 } 505 return obj; 506 } 507 } 508 509 514 public DataRow currentSnapshot(DataObject anObject) { 515 ObjEntity entity = getEntityResolver().lookupObjEntity(anObject); 516 517 if (anObject.getPersistenceState() == PersistenceState.HOLLOW 519 && anObject.getDataContext() != null) { 520 521 ObjectId id = anObject.getObjectId(); 522 return getObjectStore().getSnapshot(id, this); 523 } 524 525 DataRow snapshot = new DataRow(10); 526 527 Iterator attributes = entity.getAttributeMap().entrySet().iterator(); 528 while (attributes.hasNext()) { 529 Map.Entry entry = (Map.Entry ) attributes.next(); 530 String attrName = (String ) entry.getKey(); 531 ObjAttribute objAttr = (ObjAttribute) entry.getValue(); 532 533 snapshot.put(objAttr.getDbAttributePath(), anObject 535 .readPropertyDirectly(attrName)); 536 } 537 538 Iterator relationships = entity.getRelationshipMap().entrySet().iterator(); 539 while (relationships.hasNext()) { 540 Map.Entry entry = (Map.Entry ) relationships.next(); 541 ObjRelationship rel = (ObjRelationship) entry.getValue(); 542 543 if (rel.isSourceIndependentFromTargetChange()) { 545 continue; 546 } 547 548 Object targetObject = anObject.readPropertyDirectly(rel.getName()); 549 if (targetObject == null) { 550 continue; 551 } 552 553 if (targetObject instanceof Fault) { 556 DataRow storedSnapshot = getObjectStore().getSnapshot( 557 anObject.getObjectId(), 558 this); 559 if (storedSnapshot == null) { 560 throw new CayenneRuntimeException( 561 "No matching objects found for ObjectId " 562 + anObject.getObjectId() 563 + ". Object may have been deleted externally."); 564 } 565 566 DbRelationship dbRel = (DbRelationship) rel.getDbRelationships().get(0); 567 Iterator joins = dbRel.getJoins().iterator(); 568 while (joins.hasNext()) { 569 DbJoin join = (DbJoin) joins.next(); 570 String key = join.getSourceName(); 571 snapshot.put(key, storedSnapshot.get(key)); 572 } 573 574 continue; 575 } 576 577 DataObject target = (DataObject) targetObject; 580 Map idParts = target.getObjectId().getIdSnapshot(); 581 582 if (idParts.isEmpty()) { 584 continue; 585 } 586 587 DbRelationship dbRel = (DbRelationship) rel.getDbRelationships().get(0); 588 Map fk = dbRel.srcFkSnapshotWithTargetSnapshot(idParts); 589 snapshot.putAll(fk); 590 } 591 592 Map thisIdParts = anObject.getObjectId().getIdSnapshot(); 597 if (thisIdParts != null) { 598 599 Iterator idIterator = thisIdParts.entrySet().iterator(); 601 while (idIterator.hasNext()) { 602 Map.Entry entry = (Map.Entry ) idIterator.next(); 603 Object nextKey = entry.getKey(); 604 if (!snapshot.containsKey(nextKey)) { 605 snapshot.put(nextKey, entry.getValue()); 606 } 607 } 608 } 609 610 return snapshot; 611 } 612 613 620 public List localObjects(List objects) { 621 List localObjects = new ArrayList (objects.size()); 622 623 Iterator it = objects.iterator(); 624 while (it.hasNext()) { 625 DataObject object = (DataObject) it.next(); 626 627 if (object.getPersistenceState() != PersistenceState.COMMITTED 629 && object.getPersistenceState() != PersistenceState.HOLLOW) { 630 throw new CayenneRuntimeException( 631 "Only COMMITTED and HOLLOW objects can be transferred between contexts. " 632 + "Invalid object state '" 633 + PersistenceState.persistenceStateName(object 634 .getPersistenceState()) 635 + "', ObjectId: " 636 + object.getObjectId()); 637 } 638 639 DataObject localObject = (object.getDataContext() != this) 640 ? registeredObject(object.getObjectId()) 641 : object; 642 localObjects.add(localObject); 643 } 644 645 return localObjects; 646 } 647 648 653 public List objectsFromDataRows( 654 ObjEntity entity, 655 List dataRows, 656 boolean refresh, 657 boolean resolveInheritanceHierarchy) { 658 659 return new DataContextObjectFactory(this, refresh, resolveInheritanceHierarchy) 660 .objectsFromDataRows(entity, dataRows); 661 } 662 663 672 public List objectsFromDataRows( 673 Class objectClass, 674 List dataRows, 675 boolean refresh, 676 boolean resolveInheritanceHierarchy) { 677 ObjEntity entity = this.getEntityResolver().lookupObjEntity(objectClass); 678 return objectsFromDataRows(entity, dataRows, refresh, resolveInheritanceHierarchy); 679 } 680 681 688 public DataObject objectFromDataRow( 689 Class objectClass, 690 DataRow dataRow, 691 boolean refresh) { 692 List list = objectsFromDataRows( 693 objectClass, 694 Collections.singletonList(dataRow), 695 refresh, 696 true); 697 return (DataObject) list.get(0); 698 } 699 700 701 712 public DataObject createAndRegisterNewObject(String objEntityName) { 713 ObjEntity entity = this.getEntityResolver().getObjEntity(objEntityName); 714 715 if (entity == null) { 716 throw new IllegalArgumentException ("Invalid entity name: " + objEntityName); 717 } 718 719 String objClassName = entity.getClassName(); 720 DataObject dataObject = null; 721 try { 722 dataObject = DataContext.newDataObject(objClassName); 723 } 724 catch (Exception ex) { 725 throw new CayenneRuntimeException("Error instantiating object.", ex); 726 } 727 728 registerNewObjectWithEntity(dataObject, entity); 729 return dataObject; 730 } 731 732 738 public DataObject createAndRegisterNewObject(Class objectClass) { 739 if (objectClass == null) { 740 throw new NullPointerException ("DataObject class can't be null."); 741 } 742 743 ObjEntity entity = getEntityResolver().lookupObjEntity(objectClass); 744 if (entity == null) { 745 throw new IllegalArgumentException ("Class is not mapped with Cayenne: " 746 + objectClass.getName()); 747 } 748 749 DataObject dataObject = null; 750 try { 751 dataObject = (DataObject) objectClass.newInstance(); 752 } 753 catch (Exception ex) { 754 throw new CayenneRuntimeException("Error instantiating object.", ex); 755 } 756 757 registerNewObjectWithEntity(dataObject, entity); 758 return dataObject; 759 } 760 761 762 767 public void registerNewObject(DataObject dataObject) { 768 if (dataObject == null) { 769 throw new NullPointerException ("Can't register null object."); 770 } 771 772 if (dataObject.getObjectId() != null) { 774 if (dataObject.getDataContext() == this) { 775 return; 777 } 778 else if (dataObject.getDataContext() != null) { 779 throw new IllegalStateException ( 780 "DataObject is already registered with another DataContext. Try using 'localObjects()' instead."); 781 } 782 } 783 784 ObjEntity entity = getEntityResolver().lookupObjEntity(dataObject); 785 if (entity == null) { 786 throw new IllegalArgumentException ( 787 "Can't find ObjEntity for DataObject class: " 788 + dataObject.getClass().getName() 789 + ", class is likely not mapped."); 790 } 791 792 registerNewObjectWithEntity(dataObject, entity); 793 } 794 795 private void registerNewObjectWithEntity(DataObject dataObject, ObjEntity objEntity) { 796 799 if (dataObject.getObjectId() == null) { 800 dataObject.setObjectId(new TempObjectId(dataObject.getClass())); 801 } 802 803 Iterator it = objEntity.getRelationships().iterator(); 805 while (it.hasNext()) { 806 ObjRelationship rel = (ObjRelationship) it.next(); 807 if (rel.isToMany()) { 808 dataObject.writePropertyDirectly(rel.getName(), Fault.getToManyFault()); 809 } 810 } 811 812 getObjectStore().addObject(dataObject); 813 dataObject.setDataContext(this); 814 dataObject.setPersistenceState(PersistenceState.NEW); 815 } 816 817 822 public void unregisterObjects(Collection dataObjects) { 823 getObjectStore().objectsUnregistered(dataObjects); 824 } 825 826 831 public void invalidateObjects(Collection dataObjects) { 832 getObjectStore().objectsInvalidated(dataObjects); 833 } 834 835 |