1 56 package org.objectstyle.cayenne.access; 57 58 import java.util.ArrayList ; 59 import java.util.Arrays ; 60 import java.util.Collection ; 61 import java.util.Collections ; 62 import java.util.HashMap ; 63 import java.util.HashSet ; 64 import java.util.Iterator ; 65 import java.util.List ; 66 import java.util.Map ; 67 import java.util.Set ; 68 69 import org.apache.commons.collections.map.LinkedMap; 70 import org.objectstyle.cayenne.CayenneException; 71 import org.objectstyle.cayenne.CayenneRuntimeException; 72 import org.objectstyle.cayenne.DataObject; 73 import org.objectstyle.cayenne.ObjectContext; 74 import org.objectstyle.cayenne.ObjectId; 75 import org.objectstyle.cayenne.PersistenceState; 76 import org.objectstyle.cayenne.access.util.BatchQueryUtils; 77 import org.objectstyle.cayenne.access.util.PrimaryKeyHelper; 78 import org.objectstyle.cayenne.graph.GraphChangeHandler; 79 import org.objectstyle.cayenne.map.DbAttribute; 80 import org.objectstyle.cayenne.map.DbEntity; 81 import org.objectstyle.cayenne.map.DbJoin; 82 import org.objectstyle.cayenne.map.DbRelationship; 83 import org.objectstyle.cayenne.map.EntitySorter; 84 import org.objectstyle.cayenne.map.ObjAttribute; 85 import org.objectstyle.cayenne.map.ObjEntity; 86 import org.objectstyle.cayenne.map.ObjRelationship; 87 import org.objectstyle.cayenne.query.DeleteBatchQuery; 88 import org.objectstyle.cayenne.query.InsertBatchQuery; 89 import org.objectstyle.cayenne.query.Query; 90 import org.objectstyle.cayenne.query.UpdateBatchQuery; 91 92 99 100 class DataDomainCommitAction { 112 113 DataDomain domain; 114 115 Map newObjectsByObjEntity; 116 Map objectsToDeleteByObjEntity; 117 Map objectsToUpdateByObjEntity; 118 List objEntitiesToInsert; 119 List objEntitiesToDelete; 120 List objEntitiesToUpdate; 121 List nodeHelpers; 122 123 DataDomainCommitAction(DataDomain domain) { 124 this.domain = domain; 125 } 126 127 130 void commit(ObjectContext context, GraphChangeHandler commitChangeCallback) { 131 synchronized (domain.getSharedSnapshotCache()) { 132 Collection uncommitted = context.uncommittedObjects(); 133 categorizeObjects(uncommitted); 134 createPrimaryKeys(); 135 136 for (Iterator i = nodeHelpers.iterator(); i.hasNext();) { 137 DataNodeCommitAction nodeHelper = (DataNodeCommitAction) i.next(); 138 prepareInsertQueries(nodeHelper); 139 prepareFlattenedQueries(nodeHelper, nodeHelper 140 .getFlattenedInsertQueries()); 141 142 prepareUpdateQueries(nodeHelper); 143 144 prepareFlattenedQueries(nodeHelper, nodeHelper 145 .getFlattenedDeleteQueries()); 146 147 prepareDeleteQueries(nodeHelper); 148 } 149 150 OperationObserver observer = new DataDomainCommitObserver(); 151 Transaction transaction = domain.createTransaction(); 152 153 transaction.begin(); 155 156 try { 157 Iterator i = nodeHelpers.iterator(); 158 while (i.hasNext()) { 159 DataNodeCommitAction nodeHelper = (DataNodeCommitAction) i.next(); 160 List queries = nodeHelper.getQueries(); 161 162 if (queries.size() > 0) { 163 nodeHelper.getNode().performQueries( 165 queries, 166 observer, 167 transaction); 168 } 169 } 170 171 transaction.commit(); 173 } 174 catch (Throwable th) { 175 try { 176 transaction.rollback(); 178 } 179 catch (Throwable rollbackTh) { 180 } 182 183 throw new CayenneRuntimeException("Transaction was rolledback.", th); 184 } 185 186 if (commitChangeCallback != null) { 188 Iterator it = uncommitted.iterator(); 189 while (it.hasNext()) { 190 DataObject object = (DataObject) it.next(); 191 ObjectId id = object.getObjectId(); 192 if (id.isReplacementIdAttached()) { 193 commitChangeCallback.nodeIdChanged(id, id.createReplacementId()); 194 } 195 } 196 } 197 } 198 } 199 200 private void prepareInsertQueries(DataNodeCommitAction commitHelper) { 201 202 List entities = commitHelper.getObjEntitiesForInsert(); 203 if (entities.isEmpty()) { 204 return; 205 } 206 207 boolean supportsGeneratedKeys = commitHelper 208 .getNode() 209 .getAdapter() 210 .supportsGeneratedKeys(); 211 List dbEntities = new ArrayList (entities.size()); 212 Map objEntitiesByDbEntity = new HashMap (entities.size()); 213 groupObjEntitiesBySpannedDbEntities(dbEntities, objEntitiesByDbEntity, entities); 214 215 EntitySorter sorter = commitHelper.getNode().getEntitySorter(); 216 sorter.sortDbEntities(dbEntities, false); 217 218 Iterator i = dbEntities.iterator(); 219 while (i.hasNext()) { 220 DbEntity dbEntity = (DbEntity) i.next(); 221 List objEntitiesForDbEntity = (List ) objEntitiesByDbEntity.get(dbEntity); 222 223 InsertBatchQuery batch = new InsertBatchQuery(dbEntity, 27); 224 225 for (Iterator j = objEntitiesForDbEntity.iterator(); j.hasNext();) { 226 ObjEntity entity = (ObjEntity) j.next(); 227 boolean isMasterDbEntity = (entity.getDbEntity() == dbEntity); 228 DbRelationship masterDependentDbRel = (isMasterDbEntity 229 ? null 230 : findMasterToDependentDbRelationship( 231 entity.getDbEntity(), 232 dbEntity)); 233 234 List objects = (List ) newObjectsByObjEntity.get(entity.getClassName()); 235 236 if (entity.isReadOnly() && objects.size() > 0) { 238 throw attemptToCommitReadOnlyEntity(objects.get(0).getClass(), entity); 239 } 240 241 if (isMasterDbEntity) { 242 sorter.sortObjectsForEntity(entity, objects, false); 243 } 244 245 for (Iterator k = objects.iterator(); k.hasNext();) { 246 DataObject o = (DataObject) k.next(); 247 Map snapshot = BatchQueryUtils.buildSnapshotForInsert( 248 entity, 249 o, 250 masterDependentDbRel, 251 supportsGeneratedKeys); 252 batch.add(snapshot, o.getObjectId()); 253 } 254 } 255 commitHelper.getQueries().add(batch); 256 } 257 } 258 259 private void prepareDeleteQueries(DataNodeCommitAction commitHelper) { 260 261 List entities = commitHelper.getObjEntitiesForDelete(); 262 if (entities.isEmpty()) { 263 return; 264 } 265 266 List dbEntities = new ArrayList (entities.size()); 267 Map objEntitiesByDbEntity = new HashMap (entities.size()); 268 groupObjEntitiesBySpannedDbEntities(dbEntities, objEntitiesByDbEntity, entities); 269 270 EntitySorter sorter = commitHelper.getNode().getEntitySorter(); 271 sorter.sortDbEntities(dbEntities, true); 272 273 for (Iterator i = dbEntities.iterator(); i.hasNext();) { 274 DbEntity dbEntity = (DbEntity) i.next(); 275 List objEntitiesForDbEntity = (List ) objEntitiesByDbEntity.get(dbEntity); 276 Map batches = new LinkedMap(); 277 278 for (Iterator j = objEntitiesForDbEntity.iterator(); j.hasNext();) { 279 ObjEntity entity = (ObjEntity) j.next(); 280 281 boolean optimisticLocking = (ObjEntity.LOCK_TYPE_OPTIMISTIC == entity 283 .getLockType()); 284 285 List qualifierAttributes = qualifierAttributes(entity, optimisticLocking); 286 287 boolean isRootDbEntity = (entity.getDbEntity() == dbEntity); 288 DbRelationship masterDependentDbRel = (isRootDbEntity 289 ? null 290 : findMasterToDependentDbRelationship( 291 entity.getDbEntity(), 292 dbEntity)); 293 294 List objects = (List ) objectsToDeleteByObjEntity.get(entity 295 .getClassName()); 296 297 if (entity.isReadOnly() && objects.size() > 0) { 299 throw attemptToCommitReadOnlyEntity(objects.get(0).getClass(), entity); 300 } 301 302 if (isRootDbEntity) { 303 sorter.sortObjectsForEntity(entity, objects, true); 304 } 305 306 for (Iterator k = objects.iterator(); k.hasNext();) { 307 DataObject o = (DataObject) k.next(); 308 309 Map idSnapshot = o.getObjectId().getIdSnapshot(); 311 312 if (idSnapshot == null || idSnapshot.isEmpty()) { 313 continue; 315 } 316 317 if (!isRootDbEntity && masterDependentDbRel != null) { 318 idSnapshot = masterDependentDbRel 319 .targetPkSnapshotWithSrcSnapshot(idSnapshot); 320 } 321 322 Map qualifierSnapshot = idSnapshot; 323 if (optimisticLocking) { 324 qualifierSnapshot = new HashMap (qualifierSnapshot); 326 appendOptimisticLockingAttributes( 327 qualifierSnapshot, 328 o, 329 qualifierAttributes); 330 } 331 332 Set nullQualifierNames = new HashSet (); 334 Iterator it = qualifierSnapshot.entrySet().iterator(); 335 while (it.hasNext()) { 336 Map.Entry entry = (Map.Entry ) it.next(); 337 if (entry.getValue() == null) { 338 nullQualifierNames.add(entry.getKey()); 339 } 340 } 341 342 List batchKey = Arrays.asList(new Object [] { 343 nullQualifierNames 344 }); 345 346 DeleteBatchQuery batch = (DeleteBatchQuery) batches.get(batchKey); 347 if (batch == null) { 348 batch = new DeleteBatchQuery( 349 dbEntity, 350 qualifierAttributes, 351 nullQualifierNames, 352 27); 353 354 batch.setUsingOptimisticLocking(optimisticLocking); 355 batches.put(batchKey, batch); 356 } 357 358 batch.add(qualifierSnapshot); 359 360 } 361 } 362 commitHelper.getQueries().addAll(batches.values()); 363 } 364 } 365 366 private void prepareUpdateQueries(DataNodeCommitAction commitHelper) { 367 List entities = commitHelper.getObjEntitiesForUpdate(); 368 if (entities.isEmpty()) { 369 return; 370 } 371 372 List dbEntities = new ArrayList (entities.size()); 373 Map objEntitiesByDbEntity = new HashMap (entities.size()); 374 groupObjEntitiesBySpannedDbEntities(dbEntities, objEntitiesByDbEntity, entities); 375 376 for (Iterator i = dbEntities.iterator(); i.hasNext();) { 377 DbEntity dbEntity = (DbEntity) i.next(); 378 List objEntitiesForDbEntity = (List ) objEntitiesByDbEntity.get(dbEntity); 379 Map batches = new LinkedMap(); 380 381 for (Iterator j = objEntitiesForDbEntity.iterator(); j.hasNext();) { 382 ObjEntity entity = (ObjEntity) j.next(); 383 384 boolean optimisticLocking = (ObjEntity.LOCK_TYPE_OPTIMISTIC == entity 386 .getLockType()); 387 388 List qualifierAttributes = qualifierAttributes(entity, optimisticLocking); 389 390 boolean isRootDbEntity = entity.getDbEntity() == dbEntity; 391 392 DbRelationship masterDependentDbRel = (isRootDbEntity) 393 ? null 394 : findMasterToDependentDbRelationship( 395 entity.getDbEntity(), 396 dbEntity); 397 List objects = (List ) objectsToUpdateByObjEntity.get(entity 398 .getClassName()); 399 400 for (Iterator k = objects.iterator(); k.hasNext();) { 401 DataObject o = (DataObject) k.next(); 402 403 Map snapshot = BatchQueryUtils.buildSnapshotForUpdate( 404 entity, 405 o, 406 masterDependentDbRel); 407 408 if (snapshot.isEmpty()) { 411 o.setPersistenceState(PersistenceState.COMMITTED); 412 continue; 413 } 414 415 if (entity.isReadOnly()) { 418 throw attemptToCommitReadOnlyEntity(o.getClass(), entity); 419 } 420 421 Map idSnapshot = o.getObjectId().getIdSnapshot(); 423 424 if (!isRootDbEntity && masterDependentDbRel != null) { 425 idSnapshot = masterDependentDbRel 426 .targetPkSnapshotWithSrcSnapshot(idSnapshot); 427 } 428 429 Map qualifierSnapshot = idSnapshot; 430 if (optimisticLocking) { 431 qualifierSnapshot = new HashMap (qualifierSnapshot); 433 appendOptimisticLockingAttributes( 434 qualifierSnapshot, 435 o, 436 qualifierAttributes); 437 } 438 439 Set snapshotSet = snapshot.keySet(); 441 Set nullQualifierNames = new HashSet (); 442 Iterator it = qualifierSnapshot.entrySet().iterator(); 443 while (it.hasNext()) { 444 Map.Entry entry = (Map.Entry ) it.next(); 445 if (entry.getValue() == null) { 446 nullQualifierNames.add(entry.getKey()); 447 } 448 } 449 450 List batchKey = Arrays.asList(new Object [] { 451 snapshotSet, nullQualifierNames 452 }); 453 454 UpdateBatchQuery batch = (UpdateBatchQuery) batches.get(batchKey); 455 if (batch == null) { 456 batch = new UpdateBatchQuery( 457 dbEntity, 458 qualifierAttributes, 459 updatedAttributes(dbEntity, snapshot), 460 nullQualifierNames, 461 10); 462 463 batch.setUsingOptimisticLocking(optimisticLocking); 464 batches.put(batchKey, batch); 465 } 466 467 batch.add(qualifierSnapshot, snapshot); 468 469 if (isRootDbEntity) { 470 updateId( 471 idSnapshot, 472 o.getObjectId().getReplacementIdMap(), 473 snapshot); 474 } 475 } 476 } 477 commitHelper.getQueries().addAll(batches.values()); 478 } 479 } 480 481 484 private List qualifierAttributes(ObjEntity entity, boolean optimisticLocking) { 485 if (!optimisticLocking) { 486 return entity.getDbEntity().getPrimaryKey(); 487 } 488 489 List attributes = new ArrayList (entity.getDbEntity().getPrimaryKey()); 490 491 Iterator attributeIt = entity.getAttributes().iterator(); 492 while (attributeIt.hasNext()) { 493 ObjAttribute attribute = (ObjAttribute) attributeIt.next(); 494 495 if (attribute.isUsedForLocking()) { 496 DbAttribute dbAttribute = (DbAttribute) attribute 498 .getDbPathIterator() 499 .next(); 500 501 if (!attributes.contains(dbAttribute)) { 502 attributes.add(dbAttribute); 503 } 504 } 505 } 506 507 Iterator relationshipIt = entity.getRelationships().iterator(); 508 while (relationshipIt.hasNext()) { 509 ObjRelationship relationship = (ObjRelationship) relationshipIt.next(); 510 511 if (relationship.isUsedForLocking()) { 512 DbRelationship dbRelationship = (DbRelationship) relationship 514 .getDbRelationships() 515 .get(0); 516 517 Iterator joinsIterator = dbRelationship.getJoins().iterator(); 518 while (joinsIterator.hasNext()) { 519 DbJoin dbAttrPair = (DbJoin) joinsIterator.next(); 520 DbAttribute dbAttribute = dbAttrPair.getSource(); 521 if (!attributes.contains(dbAttribute)) { 522 attributes.add(dbAttribute); 523 } 524 } 525 } 526 } 527 528 return attributes; 529 } 530 531 537 private List updatedAttributes(DbEntity entity, Map updatedSnapshot) { 538 List attributes = new ArrayList (updatedSnapshot.size()); 539 Map entityAttributes = entity.getAttributeMap(); 540 541 Iterator it = updatedSnapshot.keySet().iterator(); 542 while (it.hasNext()) { 543 Object name = it.next(); 544 attributes.add(entityAttributes.get(name)); 545 } 546 547 return attributes; 548 } 549 550 553 private void appendOptimisticLockingAttributes( 554 Map qualifierSnapshot, 555 DataObject dataObject, 556 List qualifierAttributes) { 557 558 Map snapshot = null; 559 560 Iterator it = qualifierAttributes.iterator(); 561 while (it.hasNext()) { 562 DbAttribute attribute = (DbAttribute) it.next(); 563 String name = attribute.getName(); 564 if (!qualifierSnapshot.containsKey(name)) { 565 566 if (snapshot == null) { 568 snapshot = dataObject 569 .getDataContext() 570 .getObjectStore() 571 .getCachedSnapshot(dataObject.getObjectId()); 572 573 if (snapshot == null) { 575 throw new CayenneRuntimeException( 576 "Can't build qualifier for optimistic locking, " 577 + "no snapshot for id " 578 + dataObject.getObjectId()); 579 } 580 } 581 582 qualifierSnapshot.put(name, snapshot.get(name)); 583 } 584 } 585 } 586 587 private void createPrimaryKeys() { 588 589 PrimaryKeyHelper pkHelper = domain.getPrimaryKeyHelper(); 590 591 Collections.sort(objEntitiesToInsert, pkHelper.getObjEntityComparator()); 592 Iterator i = objEntitiesToInsert.iterator(); 593 while (i.hasNext()) { 594 ObjEntity currentEntity = (ObjEntity) i.next(); 595 List dataObjects = (List ) newObjectsByObjEntity.get(currentEntity 596 .getClassName()); 597 598 try { 599 pkHelper.createPermIdsForObjEntity(currentEntity, dataObjects); 600 } 601 catch (CayenneException ex) { 602 throw new CayenneRuntimeException( 603 "Error creating primary key for entity " + currentEntity, 604 ex); 605 } 606 } 607 } 608 609 612 private void categorizeObjects(Collection uncommittedObjects) { 613 this.nodeHelpers = new ArrayList (); 614 615 newObjectsByObjEntity = new HashMap (); 616 objectsToDeleteByObjEntity = new HashMap (); 617 objectsToUpdateByObjEntity = new HashMap (); 618 objEntitiesToInsert = new ArrayList (); 619 objEntitiesToDelete = new ArrayList (); 620 objEntitiesToUpdate = new ArrayList (); 621 622 Iterator it = uncommittedObjects.iterator(); 623 while (it.hasNext()) { 624 DataObject nextObject = (DataObject) it.next(); 625 int objectState = nextObject.getPersistenceState(); 626 switch (objectState) { 627 case PersistenceState.NEW: 628 objectToInsert(nextObject); 629 break; 630 case PersistenceState.DELETED: 631 objectToDelete(nextObject); 632 break; 633 case PersistenceState.MODIFIED: 634 objectToUpdate(nextObject); 635 break; 636 } 637 } 638 } 639 640 private void objectToInsert(DataObject o) { 641 classifyByEntityAndNode( 642 o, 643 newObjectsByObjEntity, 644 objEntitiesToInsert, 645 DataNodeCommitAction.INSERT); 646 } 647 648 private void objectToDelete(DataObject o) { 649 classifyByEntityAndNode( 650 o, 651 objectsToDeleteByObjEntity, 652 objEntitiesToDelete, 653 DataNodeCommitAction.DELETE); 654 } 655 656 private void objectToUpdate(DataObject o) { 657 classifyByEntityAndNode( 658 o, 659 objectsToUpdateByObjEntity, 660 objEntitiesToUpdate, 661 DataNodeCommitAction.UPDATE); 662 } 663 664 private RuntimeException attemptToCommitReadOnlyEntity( 665 Class objectClass, 666 ObjEntity entity) { 667 String className = (objectClass != null) ? objectClass.getName() : "<null>"; 668 StringBuffer message = new StringBuffer (); 669 message 670 .append("Class '") 671 .append(className) 672 .append("' maps to a read-only entity"); 673 674 if (entity != null) { 675 message.append(" '").append(entity.getName()).append("'"); 676 } 677 678 message.append(". Can't commit changes."); 679 return new CayenneRuntimeException(message.toString()); 680 } 681 682 686 private void classifyByEntityAndNode( 687 DataObject o, 688 Map objectsByObjEntity, 689 List objEntities, 690 int operationType) { 691 692 Class objEntityClass = o.getObjectId().getObjectClass(); 693 ObjEntity entity = domain.getEntityResolver().lookupObjEntity(objEntityClass); 694 Collection objectsForObjEntity = (Collection ) objectsByObjEntity 695 .get(objEntityClass.getName()); 696 if (objectsForObjEntity == null) { 697 objEntities.add(entity); 698 DataNode responsibleNode = domain.lookupDataNode(entity.getDataMap()); 699 700 DataNodeCommitAction commitHelper = nodeHelper(responsibleNode); 701 702 commitHelper.addToEntityList(entity, operationType); 703 objectsForObjEntity = new ArrayList (); 704 objectsByObjEntity.put(objEntityClass.getName(), objectsForObjEntity); 705 } 706 objectsForObjEntity.add(o); 707 } 708 709 private void prepareFlattenedQueries( 710 DataNodeCommitAction commitHelper, 711 Map flattenedBatches) { 712 713 for (Iterator i = flattenedBatches.values().iterator(); i.hasNext();) { 714 commitHelper.addToQueries((Query) i.next()); 715 } 716 } 717 718 private void updateId(Map oldID, Map replacementID, Map updatedKeys) { 719 Iterator it = updatedKeys.entrySet().iterator(); 720 721 while (it.hasNext()) { 722 Map.Entry entry = (Map.Entry ) it.next(); 723 Object key = entry.getKey(); 724 725 if (oldID.containsKey(key) && !replacementID.containsKey(key)) { 726 replacementID.put(key, entry.getValue()); 727 } 728 } 729 } 730 731 private void groupObjEntitiesBySpannedDbEntities( 732 List dbEntities, 733 Map objEntitiesByDbEntity, 734 List objEntities) { 735 736 Iterator i = objEntities.iterator(); 737 while (i.hasNext()) { 738 ObjEntity objEntity = (ObjEntity) i.next(); 739 DbEntity dbEntity = objEntity.getDbEntity(); 740 741 List objEntitiesForDbEntity = (List ) objEntitiesByDbEntity.get(dbEntity); 742 if (objEntitiesForDbEntity == null) { 743 objEntitiesForDbEntity = new ArrayList (1); 744 dbEntities.add(dbEntity); 745 objEntitiesByDbEntity.put(dbEntity, objEntitiesForDbEntity); 746 } 747 if (!objEntitiesForDbEntity.contains(objEntity)) 748 objEntitiesForDbEntity.add(objEntity); 749 for (Iterator j = objEntity.getAttributeMap().values().iterator(); j 750 .hasNext();) { 751 ObjAttribute objAttribute = (ObjAttribute) j.next(); 752 if (!objAttribute.isCompound()) 753 continue; 754 dbEntity = (DbEntity) objAttribute.getDbAttribute().getEntity(); 755 objEntitiesForDbEntity = (List ) objEntitiesByDbEntity.get(dbEntity); 756 if (objEntitiesForDbEntity == null) { 757 objEntitiesForDbEntity = new ArrayList (1); 758 dbEntities.add(dbEntity); 759 objEntitiesByDbEntity.put(dbEntity, objEntitiesForDbEntity); 760 } 761 if (!objEntitiesForDbEntity.contains(objEntity)) 762 objEntitiesForDbEntity.add(objEntity); 763 } 764 } 765 } 766 767 private DbRelationship findMasterToDependentDbRelationship( 768 DbEntity masterDbEntity, 769 DbEntity dependentDbEntity) { 770 if (masterDbEntity.equals(dependentDbEntity)) 771 return null; 772 for (Iterator i = masterDbEntity.getRelationshipMap().values().iterator(); i 773 .hasNext();) { 774 DbRelationship rel = (DbRelationship) i.next(); 775 if (dependentDbEntity.equals(rel.getTargetEntity()) && rel.isToDependentPK()) 776 return rel; 777 } 778 return null; 779 } 780 781 785 private DataNodeCommitAction nodeHelper(DataNode node) { 786 787 DataNodeCommitAction helper = null; 788 Iterator it = nodeHelpers.iterator(); 789 while (it.hasNext()) { 790 DataNodeCommitAction itHelper = (DataNodeCommitAction) it.next(); 791 if (itHelper.getNode() == node) { 792 helper = itHelper; 793 break; 794 } 795 } 796 797 if (helper == null) { 798 helper = new DataNodeCommitAction(node); 799 nodeHelpers.add(helper); 800 } 801 802 return helper; 803 } 804 } 805 | Popular Tags |