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 child topics are not fetched.
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 child topics are not fetched.
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 child topics are not fetched.
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 child topics are not fetched.
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 topic.
298         *          Note: its child topics are not fetched.
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 child topics are not fetched.
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 child topics are not fetched.
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 child topics are not fetched.
364         */
365        RelatedAssociationModel fetchTopicRelatedAssociation(long topicId, String assocTypeUri, String myRoleTypeUri,
366                                                             String othersRoleTypeUri, String othersAssocTypeUri) {
367            ResultList<RelatedAssociationModel> assocs = fetchTopicRelatedAssociations(topicId, assocTypeUri, myRoleTypeUri,
368                othersRoleTypeUri, othersAssocTypeUri);
369            switch (assocs.getSize()) {
370            case 0:
371                return null;
372            case 1:
373                return assocs.iterator().next();
374            default:
375                throw new RuntimeException("Ambiguity: there are " + assocs.getSize() + " 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 child topics are not fetched.
389         */
390        ResultList<RelatedAssociationModel> fetchTopicRelatedAssociations(long topicId, String assocTypeUri,
391                                                                          String myRoleTypeUri, String othersRoleTypeUri,
392                                                                          String othersAssocTypeUri) {
393            List<RelatedAssociationModel> relAssocs = storage.fetchTopicRelatedAssociations(topicId, assocTypeUri,
394                myRoleTypeUri, othersRoleTypeUri, othersAssocTypeUri);
395            return new ResultList(relAssocs.size(), relAssocs);
396        }
397    
398        // ---
399    
400        /**
401         * Convenience method (checks singularity).
402         *
403         * @return  The fetched topic.
404         *          Note: its child topics are not fetched.
405         */
406        RelatedTopicModel fetchAssociationRelatedTopic(long assocId, String assocTypeUri, String myRoleTypeUri,
407                                                              String othersRoleTypeUri, String othersTopicTypeUri) {
408            ResultList<RelatedTopicModel> topics = fetchAssociationRelatedTopics(assocId, assocTypeUri, myRoleTypeUri,
409                othersRoleTypeUri, othersTopicTypeUri, 0);
410            switch (topics.getSize()) {
411            case 0:
412                return null;
413            case 1:
414                return topics.iterator().next();
415            default:
416                throw new RuntimeException("Ambiguity: there are " + topics.getSize() + " related topics (assocId=" +
417                    assocId + ", assocTypeUri=\"" + assocTypeUri + "\", myRoleTypeUri=\"" + myRoleTypeUri + "\", " +
418                    "othersRoleTypeUri=\"" + othersRoleTypeUri + "\", othersTopicTypeUri=\"" + othersTopicTypeUri + "\")");
419            }
420        }
421    
422        /**
423         * @return  The fetched topics.
424         *          Note: their child topics are not fetched.
425         */
426        ResultList<RelatedTopicModel> fetchAssociationRelatedTopics(long assocId, String assocTypeUri,
427                                                                    String myRoleTypeUri, String othersRoleTypeUri,
428                                                                    String othersTopicTypeUri, int maxResultSize) {
429            List<RelatedTopicModel> relTopics = storage.fetchAssociationRelatedTopics(assocId, assocTypeUri, myRoleTypeUri,
430                othersRoleTypeUri, othersTopicTypeUri);
431            // ### TODO: respect maxResultSize
432            return new ResultList(relTopics.size(), relTopics);
433        }
434    
435        /**
436         * Convenience method (receives *list* of association types).
437         *
438         * @param   assocTypeUris       may be null
439         * @param   myRoleTypeUri       may be null
440         * @param   othersRoleTypeUri   may be null
441         * @param   othersTopicTypeUri  may be null
442         *
443         * @return  The fetched topics.
444         *          Note: their child topics are not fetched.
445         */
446        ResultList<RelatedTopicModel> fetchAssociationRelatedTopics(long assocId, List<String> assocTypeUris,
447                                                                    String myRoleTypeUri, String othersRoleTypeUri,
448                                                                    String othersTopicTypeUri, int maxResultSize) {
449            ResultList<RelatedTopicModel> result = new ResultList();
450            for (String assocTypeUri : assocTypeUris) {
451                ResultList<RelatedTopicModel> res = fetchAssociationRelatedTopics(assocId, assocTypeUri, myRoleTypeUri,
452                    othersRoleTypeUri, othersTopicTypeUri, maxResultSize);
453                result.addAll(res);
454            }
455            return result;
456        }
457    
458        // ---
459    
460        /**
461         * Convenience method (checks singularity).
462         *
463         * @return  The fetched association.
464         *          Note: its child topics are not fetched.
465         */
466        RelatedAssociationModel fetchAssociationRelatedAssociation(long assocId, String assocTypeUri,
467                                                String myRoleTypeUri, String othersRoleTypeUri, String othersAssocTypeUri) {
468            ResultList<RelatedAssociationModel> assocs = fetchAssociationRelatedAssociations(assocId, assocTypeUri,
469                myRoleTypeUri, othersRoleTypeUri, othersAssocTypeUri);
470            switch (assocs.getSize()) {
471            case 0:
472                return null;
473            case 1:
474                return assocs.iterator().next();
475            default:
476                throw new RuntimeException("Ambiguity: there are " + assocs.getSize() + " related associations (assocId=" +
477                    assocId + ", assocTypeUri=\"" + assocTypeUri + "\", myRoleTypeUri=\"" + myRoleTypeUri + "\", " +
478                    "othersRoleTypeUri=\"" + othersRoleTypeUri + "\", othersAssocTypeUri=\"" + othersAssocTypeUri +
479                    "\"),\nresult=" + assocs);
480            }
481        }
482    
483        /**
484         * @param   assocTypeUri        may be null
485         * @param   myRoleTypeUri       may be null
486         * @param   othersRoleTypeUri   may be null
487         * @param   othersAssocTypeUri  may be null
488         *
489         * @return  The fetched associations.
490         *          Note: their child topics are not fetched.
491         */
492        ResultList<RelatedAssociationModel> fetchAssociationRelatedAssociations(long assocId, String assocTypeUri,
493                                                                             String myRoleTypeUri, String othersRoleTypeUri,
494                                                                             String othersAssocTypeUri) {
495            List<RelatedAssociationModel> relAssocs = storage.fetchAssociationRelatedAssociations(assocId, assocTypeUri,
496                myRoleTypeUri, othersRoleTypeUri, othersAssocTypeUri);
497            return new ResultList(relAssocs.size(), relAssocs);
498        }
499    
500        // ---
501    
502        /**
503         * Convenience method (checks singularity).
504         *
505         * @param   objectId            id of a topic or an association
506         * @param   assocTypeUri        may be null
507         * @param   myRoleTypeUri       may be null
508         * @param   othersRoleTypeUri   may be null
509         * @param   othersTopicTypeUri  may be null
510         *
511         * @return  The fetched topic.
512         *          Note: its child topics are not fetched.
513         */
514        RelatedTopicModel fetchRelatedTopic(long objectId, String assocTypeUri, String myRoleTypeUri,
515                                                           String othersRoleTypeUri, String othersTopicTypeUri) {
516            ResultList<RelatedTopicModel> topics = fetchRelatedTopics(objectId, assocTypeUri, myRoleTypeUri,
517                othersRoleTypeUri, othersTopicTypeUri);
518            switch (topics.getSize()) {
519            case 0:
520                return null;
521            case 1:
522                return topics.iterator().next();
523            default:
524                throw new RuntimeException("Ambiguity: there are " + topics.getSize() + " related topics (objectId=" +
525                    objectId + ", assocTypeUri=\"" + assocTypeUri + "\", myRoleTypeUri=\"" + myRoleTypeUri + "\", " +
526                    "othersRoleTypeUri=\"" + othersRoleTypeUri + "\", othersTopicTypeUri=\"" + othersTopicTypeUri + "\")");
527            }
528        }
529    
530        /**
531         * @param   objectId            id of a topic or an association
532         * @param   assocTypeUri        may be null
533         * @param   myRoleTypeUri       may be null
534         * @param   othersRoleTypeUri   may be null
535         * @param   othersTopicTypeUri  may be null
536         *
537         * @return  The fetched topics.
538         *          Note: their child topics are not fetched.
539         */
540        ResultList<RelatedTopicModel> fetchRelatedTopics(long objectId, String assocTypeUri, String myRoleTypeUri,
541                                                                      String othersRoleTypeUri, String othersTopicTypeUri) {
542            List<RelatedTopicModel> relTopics = storage.fetchRelatedTopics(objectId, assocTypeUri, myRoleTypeUri,
543                othersRoleTypeUri, othersTopicTypeUri);
544            return new ResultList(relTopics.size(), relTopics);
545        }
546    
547        // ### TODO: decorator for fetchRelatedAssociations()
548    
549    
550    
551        // === Properties ===
552    
553        Object fetchTopicProperty(long topicId, String propUri) {
554            return storage.fetchTopicProperty(topicId, propUri);
555        }
556    
557        Object fetchAssociationProperty(long assocId, String propUri) {
558            return storage.fetchAssociationProperty(assocId, propUri);
559        }
560    
561        // ---
562    
563        List<TopicModel> fetchTopicsByProperty(String propUri, Object propValue) {
564            return storage.fetchTopicsByProperty(propUri, propValue);
565        }
566    
567        List<TopicModel> fetchTopicsByPropertyRange(String propUri, Number from, Number to) {
568            return storage.fetchTopicsByPropertyRange(propUri, from, to);
569        }
570    
571        List<AssociationModel> fetchAssociationsByProperty(String propUri, Object propValue) {
572            return storage.fetchAssociationsByProperty(propUri, propValue);
573        }
574    
575        List<AssociationModel> fetchAssociationsByPropertyRange(String propUri, Number from, Number to) {
576            return storage.fetchAssociationsByPropertyRange(propUri, from, to);
577        }
578    
579        // ---
580    
581        void storeTopicProperty(long topicId, String propUri, Object propValue, boolean addToIndex) {
582            storage.storeTopicProperty(topicId, propUri, propValue, addToIndex);
583        }
584    
585        void storeAssociationProperty(long assocId, String propUri, Object propValue, boolean addToIndex) {
586            storage.storeAssociationProperty(assocId, propUri, propValue, addToIndex);
587        }
588    
589        // ---
590    
591        boolean hasTopicProperty(long topicId, String propUri) {
592            return storage.hasTopicProperty(topicId, propUri);
593        }
594    
595        boolean hasAssociationProperty(long assocId, String propUri) {
596            return storage.hasAssociationProperty(assocId, propUri);
597        }
598    
599        // ---
600    
601        void removeTopicProperty(long topicId, String propUri) {
602            storage.deleteTopicProperty(topicId, propUri);
603        }
604    
605        void removeAssociationProperty(long assocId, String propUri) {
606            storage.deleteAssociationProperty(assocId, propUri);
607        }
608    
609    
610    
611        // === DB ===
612    
613        DeepaMehtaTransaction beginTx() {
614            return storage.beginTx();
615        }
616    
617        /**
618         * Initializes the database.
619         * Prerequisite: there is an open transaction.
620         *
621         * @return  <code>true</code> if a clean install is detected, <code>false</code> otherwise.
622         */
623        boolean init() {
624            boolean isCleanInstall = storage.setupRootNode();
625            if (isCleanInstall) {
626                logger.info("Starting with a fresh DB -- Setting migration number to 0");
627                storeMigrationNr(0);
628            }
629            return isCleanInstall;
630        }
631    
632        void shutdown() {
633            storage.shutdown();
634        }
635    
636        // ---
637    
638        int fetchMigrationNr() {
639            return (Integer) storage.fetchTopicProperty(0, "core_migration_nr");
640        }
641    
642        void storeMigrationNr(int migrationNr) {
643            storage.storeTopicProperty(0, "core_migration_nr", migrationNr, false);     // addToIndex=false
644        }
645    
646        // ---
647    
648        Object getDatabaseVendorObject() {
649            return storage.getDatabaseVendorObject();
650        }
651    
652        Object getDatabaseVendorObject(long objectId) {
653            return storage.getDatabaseVendorObject(objectId);
654        }
655    }