001package systems.dmx.core.impl; 002 003import systems.dmx.core.Association; 004import systems.dmx.core.AssociationType; 005import systems.dmx.core.DMXObject; 006import systems.dmx.core.Topic; 007import systems.dmx.core.TopicType; 008import systems.dmx.core.model.AssociationModel; 009import systems.dmx.core.model.RoleModel; 010import systems.dmx.core.model.SimpleValue; 011import systems.dmx.core.model.TopicModel; 012import systems.dmx.core.service.accesscontrol.AccessControlException; 013import systems.dmx.core.storage.spi.DMXStorage; 014 015import java.util.ArrayList; 016import java.util.Iterator; 017import java.util.List; 018import java.util.logging.Logger; 019 020 021 022/** 023 * Storage vendor agnostic access control on top of vendor specific storage. 024 * 025 * 2 kinds of methods: 026 * - access controlled: get/create/update 027 * - direct DB access: fetch/store (as derived from storage impl) 028 * 029 * ### TODO: no instatiations here 030 * ### TODO: hold storage object in instance variable (instead deriving) to make direct DB access more explicit 031 */ 032public final class PersistenceLayer extends StorageDecorator { 033 034 // ------------------------------------------------------------------------------------------------------- Constants 035 036 private static final String URI_PREFIX_TOPIC_TYPE = "domain.project.topic_type_"; 037 private static final String URI_PREFIX_ASSOCIATION_TYPE = "domain.project.assoc_type_"; 038 private static final String URI_PREFIX_ROLE_TYPE = "domain.project.role_type_"; 039 040 // ---------------------------------------------------------------------------------------------- Instance Variables 041 042 TypeStorage typeStorage; 043 EventManager em; 044 ModelFactoryImpl mf; 045 046 private final Logger logger = Logger.getLogger(getClass().getName()); 047 048 // ---------------------------------------------------------------------------------------------------- Constructors 049 050 public PersistenceLayer(DMXStorage storage) { 051 super(storage); 052 // Note: mf must be initialzed before the type storage is instantiated 053 this.em = new EventManager(); 054 this.mf = (ModelFactoryImpl) storage.getModelFactory(); 055 this.typeStorage = new TypeStorage(this); 056 // 057 // Note: this is a constructor side effect. This is a cyclic dependency. This is nasty. 058 // ### TODO: explain why we do it. 059 mf.pl = this; 060 // 061 bootstrapTypeCache(); 062 } 063 064 // ----------------------------------------------------------------------------------------- Package Private Methods 065 066 067 068 // === Topics === 069 070 Topic getTopic(long topicId) { 071 try { 072 return checkReadAccessAndInstantiate(fetchTopic(topicId)); 073 } catch (Exception e) { 074 throw new RuntimeException("Fetching topic " + topicId + " failed", e); 075 } 076 } 077 078 TopicImpl getTopicByUri(String uri) { 079 return getTopicByValue("uri", new SimpleValue(uri)); 080 } 081 082 TopicImpl getTopicByValue(String key, SimpleValue value) { 083 try { 084 TopicModelImpl topic = fetchTopic(key, value); 085 return topic != null ? this.<TopicImpl>checkReadAccessAndInstantiate(topic) : null; 086 // Note: inside a conditional operator the type witness is required (at least in Java 6) 087 } catch (Exception e) { 088 throw new RuntimeException("Fetching topic failed (key=\"" + key + "\", value=\"" + value + "\")", e); 089 } 090 } 091 092 List<Topic> getTopicsByValue(String key, SimpleValue value) { 093 try { 094 return checkReadAccessAndInstantiate(fetchTopics(key, value)); 095 } catch (Exception e) { 096 throw new RuntimeException("Fetching topics failed (key=\"" + key + "\", value=\"" + value + "\")", e); 097 } 098 } 099 100 List<Topic> getTopicsByType(String topicTypeUri) { 101 try { 102 return checkReadAccessAndInstantiate(_getTopicType(topicTypeUri).getAllInstances()); 103 } catch (Exception e) { 104 throw new RuntimeException("Fetching topics by type failed (topicTypeUri=\"" + topicTypeUri + "\")", e); 105 } 106 } 107 108 List<Topic> searchTopics(String searchTerm, String fieldUri) { 109 try { 110 return checkReadAccessAndInstantiate(queryTopics(fieldUri, new SimpleValue(searchTerm))); 111 } catch (Exception e) { 112 throw new RuntimeException("Searching topics failed (searchTerm=\"" + searchTerm + "\", fieldUri=\"" + 113 fieldUri + "\")", e); 114 } 115 } 116 117 Iterable<Topic> getAllTopics() { 118 return new TopicIterable(this); 119 } 120 121 // --- 122 123 TopicImpl createTopic(TopicModelImpl model) { 124 try { 125 return updateValues(model, null).instantiate(); 126 } catch (Exception e) { 127 throw new RuntimeException("Creating topic failed, model=" + model, e); 128 } 129 } 130 131 // --- 132 133 TopicImpl _createTopic(TopicModelImpl model) { 134 return _createTopic(model, null); // uriPrefix=null 135 } 136 137 /** 138 * Creates a new topic in the DB. 139 * No child topics are created. 140 */ 141 private TopicImpl _createTopic(TopicModelImpl model, String uriPrefix) { 142 try { 143 em.fireEvent(CoreEvent.PRE_CREATE_TOPIC, model); 144 // 145 model.preCreate(); 146 // 147 // 1) store in DB 148 storeTopic(model); 149 if (model.getType().isSimple()) { 150 model.storeSimpleValue(); 151 } 152 createTopicInstantiation(model.getId(), model.getTypeUri()); 153 // 2) set default URI 154 // If no URI is given the topic gets a default URI based on its ID, if requested. 155 // Note: this must be done *after* the topic is stored. The ID is not known before. 156 // Note: in case no URI was given: once stored a topic's URI is empty (not null). 157 if (uriPrefix != null && model.getUri().equals("")) { 158 model.updateUri(uriPrefix + model.getId()); // update memory + DB 159 } 160 // 3) instantiate 161 TopicImpl topic = model.instantiate(); 162 // 163 model.postCreate(); 164 // 165 em.fireEvent(CoreEvent.POST_CREATE_TOPIC, topic); 166 return topic; 167 } catch (Exception e) { 168 throw new RuntimeException("Creating topic failed, model=" + model + ", uriPrefix=" + uriPrefix, e); 169 } 170 } 171 172 // --- 173 174 void updateTopic(TopicModelImpl updateModel) { 175 try { 176 TopicModelImpl model = fetchTopic(updateModel.getId()); 177 updateTopic(model, updateModel); 178 // 179 // Note: POST_UPDATE_TOPIC_REQUEST is fired only once per update request. 180 // On the other hand TopicModel's update() method is called multiple times while updating the child topics 181 // (see ChildTopicsModelImpl). 182 em.fireEvent(CoreEvent.POST_UPDATE_TOPIC_REQUEST, model.instantiate()); 183 } catch (Exception e) { 184 throw new RuntimeException("Fetching and updating topic " + updateModel.getId() + " failed", e); 185 } 186 } 187 188 void updateTopic(TopicModelImpl topic, TopicModelImpl updateModel) { 189 try { 190 topic.checkWriteAccess(); 191 topic.update(updateModel); 192 } catch (Exception e) { 193 throw new RuntimeException("Updating topic " + topic.getId() + " failed", e); 194 } 195 } 196 197 // --- 198 199 /** 200 * Convenience. 201 */ 202 void deleteTopic(long topicId) { 203 try { 204 deleteTopic(fetchTopic(topicId)); 205 } catch (Exception e) { 206 throw new RuntimeException("Fetching and deleting topic " + topicId + " failed", e); 207 } 208 } 209 210 void deleteTopic(TopicModelImpl topic) { 211 try { 212 topic.checkWriteAccess(); 213 topic.delete(); 214 } catch (Exception e) { 215 throw new RuntimeException("Deleting topic " + topic.getId() + " failed", e); 216 } 217 } 218 219 220 221 // === Associations === 222 223 Association getAssociation(long assocId) { 224 try { 225 return checkReadAccessAndInstantiate(fetchAssociation(assocId)); 226 } catch (Exception e) { 227 throw new RuntimeException("Fetching association " + assocId + " failed", e); 228 } 229 } 230 231 Association getAssociationByValue(String key, SimpleValue value) { 232 try { 233 AssociationModelImpl assoc = fetchAssociation(key, value); 234 return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null; 235 // Note: inside a conditional operator the type witness is required (at least in Java 6) 236 } catch (Exception e) { 237 throw new RuntimeException("Fetching association failed (key=\"" + key + "\", value=\"" + value + "\")", e); 238 } 239 } 240 241 List<Association> getAssociationsByValue(String key, SimpleValue value) { 242 try { 243 return checkReadAccessAndInstantiate(fetchAssociations(key, value)); 244 } catch (Exception e) { 245 throw new RuntimeException("Fetching associationss failed (key=\"" + key + "\", value=\"" + value + "\")", 246 e); 247 } 248 } 249 250 Association getAssociation(String assocTypeUri, long topic1Id, long topic2Id, String roleTypeUri1, 251 String roleTypeUri2) { 252 String info = "assocTypeUri=\"" + assocTypeUri + "\", topic1Id=" + topic1Id + ", topic2Id=" + topic2Id + 253 ", roleTypeUri1=\"" + roleTypeUri1 + "\", roleTypeUri2=\"" + roleTypeUri2 + "\""; 254 try { 255 AssociationModelImpl assoc = fetchAssociation(assocTypeUri, topic1Id, topic2Id, roleTypeUri1, roleTypeUri2); 256 return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null; 257 // Note: inside a conditional operator the type witness is required (at least in Java 6) 258 } catch (Exception e) { 259 throw new RuntimeException("Fetching association failed (" + info + ")", e); 260 } 261 } 262 263 Association getAssociationBetweenTopicAndAssociation(String assocTypeUri, long topicId, long assocId, 264 String topicRoleTypeUri, String assocRoleTypeUri) { 265 String info = "assocTypeUri=\"" + assocTypeUri + "\", topicId=" + topicId + ", assocId=" + assocId + 266 ", topicRoleTypeUri=\"" + topicRoleTypeUri + "\", assocRoleTypeUri=\"" + assocRoleTypeUri + "\""; 267 logger.info(info); 268 try { 269 AssociationModelImpl assoc = fetchAssociationBetweenTopicAndAssociation(assocTypeUri, topicId, assocId, 270 topicRoleTypeUri, assocRoleTypeUri); 271 return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null; 272 // Note: inside a conditional operator the type witness is required (at least in Java 6) 273 } catch (Exception e) { 274 throw new RuntimeException("Fetching association failed (" + info + ")", e); 275 } 276 } 277 278 // --- 279 280 List<Association> getAssociationsByType(String assocTypeUri) { 281 try { 282 return checkReadAccessAndInstantiate(_getAssociationType(assocTypeUri).getAllInstances()); 283 } catch (Exception e) { 284 throw new RuntimeException("Fetching associations by type failed (assocTypeUri=\"" + assocTypeUri + "\")", 285 e); 286 } 287 } 288 289 List<Association> getAssociations(long topic1Id, long topic2Id) { 290 return getAssociations(null, topic1Id, topic2Id); // assocTypeUri=null 291 } 292 293 List<Association> getAssociations(String assocTypeUri, long topic1Id, long topic2Id) { 294 return getAssociations(assocTypeUri, topic1Id, topic2Id, null, null); // roleTypeUri1=null, roleTypeUri2=null 295 } 296 297 List<Association> getAssociations(String assocTypeUri, long topic1Id, long topic2Id, String roleTypeUri1, 298 String roleTypeUri2) { 299 return instantiate(_getAssociations(assocTypeUri, topic1Id, topic2Id, roleTypeUri1, roleTypeUri2)); 300 } 301 302 /** 303 * Fetches from DB and filters READables. No instantiation. 304 * 305 * ### TODO: drop this. Use the new traversal methods instead. 306 */ 307 Iterable<AssociationModelImpl> _getAssociations(String assocTypeUri, long topic1Id, long topic2Id, 308 String roleTypeUri1, String roleTypeUri2) { 309 logger.fine("assocTypeUri=\"" + assocTypeUri + "\", topic1Id=" + topic1Id + ", topic2Id=" + topic2Id + 310 ", roleTypeUri1=\"" + roleTypeUri1 + "\", roleTypeUri2=\"" + roleTypeUri2 + "\""); 311 try { 312 return filterReadables(fetchAssociations(assocTypeUri, topic1Id, topic2Id, roleTypeUri1, roleTypeUri2)); 313 } catch (Exception e) { 314 throw new RuntimeException("Fetching associations between topics " + topic1Id + " and " + topic2Id + 315 " failed (assocTypeUri=\"" + assocTypeUri + "\", roleTypeUri1=\"" + roleTypeUri1 + 316 "\", roleTypeUri2=\"" + roleTypeUri2 + "\")", e); 317 } 318 } 319 320 // --- 321 322 Iterable<Association> getAllAssociations() { 323 return new AssociationIterable(this); 324 } 325 326 long[] getPlayerIds(long assocId) { 327 return fetchPlayerIds(assocId); 328 } 329 330 // --- 331 332 /** 333 * Convenience. 334 */ 335 AssociationImpl createAssociation(String typeUri, RoleModel roleModel1, RoleModel roleModel2) { 336 return createAssociation(mf.newAssociationModel(typeUri, roleModel1, roleModel2)); 337 } 338 339 /** 340 * Creates a new association in the DB. 341 */ 342 AssociationImpl createAssociation(AssociationModelImpl model) { 343 try { 344 em.fireEvent(CoreEvent.PRE_CREATE_ASSOCIATION, model); 345 // 346 model.preCreate(); 347 // 348 // 1) store in DB 349 storeAssociation(model); 350 updateValues(model, null); 351 createAssociationInstantiation(model.getId(), model.getTypeUri()); 352 // 2) instantiate 353 AssociationImpl assoc = model.instantiate(); 354 // 355 model.postCreate(); 356 // 357 em.fireEvent(CoreEvent.POST_CREATE_ASSOCIATION, assoc); 358 return assoc; 359 } catch (Exception e) { 360 throw new RuntimeException("Creating association failed, model=" + model, e); 361 } 362 } 363 364 // --- 365 366 void updateAssociation(AssociationModelImpl updateModel) { 367 try { 368 AssociationModelImpl model = fetchAssociation(updateModel.getId()); 369 updateAssociation(model, updateModel); 370 // 371 // Note: there is no possible POST_UPDATE_ASSOCIATION_REQUEST event to fire here (compare to updateTopic()). 372 // It would be equivalent to POST_UPDATE_ASSOCIATION. Per request exactly one association is updated. 373 // Its childs are always topics (never associations). 374 } catch (Exception e) { 375 throw new RuntimeException("Fetching and updating association " + updateModel.getId() + " failed", e); 376 } 377 } 378 379 void updateAssociation(AssociationModelImpl assoc, AssociationModelImpl updateModel) { 380 try { 381 checkAssociationWriteAccess(assoc.getId()); 382 assoc.update(updateModel); 383 } catch (Exception e) { 384 throw new RuntimeException("Updating association " + assoc.getId() + " failed, assoc=" + assoc + 385 ", updateModel=" + updateModel, e); 386 } 387 } 388 389 // --- 390 391 /** 392 * Convenience. 393 */ 394 void deleteAssociation(long assocId) { 395 try { 396 deleteAssociation(fetchAssociation(assocId)); 397 } catch (IllegalStateException e) { 398 // Note: fetchAssociation() might throw IllegalStateException and is no problem. 399 // This happens when the association is deleted already. In this case nothing needs to be performed. 400 // 401 // Compare to DMXObjectModelImpl.delete() 402 // TODO: introduce storage-vendor neutral DM exception. 403 if (e.getMessage().equals("Node[" + assocId + "] has been deleted in this tx")) { 404 logger.info("### Association " + assocId + " has already been deleted in this transaction. " + 405 "This can happen while delete-multi."); 406 } else { 407 throw e; 408 } 409 } catch (Exception e) { 410 throw new RuntimeException("Fetching and deleting association " + assocId + " failed", e); 411 } 412 } 413 414 void deleteAssociation(AssociationModelImpl assoc) { 415 try { 416 checkAssociationWriteAccess(assoc.getId()); 417 assoc.delete(); 418 } catch (Exception e) { 419 throw new RuntimeException("Deleting association " + assoc.getId() + " failed", e); 420 } 421 } 422 423 424 425 // === 426 427 void createTopicInstantiation(long topicId, String topicTypeUri) { 428 try { 429 AssociationModel assoc = mf.newAssociationModel("dmx.core.instantiation", 430 mf.newTopicRoleModel(topicTypeUri, "dmx.core.type"), 431 mf.newTopicRoleModel(topicId, "dmx.core.instance")); 432 storeAssociation(assoc); // direct storage calls used here ### explain 433 storeAssociationValue(assoc.getId(), assoc.getSimpleValue()); 434 createAssociationInstantiation(assoc.getId(), assoc.getTypeUri()); 435 } catch (Exception e) { 436 throw new RuntimeException("Associating topic " + topicId + " with topic type \"" + 437 topicTypeUri + "\" failed", e); 438 } 439 } 440 441 void createAssociationInstantiation(long assocId, String assocTypeUri) { 442 try { 443 AssociationModel assoc = mf.newAssociationModel("dmx.core.instantiation", 444 mf.newTopicRoleModel(assocTypeUri, "dmx.core.type"), 445 mf.newAssociationRoleModel(assocId, "dmx.core.instance")); 446 storeAssociation(assoc); // direct storage calls used here ### explain 447 storeAssociationValue(assoc.getId(), assoc.getSimpleValue()); 448 } catch (Exception e) { 449 throw new RuntimeException("Associating association " + assocId + " with association type \"" + 450 assocTypeUri + "\" failed", e); 451 } 452 } 453 454 455 456 // === Types === 457 458 TopicTypeImpl getTopicType(String uri) { 459 return checkReadAccessAndInstantiate(_getTopicType(uri)); 460 } 461 462 TopicTypeImpl getTopicTypeImplicitly(long topicId) { 463 checkTopicReadAccess(topicId); 464 return _getTopicType(typeUri(topicId)).instantiate(); 465 } 466 467 // --- 468 469 AssociationTypeImpl getAssociationType(String uri) { 470 return checkReadAccessAndInstantiate(_getAssociationType(uri)); 471 } 472 473 AssociationTypeImpl getAssociationTypeImplicitly(long assocId) { 474 checkAssociationReadAccess(assocId); 475 return _getAssociationType(typeUri(assocId)).instantiate(); 476 } 477 478 // --- 479 480 List<TopicType> getAllTopicTypes() { 481 try { 482 List<TopicType> topicTypes = new ArrayList(); 483 for (String uri : getTopicTypeUris()) { 484 topicTypes.add(_getTopicType(uri).instantiate()); 485 } 486 return topicTypes; 487 } catch (Exception e) { 488 throw new RuntimeException("Fetching all topic types failed", e); 489 } 490 } 491 492 List<AssociationType> getAllAssociationTypes() { 493 try { 494 List<AssociationType> assocTypes = new ArrayList(); 495 for (String uri : getAssociationTypeUris()) { 496 assocTypes.add(_getAssociationType(uri).instantiate()); 497 } 498 return assocTypes; 499 } catch (Exception e) { 500 throw new RuntimeException("Fetching all association types failed", e); 501 } 502 } 503 504 // --- 505 506 TopicTypeImpl createTopicType(TopicTypeModelImpl model) { 507 try { 508 em.fireEvent(CoreEvent.PRE_CREATE_TOPIC_TYPE, model); 509 // 510 // store in DB 511 createType(model, URI_PREFIX_TOPIC_TYPE); 512 // 513 TopicTypeImpl topicType = model.instantiate(); 514 em.fireEvent(CoreEvent.INTRODUCE_TOPIC_TYPE, topicType); 515 // 516 return topicType; 517 } catch (Exception e) { 518 throw new RuntimeException("Creating topic type \"" + model.getUri() + "\" failed", e); 519 } 520 } 521 522 AssociationTypeImpl createAssociationType(AssociationTypeModelImpl model) { 523 try { 524 em.fireEvent(CoreEvent.PRE_CREATE_ASSOCIATION_TYPE, model); 525 // 526 // store in DB 527 createType(model, URI_PREFIX_ASSOCIATION_TYPE); 528 // 529 AssociationTypeImpl assocType = model.instantiate(); 530 em.fireEvent(CoreEvent.INTRODUCE_ASSOCIATION_TYPE, assocType); 531 // 532 return assocType; 533 } catch (Exception e) { 534 throw new RuntimeException("Creating association type \"" + model.getUri() + "\" failed", e); 535 } 536 } 537 538 // --- 539 540 void updateTopicType(TopicTypeModelImpl updateModel) { 541 try { 542 // Note: type lookup is by ID. The URI might have changed, the ID does not. 543 TopicModelImpl topic = fetchTopic(updateModel.getId()); 544 topic.checkWriteAccess(); 545 _getTopicType(topic.getUri()).update(updateModel); 546 } catch (Exception e) { 547 throw new RuntimeException("Updating topic type failed, updateModel=" + updateModel, e); 548 } 549 } 550 551 void updateAssociationType(AssociationTypeModelImpl updateModel) { 552 try { 553 // Note: type lookup is by ID. The URI might have changed, the ID does not. 554 TopicModelImpl topic = fetchTopic(updateModel.getId()); 555 topic.checkWriteAccess(); 556 _getAssociationType(topic.getUri()).update(updateModel); 557 } catch (Exception e) { 558 throw new RuntimeException("Updating association type failed, updateModel=" + updateModel, e); 559 } 560 } 561 562 // --- 563 564 void deleteTopicType(String topicTypeUri) { 565 try { 566 TypeModelImpl type = _getTopicType(topicTypeUri); 567 type.checkWriteAccess(); 568 type.delete(); 569 // ### TODO: delete view config topics 570 } catch (Exception e) { 571 throw new RuntimeException("Deleting topic type \"" + topicTypeUri + "\" failed", e); 572 } 573 } 574 575 void deleteAssociationType(String assocTypeUri) { 576 try { 577 TypeModelImpl type = _getAssociationType(assocTypeUri); 578 type.checkWriteAccess(); 579 type.delete(); 580 // ### TODO: delete view config topics 581 } catch (Exception e) { 582 throw new RuntimeException("Deleting association type \"" + assocTypeUri + "\" failed", e); 583 } 584 } 585 586 // --- 587 588 Topic createRoleType(TopicModelImpl model) { 589 // check type URI argument 590 String typeUri = model.getTypeUri(); 591 if (typeUri == null) { 592 model.setTypeUri("dmx.core.role_type"); 593 } else { 594 if (!typeUri.equals("dmx.core.role_type")) { 595 throw new IllegalArgumentException("A role type is supposed to be of type \"dmx.core.role_type\" " + 596 "(found: \"" + typeUri + "\")"); 597 } 598 } 599 // 600 return _createTopic(model, URI_PREFIX_ROLE_TYPE); 601 } 602 603 // --- 604 605 /** 606 * Type cache direct access. No permission check. 607 */ 608 TopicTypeModelImpl _getTopicType(String uri) { 609 return typeStorage.getTopicType(uri); 610 } 611 612 /** 613 * Type cache direct access. No permission check. 614 */ 615 AssociationTypeModelImpl _getAssociationType(String uri) { 616 return typeStorage.getAssociationType(uri); 617 } 618 619 620 621 // === Generic Object === 622 623 DMXObject getObject(long id) { 624 return checkReadAccessAndInstantiate(fetchObject(id)); 625 } 626 627 628 629 // === Traversal === 630 631 // --- Topic Source --- 632 633 List<RelatedTopicModelImpl> getTopicRelatedTopics(long topicId, String assocTypeUri, String myRoleTypeUri, 634 String othersRoleTypeUri, String othersTopicTypeUri) { 635 return filterReadables(fetchTopicRelatedTopics(topicId, assocTypeUri, myRoleTypeUri, othersRoleTypeUri, 636 othersTopicTypeUri)); 637 } 638 639 List<RelatedTopicModelImpl> getTopicRelatedTopics(long topicId, List<String> assocTypeUris, String myRoleTypeUri, 640 String othersRoleTypeUri, String othersTopicTypeUri) { 641 return filterReadables(fetchTopicRelatedTopics(topicId, assocTypeUris, myRoleTypeUri, othersRoleTypeUri, 642 othersTopicTypeUri)); 643 } 644 645 RelatedAssociationModelImpl getTopicRelatedAssociation(long topicId, String assocTypeUri, String myRoleTypeUri, 646 String othersRoleTypeUri, String othersAssocTypeUri) { 647 RelatedAssociationModelImpl assoc = fetchTopicRelatedAssociation(topicId, assocTypeUri, myRoleTypeUri, 648 othersRoleTypeUri, othersAssocTypeUri); 649 return assoc != null ? checkReadAccess(assoc) : null; 650 } 651 652 List<RelatedAssociationModelImpl> getTopicRelatedAssociations(long topicId, String assocTypeUri, 653 String myRoleTypeUri, String othersRoleTypeUri, String othersAssocTypeUri) { 654 return filterReadables(fetchTopicRelatedAssociations(topicId, assocTypeUri, myRoleTypeUri, othersRoleTypeUri, 655 othersAssocTypeUri)); 656 } 657 658 List<AssociationModelImpl> getTopicAssociations(long topicId) { 659 return filterReadables(fetchTopicAssociations(topicId)); 660 } 661 662 // --- Association Source --- 663 664 List<RelatedTopicModelImpl> getAssociationRelatedTopics(long assocId, String assocTypeUri, 665 String myRoleTypeUri, String othersRoleTypeUri, String othersTopicTypeUri) { 666 return filterReadables(fetchAssociationRelatedTopics(assocId, assocTypeUri, myRoleTypeUri, othersRoleTypeUri, 667 othersTopicTypeUri)); 668 } 669 670 List<RelatedTopicModelImpl> getAssociationRelatedTopics(long assocId, List<String> assocTypeUris, 671 String myRoleTypeUri, String othersRoleTypeUri, String othersTopicTypeUri) { 672 return filterReadables(fetchAssociationRelatedTopics(assocId, assocTypeUris, myRoleTypeUri, othersRoleTypeUri, 673 othersTopicTypeUri)); 674 } 675 676 RelatedAssociationModelImpl getAssociationRelatedAssociation(long assocId, String assocTypeUri, 677 String myRoleTypeUri, String othersRoleTypeUri, String othersAssocTypeUri) { 678 RelatedAssociationModelImpl assoc = fetchAssociationRelatedAssociation(assocId, assocTypeUri, myRoleTypeUri, 679 othersRoleTypeUri, othersAssocTypeUri); 680 return assoc != null ? checkReadAccess(assoc) : null; 681 } 682 683 List<RelatedAssociationModelImpl> getAssociationRelatedAssociations(long assocId, String assocTypeUri, 684 String myRoleTypeUri, String othersRoleTypeUri, String othersAssocTypeUri) { 685 return filterReadables(fetchAssociationRelatedAssociations(assocId, assocTypeUri, myRoleTypeUri, 686 othersRoleTypeUri, othersAssocTypeUri)); 687 } 688 689 List<AssociationModelImpl> getAssociationAssociations(long assocId) { 690 return filterReadables(fetchAssociationAssociations(assocId)); 691 } 692 693 // --- Object Source --- 694 695 RelatedTopicModelImpl getRelatedTopic(long objectId, String assocTypeUri, String myRoleTypeUri, 696 String othersRoleTypeUri, String othersTopicTypeUri) { 697 RelatedTopicModelImpl topic = fetchRelatedTopic(objectId, assocTypeUri, myRoleTypeUri, othersRoleTypeUri, 698 othersTopicTypeUri); 699 return topic != null ? checkReadAccess(topic) : null; 700 } 701 702 List<RelatedTopicModelImpl> getRelatedTopics(long objectId, String assocTypeUri, String myRoleTypeUri, 703 String othersRoleTypeUri, String othersTopicTypeUri) { 704 return filterReadables(fetchRelatedTopics(objectId, assocTypeUri, myRoleTypeUri, othersRoleTypeUri, 705 othersTopicTypeUri)); 706 } 707 708 709 710 // === Properties === 711 712 List<Topic> getTopicsByProperty(String propUri, Object propValue) { 713 return checkReadAccessAndInstantiate(fetchTopicsByProperty(propUri, propValue)); 714 } 715 716 List<Topic> getTopicsByPropertyRange(String propUri, Number from, Number to) { 717 return checkReadAccessAndInstantiate(fetchTopicsByPropertyRange(propUri, from, to)); 718 } 719 720 List<Association> getAssociationsByProperty(String propUri, Object propValue) { 721 return checkReadAccessAndInstantiate(fetchAssociationsByProperty(propUri, propValue)); 722 } 723 724 List<Association> getAssociationsByPropertyRange(String propUri, Number from, Number to) { 725 return checkReadAccessAndInstantiate(fetchAssociationsByPropertyRange(propUri, from, to)); 726 } 727 728 // ------------------------------------------------------------------------------------------------- Private Methods 729 730 731 732 // === Access Control / Instantiation === 733 734 // These methods 1) instantiate objects from models, and 2) check the READ permission for each model. 735 // Call these methods when passing objects fetched from the DB to the user. 736 737 // ### TODO: drop this. No instatiations in this class. 738 <O> O checkReadAccessAndInstantiate(DMXObjectModelImpl model) { 739 return (O) checkReadAccess(model).instantiate(); 740 } 741 742 // ### TODO: drop this. No instatiations in this class. 743 <O> List<O> checkReadAccessAndInstantiate(List<? extends DMXObjectModelImpl> models) { 744 return instantiate(filterReadables(models)); 745 } 746 747 // --- 748 749 private <M extends DMXObjectModelImpl> List<M> filterReadables(List<M> models) { 750 Iterator<? extends DMXObjectModelImpl> i = models.iterator(); 751 while (i.hasNext()) { 752 if (!hasReadAccess(i.next())) { 753 i.remove(); 754 } 755 } 756 return models; 757 } 758 759 boolean hasReadAccess(DMXObjectModelImpl model) { 760 try { 761 checkReadAccess(model); 762 return true; 763 } catch (AccessControlException e) { 764 return false; 765 } 766 } 767 768 // --- 769 770 // TODO: add return to model's checkReadAccess() and drop this method? 771 <M extends DMXObjectModelImpl> M checkReadAccess(M model) { 772 model.checkReadAccess(); 773 return model; 774 } 775 776 // --- 777 778 /** 779 * @throws AccessControlException if the current user has no permission. 780 */ 781 void checkTopicReadAccess(long topicId) { 782 em.fireEvent(CoreEvent.CHECK_TOPIC_READ_ACCESS, topicId); 783 } 784 785 /** 786 * @throws AccessControlException if the current user has no permission. 787 */ 788 void checkAssociationReadAccess(long assocId) { 789 em.fireEvent(CoreEvent.CHECK_ASSOCIATION_READ_ACCESS, assocId); 790 } 791 792 // --- 793 794 /** 795 * @throws AccessControlException if the current user has no permission. 796 */ 797 void checkTopicWriteAccess(long topicId) { 798 em.fireEvent(CoreEvent.CHECK_TOPIC_WRITE_ACCESS, topicId); 799 } 800 801 /** 802 * @throws AccessControlException if the current user has no permission. 803 */ 804 void checkAssociationWriteAccess(long assocId) { 805 em.fireEvent(CoreEvent.CHECK_ASSOCIATION_WRITE_ACCESS, assocId); 806 } 807 808 809 810 // === Instantiation === 811 812 // ### TODO: move to kernel utils 813 <O> List<O> instantiate(Iterable<? extends DMXObjectModelImpl> models) { 814 List<O> objects = new ArrayList(); 815 for (DMXObjectModelImpl model : models) { 816 objects.add((O) model.instantiate()); 817 } 818 return objects; 819 } 820 821 822 823 // === 824 825 private List<String> getTopicTypeUris() { 826 try { 827 List<String> topicTypeUris = new ArrayList(); 828 // add meta types 829 topicTypeUris.add("dmx.core.topic_type"); 830 topicTypeUris.add("dmx.core.assoc_type"); 831 topicTypeUris.add("dmx.core.meta_type"); 832 // add regular types 833 for (TopicModel topicType : filterReadables(fetchTopics("typeUri", new SimpleValue( 834 "dmx.core.topic_type")))) { 835 topicTypeUris.add(topicType.getUri()); 836 } 837 return topicTypeUris; 838 } catch (Exception e) { 839 throw new RuntimeException("Fetching list of topic type URIs failed", e); 840 } 841 } 842 843 private List<String> getAssociationTypeUris() { 844 try { 845 List<String> assocTypeUris = new ArrayList(); 846 for (TopicModel assocType : filterReadables(fetchTopics("typeUri", new SimpleValue( 847 "dmx.core.assoc_type")))) { 848 assocTypeUris.add(assocType.getUri()); 849 } 850 return assocTypeUris; 851 } catch (Exception e) { 852 throw new RuntimeException("Fetching list of association type URIs failed", e); 853 } 854 } 855 856 // --- 857 858 private void createType(TypeModelImpl model, String uriPrefix) { 859 // Note: the type topic is instantiated explicitly on a `TopicModel` (which is freshly created from the 860 // `TypeModel`). Creating the type topic from the `TypeModel` directly would fail as topic creation implies 861 // topic instantiation, and due to the polymorphic `instantiate()` method a `Type` object would be instantiated 862 // (instead a `Topic` object). But instantiating a type implies per-user type projection, that is removing the 863 // assoc defs not readable by the current user. But at the time the type topic is stored in the DB its assoc 864 // defs are not yet stored, and the readability check would fail. 865 TopicModelImpl typeTopic = mf.newTopicModel(model); 866 _createTopic(typeTopic, uriPrefix); // create generic topic 867 // 868 model.id = typeTopic.id; 869 model.uri = typeTopic.uri; 870 // 871 typeStorage.storeType(model); // store type-specific parts 872 } 873 874 private String typeUri(long objectId) { 875 return (String) fetchProperty(objectId, "typeUri"); 876 } 877 878 private void bootstrapTypeCache() { 879 TopicTypeModelImpl metaMetaType = mf.newTopicTypeModel("dmx.core.meta_meta_type", "Meta Meta Type", 880 "dmx.core.text"); 881 metaMetaType.setTypeUri("dmx.core.meta_meta_meta_type"); 882 typeStorage.putInTypeCache(metaMetaType); 883 } 884 885 // --- 886 887 private <M extends DMXObjectModelImpl> M updateValues(M updateModel, M targetObject) { 888 M value = new ValueIntegrator(this).integrate(updateModel, targetObject, null).value; 889 // sanity check 890 if (value == null) { 891 throw new RuntimeException("ValueIntegrator yields no result"); 892 } 893 // 894 return value; 895 } 896}