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