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 and indexes the topic's value. ### TODO: separate storing/indexing?
124 */
125 void storeTopicValue(long topicId, SimpleValue value, List<IndexMode> indexModes, String indexKey,
126 SimpleValue indexValue) {
127 storage.storeTopicValue(topicId, value, indexModes, indexKey, indexValue);
128 }
129
130 void indexTopicValue(long topicId, IndexMode indexMode, String indexKey, SimpleValue indexValue) {
131 storage.indexTopicValue(topicId, indexMode, 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 and indexes the association's value. ### TODO: separate storing/indexing?
251 */
252 void storeAssociationValue(long assocId, SimpleValue value, List<IndexMode> indexModes, String indexKey,
253 SimpleValue indexValue) {
254 storage.storeAssociationValue(assocId, value, indexModes, indexKey, indexValue);
255 }
256
257 void indexAssociationValue(long assocId, IndexMode indexMode, String indexKey, SimpleValue indexValue) {
258 storage.indexAssociationValue(assocId, indexMode, 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 // ---
633
634 int fetchMigrationNr() {
635 return (Integer) storage.fetchTopicProperty(0, "core_migration_nr");
636 }
637
638 void storeMigrationNr(int migrationNr) {
639 storage.storeTopicProperty(0, "core_migration_nr", migrationNr, false); // addToIndex=false
640 }
641
642 // ---
643
644 Object getDatabaseVendorObject() {
645 return storage.getDatabaseVendorObject();
646 }
647 }