001package de.deepamehta.core.impl; 002 003import de.deepamehta.core.Association; 004import de.deepamehta.core.AssociationType; 005import de.deepamehta.core.DeepaMehtaObject; 006import de.deepamehta.core.RelatedAssociation; 007import de.deepamehta.core.RelatedTopic; 008import de.deepamehta.core.Topic; 009import de.deepamehta.core.TopicType; 010import de.deepamehta.core.model.AssociationModel; 011import de.deepamehta.core.model.AssociationTypeModel; 012import de.deepamehta.core.model.DeepaMehtaObjectModel; 013import de.deepamehta.core.model.RelatedAssociationModel; 014import de.deepamehta.core.model.RelatedTopicModel; 015import de.deepamehta.core.model.RoleModel; 016import de.deepamehta.core.model.SimpleValue; 017import de.deepamehta.core.model.TopicModel; 018import de.deepamehta.core.model.TopicTypeModel; 019import de.deepamehta.core.service.accesscontrol.AccessControlException; 020import de.deepamehta.core.storage.spi.DeepaMehtaStorage; 021 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Iterator; 025import java.util.List; 026import java.util.logging.Logger; 027 028 029 030public class PersistenceLayer extends StorageDecorator { 031 032 // ------------------------------------------------------------------------------------------------------- Constants 033 034 private static final String URI_PREFIX_TOPIC_TYPE = "domain.project.topic_type_"; 035 private static final String URI_PREFIX_ASSOCIATION_TYPE = "domain.project.assoc_type_"; 036 private static final String URI_PREFIX_ROLE_TYPE = "domain.project.role_type_"; 037 038 // ---------------------------------------------------------------------------------------------- Instance Variables 039 040 TypeStorage typeStorage; 041 ValueStorage valueStorage; 042 043 EventManager em; 044 ModelFactoryImpl mf; 045 046 private final Logger logger = Logger.getLogger(getClass().getName()); 047 048 // ---------------------------------------------------------------------------------------------------- Constructors 049 050 public PersistenceLayer(DeepaMehtaStorage 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 // 056 this.typeStorage = new TypeStorage(this); 057 this.valueStorage = new ValueStorage(this); 058 // 059 // Note: this is a constructor side effect. This is a cyclic dependency. This is nasty. 060 // ### TODO: explain why we do it. 061 mf.pl = this; 062 // 063 bootstrapTypeCache(); 064 } 065 066 // ----------------------------------------------------------------------------------------- Package Private Methods 067 068 069 070 // === Topics === 071 072 Topic getTopic(long topicId) { 073 try { 074 return checkReadAccessAndInstantiate(fetchTopic(topicId)); 075 } catch (Exception e) { 076 throw new RuntimeException("Fetching topic " + topicId + " failed", e); 077 } 078 } 079 080 TopicImpl getTopicByUri(String uri) { 081 return getTopicByValue("uri", new SimpleValue(uri)); 082 } 083 084 TopicImpl getTopicByValue(String key, SimpleValue value) { 085 try { 086 TopicModelImpl topic = fetchTopic(key, value); 087 return topic != null ? this.<TopicImpl>checkReadAccessAndInstantiate(topic) : null; 088 // Note: inside a conditional operator the type witness is required (at least in Java 6) 089 } catch (Exception e) { 090 throw new RuntimeException("Fetching topic failed (key=\"" + key + "\", value=\"" + value + "\")", e); 091 } 092 } 093 094 List<Topic> getTopicsByValue(String key, SimpleValue value) { 095 try { 096 return checkReadAccessAndInstantiate(fetchTopics(key, value)); 097 } catch (Exception e) { 098 throw new RuntimeException("Fetching topics failed (key=\"" + key + "\", value=\"" + value + "\")", e); 099 } 100 } 101 102 List<Topic> getTopicsByType(String topicTypeUri) { 103 try { 104 return checkReadAccessAndInstantiate(_getTopicType(topicTypeUri).getAllInstances()); 105 } catch (Exception e) { 106 throw new RuntimeException("Fetching topics by type failed (topicTypeUri=\"" + topicTypeUri + "\")", e); 107 } 108 } 109 110 List<Topic> searchTopics(String searchTerm, String fieldUri) { 111 try { 112 return checkReadAccessAndInstantiate(queryTopics(fieldUri, new SimpleValue(searchTerm))); 113 } catch (Exception e) { 114 throw new RuntimeException("Searching topics failed (searchTerm=\"" + searchTerm + "\", fieldUri=\"" + 115 fieldUri + "\")", e); 116 } 117 } 118 119 Iterable<Topic> getAllTopics() { 120 return new TopicIterable(this); 121 } 122 123 // --- 124 125 /** 126 * Convenience. 127 */ 128 TopicImpl createTopic(TopicModel model) { 129 return createTopic(model, null); // uriPrefix=null 130 } 131 132 /** 133 * Creates a new topic in the DB. 134 */ 135 TopicImpl createTopic(TopicModel model, String uriPrefix) { 136 try { 137 em.fireEvent(CoreEvent.PRE_CREATE_TOPIC, model); 138 // 139 // 1) store in DB 140 storeTopic(model); 141 valueStorage.storeValue((TopicModelImpl) model); 142 createTopicInstantiation(model.getId(), model.getTypeUri()); 143 // 2) set default URI 144 // If no URI is given the topic gets a default URI based on its ID, if requested. 145 // Note: this must be done *after* the topic is stored. The ID is not known before. 146 // Note: in case no URI was given: once stored a topic's URI is empty (not null). 147 if (uriPrefix != null && model.getUri().equals("")) { 148 ((TopicModelImpl) model).updateUri(uriPrefix + model.getId()); 149 } 150 // 3) instantiate 151 TopicImpl topic = new TopicImpl((TopicModelImpl) model, this); 152 // 153 em.fireEvent(CoreEvent.POST_CREATE_TOPIC, topic); 154 return topic; 155 } catch (Exception e) { 156 throw new RuntimeException("Creating topic " + model.getId() + " failed (typeUri=\"" + model.getTypeUri() + 157 "\")", e); 158 } 159 } 160 161 // --- 162 163 void updateTopic(TopicModel newModel) { 164 long topicId = newModel.getId(); 165 try { 166 checkTopicWriteAccess(topicId); 167 // 168 TopicModelImpl model = fetchTopic(topicId); 169 model.update(newModel); 170 // 171 // Note: POST_UPDATE_TOPIC_REQUEST is fired only once per update request. 172 // On the other hand TopicModel's update() method is called multiple times while updating the child topics 173 // (see ChildTopicsModelImpl). 174 em.fireEvent(CoreEvent.POST_UPDATE_TOPIC_REQUEST, model.instantiate()); 175 } catch (Exception e) { 176 throw new RuntimeException("Updating topic " + topicId + " failed", e); 177 } 178 } 179 180 void deleteTopic(long topicId) { 181 try { 182 checkTopicWriteAccess(topicId); 183 // 184 fetchTopic(topicId).delete(); 185 } catch (Exception e) { 186 throw new RuntimeException("Deleting topic " + topicId + " failed", e); 187 } 188 } 189 190 191 192 // === Associations === 193 194 Association getAssociation(long assocId) { 195 try { 196 return checkReadAccessAndInstantiate(fetchAssociation(assocId)); 197 } catch (Exception e) { 198 throw new RuntimeException("Fetching association " + assocId + " failed", e); 199 } 200 } 201 202 Association getAssociationByValue(String key, SimpleValue value) { 203 try { 204 AssociationModelImpl assoc = fetchAssociation(key, value); 205 return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null; 206 // Note: inside a conditional operator the type witness is required (at least in Java 6) 207 } catch (Exception e) { 208 throw new RuntimeException("Fetching association failed (key=\"" + key + "\", value=\"" + value + "\")", e); 209 } 210 } 211 212 List<Association> getAssociationsByValue(String key, SimpleValue value) { 213 try { 214 return checkReadAccessAndInstantiate(fetchAssociations(key, value)); 215 } catch (Exception e) { 216 throw new RuntimeException("Fetching associationss failed (key=\"" + key + "\", value=\"" + value + "\")", 217 e); 218 } 219 } 220 221 Association getAssociation(String assocTypeUri, long topic1Id, long topic2Id, String roleTypeUri1, 222 String roleTypeUri2) { 223 String info = "assocTypeUri=\"" + assocTypeUri + "\", topic1Id=" + topic1Id + ", topic2Id=" + topic2Id + 224 ", roleTypeUri1=\"" + roleTypeUri1 + "\", roleTypeUri2=\"" + roleTypeUri2 + "\""; 225 try { 226 AssociationModelImpl assoc = fetchAssociation(assocTypeUri, topic1Id, topic2Id, roleTypeUri1, roleTypeUri2); 227 return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null; 228 // Note: inside a conditional operator the type witness is required (at least in Java 6) 229 } catch (Exception e) { 230 throw new RuntimeException("Fetching association failed (" + info + ")", e); 231 } 232 } 233 234 Association getAssociationBetweenTopicAndAssociation(String assocTypeUri, long topicId, long assocId, 235 String topicRoleTypeUri, String assocRoleTypeUri) { 236 String info = "assocTypeUri=\"" + assocTypeUri + "\", topicId=" + topicId + ", assocId=" + assocId + 237 ", topicRoleTypeUri=\"" + topicRoleTypeUri + "\", assocRoleTypeUri=\"" + assocRoleTypeUri + "\""; 238 logger.info(info); 239 try { 240 AssociationModelImpl assoc = fetchAssociationBetweenTopicAndAssociation(assocTypeUri, topicId, assocId, 241 topicRoleTypeUri, assocRoleTypeUri); 242 return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null; 243 // Note: inside a conditional operator the type witness is required (at least in Java 6) 244 } catch (Exception e) { 245 throw new RuntimeException("Fetching association failed (" + info + ")", e); 246 } 247 } 248 249 // --- 250 251 List<Association> getAssociationsByType(String assocTypeUri) { 252 try { 253 return checkReadAccessAndInstantiate(_getAssociationType(assocTypeUri).getAllInstances()); 254 } catch (Exception e) { 255 throw new RuntimeException("Fetching associations by type failed (assocTypeUri=\"" + assocTypeUri + "\")", 256 e); 257 } 258 } 259 260 List<Association> getAssociations(long topic1Id, long topic2Id) { 261 return getAssociations(topic1Id, topic2Id, null); 262 } 263 264 List<Association> getAssociations(long topic1Id, long topic2Id, String assocTypeUri) { 265 logger.info("topic1Id=" + topic1Id + ", topic2Id=" + topic2Id + ", assocTypeUri=\"" + assocTypeUri + "\""); 266 try { 267 return checkReadAccessAndInstantiate(fetchAssociations(assocTypeUri, topic1Id, topic2Id, null, null)); 268 // roleTypeUri1=null, roleTypeUri2=null 269 } catch (Exception e) { 270 throw new RuntimeException("Fetching associations between topics " + topic1Id + " and " + topic2Id + 271 " failed (assocTypeUri=\"" + assocTypeUri + "\")", e); 272 } 273 } 274 275 // --- 276 277 Iterable<Association> getAllAssociations() { 278 return new AssociationIterable(this); 279 } 280 281 long[] getPlayerIds(long assocId) { 282 return fetchPlayerIds(assocId); 283 } 284 285 // --- 286 287 /** 288 * Convenience. 289 */ 290 Association createAssociation(String typeUri, RoleModel roleModel1, RoleModel roleModel2) { 291 return createAssociation(mf.newAssociationModel(typeUri, roleModel1, roleModel2)); 292 } 293 294 /** 295 * Creates a new association in the DB. 296 */ 297 Association createAssociation(AssociationModelImpl model) { 298 try { 299 em.fireEvent(CoreEvent.PRE_CREATE_ASSOCIATION, model); 300 // 301 // 1) store in DB 302 storeAssociation(model); 303 valueStorage.storeValue(model); 304 createAssociationInstantiation(model.getId(), model.getTypeUri()); 305 // 2) instantiate 306 Association assoc = new AssociationImpl(model, this); 307 // 308 em.fireEvent(CoreEvent.POST_CREATE_ASSOCIATION, assoc); 309 return assoc; 310 } catch (Exception e) { 311 throw new RuntimeException("Creating association failed (" + model + ")", e); 312 } 313 } 314 315 // --- 316 317 void updateAssociation(AssociationModel newModel) { 318 long assocId = newModel.getId(); 319 try { 320 checkAssociationWriteAccess(assocId); 321 // 322 AssociationModelImpl model = fetchAssociation(assocId); 323 model.update(newModel); 324 // 325 // Note: there is no possible POST_UPDATE_ASSOCIATION_REQUEST event to fire here (compare to updateTopic()). 326 // It would be equivalent to POST_UPDATE_ASSOCIATION. Per request exactly one association is updated. 327 // Its childs are always topics (never associations). 328 } catch (Exception e) { 329 throw new RuntimeException("Updating association " + assocId + " failed", e); 330 } 331 } 332 333 void deleteAssociation(long assocId) { 334 try { 335 checkAssociationWriteAccess(assocId); 336 // 337 fetchAssociation(assocId).delete(); 338 } catch (Exception e) { 339 throw new RuntimeException("Deleting association " + assocId + " failed", e); 340 } 341 } 342 343 344 345 // === 346 347 void createTopicInstantiation(long topicId, String topicTypeUri) { 348 try { 349 AssociationModel assoc = mf.newAssociationModel("dm4.core.instantiation", 350 mf.newTopicRoleModel(topicTypeUri, "dm4.core.type"), 351 mf.newTopicRoleModel(topicId, "dm4.core.instance")); 352 storeAssociation(assoc); // direct storage calls used here ### explain 353 storeAssociationValue(assoc.getId(), assoc.getSimpleValue()); 354 createAssociationInstantiation(assoc.getId(), assoc.getTypeUri()); 355 } catch (Exception e) { 356 throw new RuntimeException("Associating topic " + topicId + 357 " with topic type \"" + topicTypeUri + "\" failed", e); 358 } 359 } 360 361 void createAssociationInstantiation(long assocId, String assocTypeUri) { 362 try { 363 AssociationModel assoc = mf.newAssociationModel("dm4.core.instantiation", 364 mf.newTopicRoleModel(assocTypeUri, "dm4.core.type"), 365 mf.newAssociationRoleModel(assocId, "dm4.core.instance")); 366 storeAssociation(assoc); // direct storage calls used here ### explain 367 storeAssociationValue(assoc.getId(), assoc.getSimpleValue()); 368 } catch (Exception e) { 369 throw new RuntimeException("Associating association " + assocId + 370 " with association type \"" + assocTypeUri + "\" failed", e); 371 } 372 } 373 374 375 376 // === Types === 377 378 TopicType getTopicType(String uri) { 379 TopicTypeModelImpl topicType = _getTopicType(uri); 380 if (!uri.equals("dm4.core.meta_meta_type")) { 381 checkReadAccess(topicType); 382 } 383 return topicType.instantiate(); 384 } 385 386 TopicType getTopicTypeImplicitly(long topicId) { 387 checkTopicReadAccess(topicId); 388 return _getTopicType(typeUri(topicId)).instantiate(); 389 } 390 391 // --- 392 393 AssociationType getAssociationType(String uri) { 394 return checkReadAccessAndInstantiate(_getAssociationType(uri)); 395 } 396 397 AssociationType getAssociationTypeImplicitly(long assocId) { 398 checkAssociationReadAccess(assocId); 399 return _getAssociationType(typeUri(assocId)).instantiate(); 400 } 401 402 // --- 403 404 List<TopicType> getAllTopicTypes() { 405 try { 406 List<TopicType> topicTypes = new ArrayList(); 407 for (String uri : getTopicTypeUris()) { 408 topicTypes.add(_getTopicType(uri).instantiate()); 409 } 410 return topicTypes; 411 } catch (Exception e) { 412 throw new RuntimeException("Fetching all topic types failed", e); 413 } 414 } 415 416 List<AssociationType> getAllAssociationTypes() { 417 try { 418 List<AssociationType> assocTypes = new ArrayList(); 419 for (String uri : getAssociationTypeUris()) { 420 assocTypes.add(_getAssociationType(uri).instantiate()); 421 } 422 return assocTypes; 423 } catch (Exception e) { 424 throw new RuntimeException("Fetching all association types failed", e); 425 } 426 } 427 428 // --- 429 430 TopicType createTopicType(TopicTypeModelImpl model) { 431 try { 432 // store in DB 433 createTopic(model, URI_PREFIX_TOPIC_TYPE); // create generic topic 434 typeStorage.storeType(model); // store type-specific parts 435 // 436 TopicType topicType = model.instantiate(); 437 em.fireEvent(CoreEvent.INTRODUCE_TOPIC_TYPE, topicType); 438 // 439 return topicType; 440 } catch (Exception e) { 441 throw new RuntimeException("Creating topic type \"" + model.getUri() + "\" failed (" + model + ")", e); 442 } 443 } 444 445 AssociationType createAssociationType(AssociationTypeModelImpl model) { 446 try { 447 // store in DB 448 createTopic(model, URI_PREFIX_ASSOCIATION_TYPE); // create generic topic 449 typeStorage.storeType(model); // store type-specific parts 450 // 451 AssociationType assocType = model.instantiate(); 452 em.fireEvent(CoreEvent.INTRODUCE_ASSOCIATION_TYPE, assocType); 453 // 454 return assocType; 455 } catch (Exception e) { 456 throw new RuntimeException("Creating association type \"" + model.getUri() + "\" failed (" + model + ")", 457 e); 458 } 459 } 460 461 // --- 462 463 Topic createRoleType(TopicModel model) { 464 // check type URI argument 465 String typeUri = model.getTypeUri(); 466 if (typeUri == null) { 467 model.setTypeUri("dm4.core.role_type"); 468 } else { 469 if (!typeUri.equals("dm4.core.role_type")) { 470 throw new IllegalArgumentException("A role type is supposed to be of type \"dm4.core.role_type\" " + 471 "(found: \"" + typeUri + "\")"); 472 } 473 } 474 // 475 return createTopic(model, URI_PREFIX_ROLE_TYPE); 476 } 477 478 479 480 // === Generic Object === 481 482 DeepaMehtaObject getObject(long id) { 483 return checkReadAccessAndInstantiate(fetchObject(id)); 484 } 485 486 487 488 // === Properties === 489 490 List<Topic> getTopicsByProperty(String propUri, Object propValue) { 491 return checkReadAccessAndInstantiate(fetchTopicsByProperty(propUri, propValue)); 492 } 493 494 List<Topic> getTopicsByPropertyRange(String propUri, Number from, Number to) { 495 return checkReadAccessAndInstantiate(fetchTopicsByPropertyRange(propUri, from, to)); 496 } 497 498 List<Association> getAssociationsByProperty(String propUri, Object propValue) { 499 return checkReadAccessAndInstantiate(fetchAssociationsByProperty(propUri, propValue)); 500 } 501 502 List<Association> getAssociationsByPropertyRange(String propUri, Number from, Number to) { 503 return checkReadAccessAndInstantiate(fetchAssociationsByPropertyRange(propUri, from, to)); 504 } 505 506 507 508 // === Access Control / Instantiation === 509 510 // These methods 1) instantiate objects from models, and 2) check the READ permission for each model. 511 // Call these methods when passing objects fetched from the DB to the user. 512 // ### TODO: make these private? 513 514 <O> O checkReadAccessAndInstantiate(DeepaMehtaObjectModelImpl model) { 515 checkReadAccess(model); 516 return (O) model.instantiate(); 517 } 518 519 <O> List<O> checkReadAccessAndInstantiate(Iterable<? extends DeepaMehtaObjectModelImpl> models) { 520 return instantiate(filterReadables(models)); 521 } 522 523 // --- 524 525 private <M extends DeepaMehtaObjectModelImpl> Iterable<M> filterReadables(Iterable<M> models) { 526 Iterator<? extends DeepaMehtaObjectModelImpl> i = models.iterator(); 527 while (i.hasNext()) { 528 DeepaMehtaObjectModelImpl model = i.next(); 529 try { 530 checkReadAccess(model); 531 } catch (AccessControlException e) { 532 i.remove(); 533 } 534 } 535 return models; 536 } 537 538 /** 539 * @throws AccessControlException 540 */ 541 private void checkReadAccess(DeepaMehtaObjectModelImpl model) { 542 em.fireEvent(model.getReadAccessEvent(), model.getId()); 543 } 544 545 // --- 546 547 private void checkTopicReadAccess(long topicId) { 548 em.fireEvent(CoreEvent.CHECK_TOPIC_READ_ACCESS, topicId); 549 } 550 551 private void checkAssociationReadAccess(long assocId) { 552 em.fireEvent(CoreEvent.CHECK_ASSOCIATION_READ_ACCESS, assocId); 553 } 554 555 // --- 556 557 private void checkTopicWriteAccess(long topicId) { 558 em.fireEvent(CoreEvent.CHECK_TOPIC_WRITE_ACCESS, topicId); 559 } 560 561 private void checkAssociationWriteAccess(long assocId) { 562 em.fireEvent(CoreEvent.CHECK_ASSOCIATION_WRITE_ACCESS, assocId); 563 } 564 565 566 567 // === Instantiation === 568 569 <O> List<O> instantiate(Iterable<? extends DeepaMehtaObjectModelImpl> models) { 570 List<O> objects = new ArrayList(); 571 for (DeepaMehtaObjectModelImpl model : models) { 572 objects.add((O) model.instantiate()); 573 } 574 return objects; 575 } 576 577 578 579 // === 580 581 private List<String> getTopicTypeUris() { 582 try { 583 List<String> topicTypeUris = new ArrayList(); 584 // add meta types 585 topicTypeUris.add("dm4.core.topic_type"); 586 topicTypeUris.add("dm4.core.assoc_type"); 587 topicTypeUris.add("dm4.core.meta_type"); 588 topicTypeUris.add("dm4.core.meta_meta_type"); 589 // add regular types 590 for (TopicModel topicType : filterReadables(fetchTopics("type_uri", new SimpleValue( 591 "dm4.core.topic_type")))) { 592 topicTypeUris.add(topicType.getUri()); 593 } 594 return topicTypeUris; 595 } catch (Exception e) { 596 throw new RuntimeException("Fetching list of topic type URIs failed", e); 597 } 598 } 599 600 private List<String> getAssociationTypeUris() { 601 try { 602 List<String> assocTypeUris = new ArrayList(); 603 for (TopicModel assocType : filterReadables(fetchTopics("type_uri", new SimpleValue( 604 "dm4.core.assoc_type")))) { 605 assocTypeUris.add(assocType.getUri()); 606 } 607 return assocTypeUris; 608 } catch (Exception e) { 609 throw new RuntimeException("Fetching list of association type URIs failed", e); 610 } 611 } 612 613 // --- 614 615 private TopicTypeModelImpl _getTopicType(String uri) { 616 return typeStorage.getTopicType(uri); 617 } 618 619 private AssociationTypeModelImpl _getAssociationType(String uri) { 620 return typeStorage.getAssociationType(uri); 621 } 622 623 // --- 624 625 private String typeUri(long objectId) { 626 return (String) fetchProperty(objectId, "type_uri"); 627 } 628 629 // --- 630 631 private void bootstrapTypeCache() { 632 TopicTypeModelImpl metaMetaType = mf.newTopicTypeModel("dm4.core.meta_meta_type", "Meta Meta Type", 633 "dm4.core.text"); 634 metaMetaType.setTypeUri("dm4.core.meta_meta_meta_type"); 635 typeStorage.putInTypeCache(metaMetaType); 636 } 637}