001 package de.deepamehta.core.impl; 002 003 import de.deepamehta.core.model.AssociationModel; 004 import de.deepamehta.core.model.IndexMode; 005 import de.deepamehta.core.model.RelatedAssociationModel; 006 import de.deepamehta.core.model.RelatedTopicModel; 007 import de.deepamehta.core.model.SimpleValue; 008 import de.deepamehta.core.model.TopicModel; 009 import de.deepamehta.core.service.ResultList; 010 import de.deepamehta.core.storage.spi.DeepaMehtaTransaction; 011 import de.deepamehta.core.storage.spi.DeepaMehtaStorage; 012 013 import static java.util.Arrays.asList; 014 import java.util.Iterator; 015 import java.util.List; 016 import java.util.logging.Logger; 017 018 019 020 public class StorageDecorator { 021 022 // ---------------------------------------------------------------------------------------------- Instance Variables 023 024 private DeepaMehtaStorage storage; 025 026 private final Logger logger = Logger.getLogger(getClass().getName()); 027 028 // ---------------------------------------------------------------------------------------------------- Constructors 029 030 public StorageDecorator(DeepaMehtaStorage storage) { 031 this.storage = storage; 032 } 033 034 // -------------------------------------------------------------------------------------------------- Public Methods 035 036 037 038 // === Topics === 039 040 /** 041 * @return The fetched topic. 042 * Note: its composite value is not initialized. 043 */ 044 TopicModel fetchTopic(long topicId) { 045 return storage.fetchTopic(topicId); 046 } 047 048 /** 049 * Looks up a single topic by exact property value. 050 * If no such topic exists <code>null</code> is returned. 051 * If more than one topic were found a runtime exception is thrown. 052 * <p> 053 * IMPORTANT: Looking up a topic this way requires the property to be indexed with indexing mode <code>KEY</code>. 054 * This is achieved by declaring the respective data field with <code>indexing_mode: "KEY"</code> 055 * (for statically declared data field, typically in <code>types.json</code>) or 056 * by calling DataField's {@link DataField#setIndexingMode} method with <code>"KEY"</code> as argument 057 * (for dynamically created data fields, typically in migration classes). 058 * 059 * @return The fetched topic. 060 * Note: its composite value is not initialized. 061 */ 062 TopicModel fetchTopic(String key, SimpleValue value) { 063 return storage.fetchTopic(key, value.value()); 064 } 065 066 List<TopicModel> fetchTopics(String key, SimpleValue value) { 067 return storage.fetchTopics(key, value.value()); 068 } 069 070 // --- 071 072 /** 073 * @return The fetched topics. 074 * Note: their composite values are not initialized. 075 */ 076 List<TopicModel> queryTopics(String searchTerm, String fieldUri) { 077 return storage.queryTopics(fieldUri, searchTerm); 078 } 079 080 // --- 081 082 Iterator<TopicModel> fetchAllTopics() { 083 return storage.fetchAllTopics(); 084 } 085 086 // --- 087 088 /** 089 * Creates a topic. 090 * <p> 091 * The topic's URI is stored and indexed. 092 * 093 * @return FIXDOC ### the created topic. Note: 094 * - the topic URI is initialzed and persisted. 095 * - the topic value is initialzed but not persisted. 096 * - the type URI is initialzed but not persisted. 097 */ 098 void storeTopic(TopicModel model) { 099 storage.storeTopic(model); 100 } 101 102 /** 103 * Stores and indexes the topic's URI. 104 */ 105 void storeTopicUri(long topicId, String uri) { 106 storage.storeTopicUri(topicId, uri); 107 } 108 109 void storeTopicTypeUri(long topicId, String topicTypeUri) { 110 storage.storeTopicTypeUri(topicId, topicTypeUri); 111 } 112 113 // --- 114 115 /** 116 * Convenience method (no indexing). 117 */ 118 void storeTopicValue(long topicId, SimpleValue value) { 119 storeTopicValue(topicId, value, asList(IndexMode.OFF), null, null); 120 } 121 122 /** 123 * Stores the topic's value. 124 * <p> 125 * Note: the value is not indexed automatically. Use the {@link indexTopicValue} method. ### FIXDOC 126 * 127 * @return The previous value, or <code>null</code> if no value was stored before. ### FIXDOC 128 */ 129 void storeTopicValue(long topicId, SimpleValue value, List<IndexMode> indexModes, String indexKey, 130 SimpleValue indexValue) { 131 storage.storeTopicValue(topicId, value, indexModes, indexKey, indexValue); 132 } 133 134 // --- 135 136 /** 137 * Deletes the topic. 138 * <p> 139 * Prerequisite: the topic has no relations. 140 */ 141 void deleteTopic(long topicId) { 142 storage.deleteTopic(topicId); 143 } 144 145 146 147 // === Associations === 148 149 AssociationModel fetchAssociation(long assocId) { 150 return storage.fetchAssociation(assocId); 151 } 152 153 // --- 154 155 /** 156 * Convenience method (checks singularity). 157 * 158 * Returns the association between two topics, qualified by association type and both role types. 159 * If no such association exists <code>null</code> is returned. 160 * If more than one association exist, a runtime exception is thrown. 161 * 162 * @param assocTypeUri Association type filter. Pass <code>null</code> to switch filter off. 163 * ### FIXME: for methods with a singular return value all filters should be mandatory 164 */ 165 AssociationModel fetchAssociation(String assocTypeUri, long topicId1, long topicId2, String roleTypeUri1, 166 String roleTypeUri2) { 167 List<AssociationModel> assocs = fetchAssociations(assocTypeUri, topicId1, topicId2, roleTypeUri1, roleTypeUri2); 168 switch (assocs.size()) { 169 case 0: 170 return null; 171 case 1: 172 return assocs.get(0); 173 default: 174 throw new RuntimeException("Ambiguity: there are " + assocs.size() + " \"" + assocTypeUri + 175 "\" associations (topicId1=" + topicId1 + ", topicId2=" + topicId2 + ", " + 176 "roleTypeUri1=\"" + roleTypeUri1 + "\", roleTypeUri2=\"" + roleTypeUri2 + "\")"); 177 } 178 } 179 180 /** 181 * Returns the associations between two topics. If no such association exists an empty set is returned. 182 * 183 * @param assocTypeUri Association type filter. Pass <code>null</code> to switch filter off. 184 */ 185 List<AssociationModel> fetchAssociations(String assocTypeUri, long topicId1, long topicId2, 186 String roleTypeUri1, String roleTypeUri2) { 187 return storage.fetchAssociations(assocTypeUri, topicId1, topicId2, roleTypeUri1, roleTypeUri2); 188 } 189 190 // --- 191 192 /** 193 * Convenience method (checks singularity). 194 */ 195 AssociationModel fetchAssociationBetweenTopicAndAssociation(String assocTypeUri, long topicId, long assocId, 196 String topicRoleTypeUri, String assocRoleTypeUri) { 197 List<AssociationModel> assocs = fetchAssociationsBetweenTopicAndAssociation(assocTypeUri, topicId, assocId, 198 topicRoleTypeUri, assocRoleTypeUri); 199 switch (assocs.size()) { 200 case 0: 201 return null; 202 case 1: 203 return assocs.get(0); 204 default: 205 throw new RuntimeException("Ambiguity: there are " + assocs.size() + " \"" + assocTypeUri + 206 "\" associations (topicId=" + topicId + ", assocId=" + assocId + ", " + 207 "topicRoleTypeUri=\"" + topicRoleTypeUri + "\", assocRoleTypeUri=\"" + assocRoleTypeUri + "\")"); 208 } 209 } 210 211 List<AssociationModel> fetchAssociationsBetweenTopicAndAssociation(String assocTypeUri, long topicId, 212 long assocId, String topicRoleTypeUri, String assocRoleTypeUri) { 213 return storage.fetchAssociationsBetweenTopicAndAssociation(assocTypeUri, topicId, assocId, topicRoleTypeUri, 214 assocRoleTypeUri); 215 } 216 217 // --- 218 219 Iterator<AssociationModel> fetchAllAssociations() { 220 return storage.fetchAllAssociations(); 221 } 222 223 // --- 224 225 /** 226 * Stores and indexes the association's URI. 227 */ 228 void storeAssociationUri(long assocId, String uri) { 229 storage.storeAssociationUri(assocId, uri); 230 } 231 232 void storeAssociationTypeUri(long assocId, String assocTypeUri) { 233 storage.storeAssociationTypeUri(assocId, assocTypeUri); 234 } 235 236 void storeRoleTypeUri(long assocId, long playerId, String roleTypeUri) { 237 storage.storeRoleTypeUri(assocId, playerId, roleTypeUri); 238 } 239 240 // --- 241 242 /** 243 * Convenience method (no indexing). 244 */ 245 void storeAssociationValue(long assocId, SimpleValue value) { 246 storeAssociationValue(assocId, value, asList(IndexMode.OFF), null, null); 247 } 248 249 /** 250 * Stores the association's value. 251 * <p> 252 * Note: the value is not indexed automatically. Use the {@link indexAssociationValue} method. ### FIXDOC 253 * 254 * @return The previous value, or <code>null</code> if no value was stored before. ### FIXDOC 255 */ 256 void storeAssociationValue(long assocId, SimpleValue value, List<IndexMode> indexModes, String indexKey, 257 SimpleValue indexValue) { 258 storage.storeAssociationValue(assocId, value, indexModes, indexKey, indexValue); 259 } 260 261 // --- 262 263 void storeAssociation(AssociationModel model) { 264 storage.storeAssociation(model); 265 } 266 267 void deleteAssociation(long assocId) { 268 storage.deleteAssociation(assocId); 269 } 270 271 272 273 // === Traversal === 274 275 /** 276 * @return The fetched associations. 277 * Note: their composite values are not initialized. 278 */ 279 List<AssociationModel> fetchTopicAssociations(long topicId) { 280 return storage.fetchTopicAssociations(topicId); 281 } 282 283 List<AssociationModel> fetchAssociationAssociations(long assocId) { 284 return storage.fetchAssociationAssociations(assocId); 285 } 286 287 // --- 288 289 /** 290 * Convenience method (checks singularity). 291 * 292 * @param assocTypeUri may be null 293 * @param myRoleTypeUri may be null 294 * @param othersRoleTypeUri may be null 295 * @param othersTopicTypeUri may be null 296 * 297 * @return The fetched topics. 298 * Note: their composite values are not initialized. 299 */ 300 RelatedTopicModel fetchTopicRelatedTopic(long topicId, String assocTypeUri, String myRoleTypeUri, 301 String othersRoleTypeUri, String othersTopicTypeUri) { 302 ResultList<RelatedTopicModel> topics = fetchTopicRelatedTopics(topicId, assocTypeUri, myRoleTypeUri, 303 othersRoleTypeUri, othersTopicTypeUri, 0); 304 switch (topics.getSize()) { 305 case 0: 306 return null; 307 case 1: 308 return topics.iterator().next(); 309 default: 310 throw new RuntimeException("Ambiguity: there are " + topics.getSize() + " related topics (topicId=" + 311 topicId + ", assocTypeUri=\"" + assocTypeUri + "\", myRoleTypeUri=\"" + myRoleTypeUri + "\", " + 312 "othersRoleTypeUri=\"" + othersRoleTypeUri + "\", othersTopicTypeUri=\"" + othersTopicTypeUri + "\")"); 313 } 314 } 315 316 /** 317 * @param assocTypeUri may be null 318 * @param myRoleTypeUri may be null 319 * @param othersRoleTypeUri may be null 320 * @param othersTopicTypeUri may be null 321 * 322 * @return The fetched topics. 323 * Note: their composite values are not initialized. 324 */ 325 ResultList<RelatedTopicModel> fetchTopicRelatedTopics(long topicId, String assocTypeUri, 326 String myRoleTypeUri, String othersRoleTypeUri, 327 String othersTopicTypeUri, int maxResultSize) { 328 List<RelatedTopicModel> relTopics = storage.fetchTopicRelatedTopics(topicId, assocTypeUri, myRoleTypeUri, 329 othersRoleTypeUri, othersTopicTypeUri); 330 // ### TODO: respect maxResultSize 331 return new ResultList(relTopics.size(), relTopics); 332 } 333 334 /** 335 * Convenience method (receives *list* of association types). 336 * 337 * @param assocTypeUris may *not* be null 338 * @param myRoleTypeUri may be null 339 * @param othersRoleTypeUri may be null 340 * @param othersTopicTypeUri may be null 341 * 342 * @return The fetched topics. 343 * Note: their composite values are not initialized. 344 */ 345 ResultList<RelatedTopicModel> fetchTopicRelatedTopics(long topicId, List<String> assocTypeUris, 346 String myRoleTypeUri, String othersRoleTypeUri, 347 String othersTopicTypeUri, int maxResultSize) { 348 ResultList<RelatedTopicModel> result = new ResultList(); 349 for (String assocTypeUri : assocTypeUris) { 350 ResultList<RelatedTopicModel> res = fetchTopicRelatedTopics(topicId, assocTypeUri, myRoleTypeUri, 351 othersRoleTypeUri, othersTopicTypeUri, maxResultSize); 352 result.addAll(res); 353 } 354 return result; 355 } 356 357 // --- 358 359 /** 360 * Convenience method (checks singularity). 361 * 362 * @return The fetched association. 363 * Note: its composite value is not initialized. 364 */ 365 RelatedAssociationModel fetchTopicRelatedAssociation(long topicId, String assocTypeUri, String myRoleTypeUri, 366 String othersRoleTypeUri, String othersAssocTypeUri) { 367 List<RelatedAssociationModel> assocs = fetchTopicRelatedAssociations(topicId, assocTypeUri, myRoleTypeUri, 368 othersRoleTypeUri, othersAssocTypeUri); 369 switch (assocs.size()) { 370 case 0: 371 return null; 372 case 1: 373 return assocs.get(0); 374 default: 375 throw new RuntimeException("Ambiguity: there are " + assocs.size() + " related associations (topicId=" + 376 topicId + ", assocTypeUri=\"" + assocTypeUri + "\", myRoleTypeUri=\"" + myRoleTypeUri + "\", " + 377 "othersRoleTypeUri=\"" + othersRoleTypeUri + "\", othersAssocTypeUri=\"" + othersAssocTypeUri + "\")"); 378 } 379 } 380 381 /** 382 * @param assocTypeUri may be null 383 * @param myRoleTypeUri may be null 384 * @param othersRoleTypeUri may be null 385 * @param othersAssocTypeUri may be null 386 * 387 * @return The fetched associations. 388 * Note: their composite values are not initialized. 389 */ 390 List<RelatedAssociationModel> fetchTopicRelatedAssociations(long topicId, String assocTypeUri, 391 String myRoleTypeUri, String othersRoleTypeUri, String othersAssocTypeUri) { 392 return storage.fetchTopicRelatedAssociations(topicId, assocTypeUri, myRoleTypeUri, othersRoleTypeUri, 393 othersAssocTypeUri); 394 } 395 396 // --- 397 398 /** 399 * Convenience method (checks singularity). 400 * 401 * @return The fetched topics. 402 * Note: their composite values are not initialized. 403 */ 404 RelatedTopicModel fetchAssociationRelatedTopic(long assocId, String assocTypeUri, String myRoleTypeUri, 405 String othersRoleTypeUri, String othersTopicTypeUri) { 406 ResultList<RelatedTopicModel> topics = fetchAssociationRelatedTopics(assocId, assocTypeUri, myRoleTypeUri, 407 othersRoleTypeUri, othersTopicTypeUri, 0); 408 switch (topics.getSize()) { 409 case 0: 410 return null; 411 case 1: 412 return topics.iterator().next(); 413 default: 414 throw new RuntimeException("Ambiguity: there are " + topics.getSize() + " related topics (assocId=" + 415 assocId + ", assocTypeUri=\"" + assocTypeUri + "\", myRoleTypeUri=\"" + myRoleTypeUri + "\", " + 416 "othersRoleTypeUri=\"" + othersRoleTypeUri + "\", othersTopicTypeUri=\"" + othersTopicTypeUri + "\")"); 417 } 418 } 419 420 /** 421 * @return The fetched topics. 422 * Note: their composite values are not initialized. 423 */ 424 ResultList<RelatedTopicModel> fetchAssociationRelatedTopics(long assocId, String assocTypeUri, 425 String myRoleTypeUri, String othersRoleTypeUri, 426 String othersTopicTypeUri, int maxResultSize) { 427 List<RelatedTopicModel> relTopics = storage.fetchAssociationRelatedTopics(assocId, assocTypeUri, myRoleTypeUri, 428 othersRoleTypeUri, othersTopicTypeUri); 429 // ### TODO: respect maxResultSize 430 return new ResultList(relTopics.size(), relTopics); 431 } 432 433 /** 434 * Convenience method (receives *list* of association types). 435 * 436 * @param assocTypeUris may be null 437 * @param myRoleTypeUri may be null 438 * @param othersRoleTypeUri may be null 439 * @param othersTopicTypeUri may be null 440 * 441 * @return The fetched topics. 442 * Note: their composite values are not initialized. 443 */ 444 ResultList<RelatedTopicModel> fetchAssociationRelatedTopics(long assocId, List<String> assocTypeUris, 445 String myRoleTypeUri, String othersRoleTypeUri, 446 String othersTopicTypeUri, int maxResultSize) { 447 ResultList<RelatedTopicModel> result = new ResultList(); 448 for (String assocTypeUri : assocTypeUris) { 449 ResultList<RelatedTopicModel> res = fetchAssociationRelatedTopics(assocId, assocTypeUri, myRoleTypeUri, 450 othersRoleTypeUri, othersTopicTypeUri, maxResultSize); 451 result.addAll(res); 452 } 453 return result; 454 } 455 456 // --- 457 458 /** 459 * Convenience method (checks singularity). 460 * 461 * @return The fetched association. 462 * Note: its composite value is not initialized. 463 */ 464 RelatedAssociationModel fetchAssociationRelatedAssociation(long assocId, String assocTypeUri, 465 String myRoleTypeUri, String othersRoleTypeUri, String othersAssocTypeUri) { 466 List<RelatedAssociationModel> assocs = fetchAssociationRelatedAssociations(assocId, assocTypeUri, myRoleTypeUri, 467 othersRoleTypeUri, othersAssocTypeUri); 468 switch (assocs.size()) { 469 case 0: 470 return null; 471 case 1: 472 return assocs.get(0); 473 default: 474 throw new RuntimeException("Ambiguity: there are " + assocs.size() + " related associations (assocId=" + 475 assocId + ", assocTypeUri=\"" + assocTypeUri + "\", myRoleTypeUri=\"" + myRoleTypeUri + "\", " + 476 "othersRoleTypeUri=\"" + othersRoleTypeUri + "\", othersAssocTypeUri=\"" + othersAssocTypeUri + 477 "\"),\nresult=" + assocs); 478 } 479 } 480 481 /** 482 * @param assocTypeUri may be null 483 * @param myRoleTypeUri may be null 484 * @param othersRoleTypeUri may be null 485 * @param othersAssocTypeUri may be null 486 * 487 * @return The fetched associations. 488 * Note: their composite values are not initialized. 489 */ 490 List<RelatedAssociationModel> fetchAssociationRelatedAssociations(long assocId, String assocTypeUri, 491 String myRoleTypeUri, String othersRoleTypeUri, String othersAssocTypeUri) { 492 return storage.fetchAssociationRelatedAssociations(assocId, assocTypeUri, myRoleTypeUri, othersRoleTypeUri, 493 othersAssocTypeUri); 494 } 495 496 // --- 497 498 /** 499 * Convenience method (checks singularity). 500 * 501 * @param id id of a topic or an association 502 * @param assocTypeUri may be null 503 * @param myRoleTypeUri may be null 504 * @param othersRoleTypeUri may be null 505 * @param othersTopicTypeUri may be null 506 * 507 * @return The fetched topic. 508 * Note: its composite value is not initialized. 509 */ 510 RelatedTopicModel fetchRelatedTopic(long id, String assocTypeUri, String myRoleTypeUri, 511 String othersRoleTypeUri, String othersTopicTypeUri) { 512 ResultList<RelatedTopicModel> topics = fetchRelatedTopics(id, assocTypeUri, myRoleTypeUri, 513 othersRoleTypeUri, othersTopicTypeUri); 514 switch (topics.getSize()) { 515 case 0: 516 return null; 517 case 1: 518 return topics.iterator().next(); 519 default: 520 throw new RuntimeException("Ambiguity: there are " + topics.getSize() + " related topics (id=" + id + 521 ", assocTypeUri=\"" + assocTypeUri + "\", myRoleTypeUri=\"" + myRoleTypeUri + "\", " + 522 "othersRoleTypeUri=\"" + othersRoleTypeUri + "\", othersTopicTypeUri=\"" + othersTopicTypeUri + "\")"); 523 } 524 } 525 526 /** 527 * @param id id of a topic or an association 528 * @param assocTypeUri may be null 529 * @param myRoleTypeUri may be null 530 * @param othersRoleTypeUri may be null 531 * @param othersTopicTypeUri may be null 532 * 533 * @return The fetched topics. 534 * Note: their composite values are not initialized. 535 */ 536 ResultList<RelatedTopicModel> fetchRelatedTopics(long id, String assocTypeUri, String myRoleTypeUri, 537 String othersRoleTypeUri, String othersTopicTypeUri) { 538 List<RelatedTopicModel> relTopics = storage.fetchRelatedTopics(id, assocTypeUri, myRoleTypeUri, 539 othersRoleTypeUri, othersTopicTypeUri); 540 return new ResultList(relTopics.size(), relTopics); 541 } 542 543 // ### TODO: decorator for fetchRelatedAssociations() 544 545 546 547 // === Properties === 548 549 Object fetchTopicProperty(long topicId, String propUri) { 550 return storage.fetchTopicProperty(topicId, propUri); 551 } 552 553 Object fetchAssociationProperty(long assocId, String propUri) { 554 return storage.fetchAssociationProperty(assocId, propUri); 555 } 556 557 // --- 558 559 List<TopicModel> fetchTopicsByProperty(String propUri, Object propValue) { 560 return storage.fetchTopicsByProperty(propUri, propValue); 561 } 562 563 List<TopicModel> fetchTopicsByPropertyRange(String propUri, Number from, Number to) { 564 return storage.fetchTopicsByPropertyRange(propUri, from, to); 565 } 566 567 List<AssociationModel> fetchAssociationsByProperty(String propUri, Object propValue) { 568 return storage.fetchAssociationsByProperty(propUri, propValue); 569 } 570 571 List<AssociationModel> fetchAssociationsByPropertyRange(String propUri, Number from, Number to) { 572 return storage.fetchAssociationsByPropertyRange(propUri, from, to); 573 } 574 575 // --- 576 577 void storeTopicProperty(long topicId, String propUri, Object propValue, boolean addToIndex) { 578 storage.storeTopicProperty(topicId, propUri, propValue, addToIndex); 579 } 580 581 void storeAssociationProperty(long assocId, String propUri, Object propValue, boolean addToIndex) { 582 storage.storeAssociationProperty(assocId, propUri, propValue, addToIndex); 583 } 584 585 // --- 586 587 boolean hasTopicProperty(long topicId, String propUri) { 588 return storage.hasTopicProperty(topicId, propUri); 589 } 590 591 boolean hasAssociationProperty(long assocId, String propUri) { 592 return storage.hasAssociationProperty(assocId, propUri); 593 } 594 595 // --- 596 597 void removeTopicProperty(long topicId, String propUri) { 598 storage.deleteTopicProperty(topicId, propUri); 599 } 600 601 void removeAssociationProperty(long assocId, String propUri) { 602 storage.deleteAssociationProperty(assocId, propUri); 603 } 604 605 606 607 // === DB === 608 609 DeepaMehtaTransaction beginTx() { 610 return storage.beginTx(); 611 } 612 613 /** 614 * Initializes the database. 615 * Prerequisite: there is an open transaction. 616 * 617 * @return <code>true</code> if a clean install is detected, <code>false</code> otherwise. 618 */ 619 boolean init() { 620 boolean isCleanInstall = storage.setupRootNode(); 621 if (isCleanInstall) { 622 logger.info("Starting with a fresh DB -- Setting migration number to 0"); 623 storeMigrationNr(0); 624 } 625 return isCleanInstall; 626 } 627 628 void shutdown() { 629 storage.shutdown(); 630 } 631 632 int fetchMigrationNr() { 633 return (Integer) storage.fetchTopicProperty(0, "core_migration_nr"); 634 } 635 636 void storeMigrationNr(int migrationNr) { 637 storage.storeTopicProperty(0, "core_migration_nr", migrationNr, false); // addToIndex=false 638 } 639 }