001    package de.deepamehta.core.impl;
002    
003    import de.deepamehta.core.Association;
004    import de.deepamehta.core.AssociationDefinition;
005    import de.deepamehta.core.AssociationType;
006    import de.deepamehta.core.RelatedAssociation;
007    import de.deepamehta.core.RelatedTopic;
008    import de.deepamehta.core.Topic;
009    import de.deepamehta.core.TopicType;
010    import de.deepamehta.core.Type;
011    import de.deepamehta.core.model.AssociationModel;
012    import de.deepamehta.core.model.AssociationRoleModel;
013    import de.deepamehta.core.model.AssociationTypeModel;
014    import de.deepamehta.core.model.DeepaMehtaObjectModel;
015    import de.deepamehta.core.model.RelatedAssociationModel;
016    import de.deepamehta.core.model.RelatedTopicModel;
017    import de.deepamehta.core.model.RoleModel;
018    import de.deepamehta.core.model.SimpleValue;
019    import de.deepamehta.core.model.TopicModel;
020    import de.deepamehta.core.model.TopicRoleModel;
021    import de.deepamehta.core.model.TopicTypeModel;
022    import de.deepamehta.core.service.DeepaMehtaEvent;
023    import de.deepamehta.core.service.DeepaMehtaService;
024    import de.deepamehta.core.service.Plugin;
025    import de.deepamehta.core.service.PluginInfo;
026    import de.deepamehta.core.service.ResultList;
027    import de.deepamehta.core.service.TypeStorage;
028    import de.deepamehta.core.storage.spi.DeepaMehtaTransaction;
029    
030    import org.osgi.framework.BundleContext;
031    
032    import java.util.ArrayList;
033    import java.util.List;
034    import java.util.logging.Logger;
035    
036    
037    
038    /**
039     * Implementation of the DeepaMehta core service. Embeddable into Java applications.
040     */
041    public class EmbeddedService implements DeepaMehtaService {
042    
043        // ------------------------------------------------------------------------------------------------------- Constants
044    
045        private static final String URI_PREFIX_TOPIC_TYPE       = "domain.project.topic_type_";
046        private static final String URI_PREFIX_ASSOCIATION_TYPE = "domain.project.assoc_type_";
047        private static final String URI_PREFIX_ROLE_TYPE        = "domain.project.role_type_";
048    
049        // ---------------------------------------------------------------------------------------------- Instance Variables
050    
051        StorageDecorator storageDecorator;
052        BundleContext bundleContext;
053        MigrationManager migrationManager;
054        PluginManager pluginManager;
055        EventManager eventManager;
056        TypeCache typeCache;
057        TypeStorageImpl typeStorage;
058        ValueStorage valueStorage;
059    
060        private Logger logger = Logger.getLogger(getClass().getName());
061    
062        // ---------------------------------------------------------------------------------------------------- Constructors
063    
064        /**
065         * @param   bundleContext   The context of the DeepaMehta 4 Core bundle.
066         */
067        public EmbeddedService(StorageDecorator storageDecorator, BundleContext bundleContext) {
068            this.storageDecorator = storageDecorator;
069            this.bundleContext = bundleContext;
070            this.migrationManager = new MigrationManager(this);
071            this.pluginManager = new PluginManager(this);
072            this.eventManager = new EventManager(this);
073            this.typeCache = new TypeCache(this);
074            this.typeStorage = new TypeStorageImpl(this);
075            this.valueStorage = new ValueStorage(this);
076            bootstrapTypeCache();
077            setupDB();
078        }
079    
080        // -------------------------------------------------------------------------------------------------- Public Methods
081    
082    
083    
084        // ****************************************
085        // *** DeepaMehtaService Implementation ***
086        // ****************************************
087    
088    
089    
090        // === Topics ===
091    
092        @Override
093        public Topic getTopic(long topicId) {
094            try {
095                return instantiateTopic(storageDecorator.fetchTopic(topicId));
096            } catch (Exception e) {
097                throw new RuntimeException("Fetching topic " + topicId + " failed", e);
098            }
099        }
100    
101        @Override
102        public Topic getTopic(String key, SimpleValue value) {
103            try {
104                TopicModel topic = storageDecorator.fetchTopic(key, value);
105                return topic != null ? instantiateTopic(topic) : null;
106            } catch (Exception e) {
107                throw new RuntimeException("Fetching topic failed (key=\"" + key + "\", value=\"" + value + "\")", e);
108            }
109        }
110    
111        @Override
112        public List<Topic> getTopics(String key, SimpleValue value) {
113            try {
114                return instantiateTopics(storageDecorator.fetchTopics(key, value));
115            } catch (Exception e) {
116                throw new RuntimeException("Fetching topics failed (key=\"" + key + "\", value=\"" + value + "\")", e);
117            }
118        }
119    
120        @Override
121        public ResultList<RelatedTopic> getTopics(String topicTypeUri, int maxResultSize) {
122            try {
123                return getTopicType(topicTypeUri).getRelatedTopics("dm4.core.instantiation", "dm4.core.type",
124                    "dm4.core.instance", topicTypeUri, maxResultSize);
125            } catch (Exception e) {
126                throw new RuntimeException("Fetching topics by type failed (topicTypeUri=\"" + topicTypeUri + "\")", e);
127            }
128        }
129    
130        @Override
131        public List<Topic> searchTopics(String searchTerm, String fieldUri) {
132            try {
133                return instantiateTopics(storageDecorator.queryTopics(searchTerm, fieldUri));
134            } catch (Exception e) {
135                throw new RuntimeException("Searching topics failed (searchTerm=\"" + searchTerm + "\", fieldUri=\"" +
136                    fieldUri + "\")", e);
137            }
138        }
139    
140        @Override
141        public Iterable<Topic> getAllTopics() {
142            return new TopicIterable(this);
143        }
144    
145        // ---
146    
147        @Override
148        public Topic createTopic(TopicModel model) {
149            return createTopic(model, null);    // uriPrefix=null
150        }
151    
152        @Override
153        public void updateTopic(TopicModel model) {
154            try {
155                getTopic(model.getId()).update(model);
156            } catch (Exception e) {
157                throw new RuntimeException("Updating topic failed (" + model + ")", e);
158            }
159        }
160    
161        @Override
162        public void deleteTopic(long topicId) {
163            try {
164                getTopic(topicId).delete();
165            } catch (Exception e) {
166                throw new RuntimeException("Deleting topic " + topicId + " failed", e);
167            }
168        }
169    
170    
171    
172        // === Associations ===
173    
174        @Override
175        public Association getAssociation(long assocId) {
176            logger.info("assocId=" + assocId);
177            try {
178                return instantiateAssociation(storageDecorator.fetchAssociation(assocId));
179            } catch (Exception e) {
180                throw new RuntimeException("Fetching association " + assocId + " failed", e);
181            }
182        }
183    
184        @Override
185        public Association getAssociation(String assocTypeUri, long topic1Id, long topic2Id,
186                                                               String roleTypeUri1, String roleTypeUri2) {
187            String info = "assocTypeUri=\"" + assocTypeUri + "\", topic1Id=" + topic1Id + ", topic2Id=" + topic2Id +
188                ", roleTypeUri1=\"" + roleTypeUri1 + "\", roleTypeUri2=\"" + roleTypeUri2 + "\"";
189            try {
190                AssociationModel assoc = storageDecorator.fetchAssociation(assocTypeUri, topic1Id, topic2Id, roleTypeUri1,
191                    roleTypeUri2);
192                return assoc != null ? instantiateAssociation(assoc) : null;
193            } catch (Exception e) {
194                throw new RuntimeException("Fetching association failed (" + info + ")", e);
195            }
196        }
197    
198        @Override
199        public Association getAssociationBetweenTopicAndAssociation(String assocTypeUri, long topicId, long assocId,
200                                                                    String topicRoleTypeUri, String assocRoleTypeUri) {
201            String info = "assocTypeUri=\"" + assocTypeUri + "\", topicId=" + topicId + ", assocId=" + assocId +
202                ", topicRoleTypeUri=\"" + topicRoleTypeUri + "\", assocRoleTypeUri=\"" + assocRoleTypeUri + "\"";
203            logger.info(info);
204            try {
205                AssociationModel assoc = storageDecorator.fetchAssociationBetweenTopicAndAssociation(assocTypeUri,
206                    topicId, assocId, topicRoleTypeUri, assocRoleTypeUri);
207                return assoc != null ? instantiateAssociation(assoc) : null;
208            } catch (Exception e) {
209                throw new RuntimeException("Fetching association failed (" + info + ")", e);
210            }
211        }
212    
213        // ---
214    
215        @Override
216        public ResultList<RelatedAssociation> getAssociations(String assocTypeUri) {
217            try {
218                return getAssociationType(assocTypeUri).getRelatedAssociations("dm4.core.instantiation",
219                    "dm4.core.type", "dm4.core.instance", assocTypeUri);
220            } catch (Exception e) {
221                throw new RuntimeException("Fetching associations by type failed (assocTypeUri=\"" + assocTypeUri + "\")",
222                    e);
223            }
224        }
225    
226        @Override
227        public List<Association> getAssociations(long topic1Id, long topic2Id) {
228            return getAssociations(topic1Id, topic2Id, null);
229        }
230    
231        @Override
232        public List<Association> getAssociations(long topic1Id, long topic2Id, String assocTypeUri) {
233            logger.info("topic1Id=" + topic1Id + ", topic2Id=" + topic2Id + ", assocTypeUri=\"" + assocTypeUri + "\"");
234            try {
235                return instantiateAssociations(storageDecorator.fetchAssociations(assocTypeUri, topic1Id, topic2Id,
236                    null, null));     // roleTypeUri1=null, roleTypeUri2=null
237            } catch (Exception e) {
238                throw new RuntimeException("Fetching associations between topics " + topic1Id + " and " + topic2Id +
239                    " failed (assocTypeUri=\"" + assocTypeUri + "\")", e);
240            }
241        }
242    
243        // ---
244    
245        @Override
246        public Iterable<Association> getAllAssociations() {
247            return new AssociationIterable(this);
248        }
249    
250        // ---
251    
252        @Override
253        public Association createAssociation(AssociationModel model) {
254            try {
255                fireEvent(CoreEvent.PRE_CREATE_ASSOCIATION, model);
256                Association assoc = associationFactory(model);
257                fireEvent(CoreEvent.POST_CREATE_ASSOCIATION, assoc);
258                return assoc;
259            } catch (Exception e) {
260                throw new RuntimeException("Creating association failed (" + model + ")", e);
261            }
262        }
263    
264        @Override
265        public void updateAssociation(AssociationModel model) {
266            try {
267                getAssociation(model.getId()).update(model);
268            } catch (Exception e) {
269                throw new RuntimeException("Updating association failed (" + model + ")", e);
270            }
271        }
272    
273        @Override
274        public void deleteAssociation(long assocId) {
275            try {
276                getAssociation(assocId).delete();
277            } catch (Exception e) {
278                throw new RuntimeException("Deleting association " + assocId + " failed", e);
279            }
280        }
281    
282    
283    
284        // === Topic Types ===
285    
286        @Override
287        public List<String> getTopicTypeUris() {
288            try {
289                Topic metaType = instantiateTopic(storageDecorator.fetchTopic("uri",
290                    new SimpleValue("dm4.core.topic_type")));
291                ResultList<RelatedTopic> topicTypes = metaType.getRelatedTopics("dm4.core.instantiation", "dm4.core.type",
292                    "dm4.core.instance", "dm4.core.topic_type", 0);
293                List<String> topicTypeUris = new ArrayList();
294                // add meta types
295                topicTypeUris.add("dm4.core.topic_type");
296                topicTypeUris.add("dm4.core.assoc_type");
297                topicTypeUris.add("dm4.core.meta_type");
298                topicTypeUris.add("dm4.core.meta_meta_type");
299                // add regular types
300                for (Topic topicType : topicTypes) {
301                    topicTypeUris.add(topicType.getUri());
302                }
303                return topicTypeUris;
304            } catch (Exception e) {
305                throw new RuntimeException("Fetching list of topic type URIs failed", e);
306            }
307        }
308    
309        @Override
310        public TopicType getTopicType(String uri) {
311            try {
312                return typeCache.getTopicType(uri);
313            } catch (Exception e) {
314                throw new RuntimeException("Fetching topic type \"" + uri + "\" failed", e);
315            }
316        }
317    
318        @Override
319        public List<TopicType> getAllTopicTypes() {
320            try {
321                List<TopicType> topicTypes = new ArrayList();
322                for (String uri : getTopicTypeUris()) {
323                    TopicType topicType = getTopicType(uri);
324                    topicTypes.add(topicType);
325                }
326                return topicTypes;
327            } catch (Exception e) {
328                throw new RuntimeException("Fetching all topic types failed", e);
329            }
330        }
331    
332        // ---
333    
334        @Override
335        public TopicType createTopicType(TopicTypeModel model) {
336            try {
337                TopicType topicType = topicTypeFactory(model);
338                fireEvent(CoreEvent.INTRODUCE_TOPIC_TYPE, topicType);
339                return topicType;
340            } catch (Exception e) {
341                throw new RuntimeException("Creating topic type \"" + model.getUri() + "\" failed (" + model + ")", e);
342            }
343        }
344    
345        @Override
346        public void updateTopicType(TopicTypeModel model) {
347            try {
348                // Note: type lookup is by ID. The URI might have changed, the ID does not.
349                String topicTypeUri = getTopic(model.getId()).getUri();
350                getTopicType(topicTypeUri).update(model);
351            } catch (Exception e) {
352                throw new RuntimeException("Updating topic type failed (" + model + ")", e);
353            }
354        }
355    
356        @Override
357        public void deleteTopicType(String topicTypeUri) {
358            try {
359                getTopicType(topicTypeUri).delete();
360            } catch (Exception e) {
361                throw new RuntimeException("Deleting topic type \"" + topicTypeUri + "\" failed", e);
362            }
363        }
364    
365    
366    
367        // === Association Types ===
368    
369        @Override
370        public List<String> getAssociationTypeUris() {
371            try {
372                Topic metaType = instantiateTopic(storageDecorator.fetchTopic("uri",
373                    new SimpleValue("dm4.core.assoc_type")));
374                ResultList<RelatedTopic> assocTypes = metaType.getRelatedTopics("dm4.core.instantiation", "dm4.core.type",
375                    "dm4.core.instance", "dm4.core.assoc_type", 0);
376                List<String> assocTypeUris = new ArrayList();
377                for (Topic assocType : assocTypes) {
378                    assocTypeUris.add(assocType.getUri());
379                }
380                return assocTypeUris;
381            } catch (Exception e) {
382                throw new RuntimeException("Fetching list of association type URIs failed", e);
383            }
384        }
385    
386        @Override
387        public AssociationType getAssociationType(String uri) {
388            try {
389                return typeCache.getAssociationType(uri);
390            } catch (Exception e) {
391                throw new RuntimeException("Fetching association type \"" + uri + "\" failed", e);
392            }
393        }
394    
395        @Override
396        public List<AssociationType> getAllAssociationTypes() {
397            try {
398                List<AssociationType> assocTypes = new ArrayList();
399                for (String uri : getAssociationTypeUris()) {
400                    AssociationType assocType = getAssociationType(uri);
401                    assocTypes.add(assocType);
402                }
403                return assocTypes;
404            } catch (Exception e) {
405                throw new RuntimeException("Fetching all association types failed", e);
406            }
407        }
408    
409        // ---
410    
411        @Override
412        public AssociationType createAssociationType(AssociationTypeModel model) {
413            try {
414                AssociationType assocType = associationTypeFactory(model);
415                fireEvent(CoreEvent.INTRODUCE_ASSOCIATION_TYPE, assocType);
416                return assocType;
417            } catch (Exception e) {
418                throw new RuntimeException("Creating association type \"" + model.getUri() + "\" failed (" + model + ")",
419                    e);
420            }
421        }
422    
423        @Override
424        public void updateAssociationType(AssociationTypeModel model) {
425            try {
426                // Note: type lookup is by ID. The URI might have changed, the ID does not.
427                String assocTypeUri = getTopic(model.getId()).getUri();
428                getAssociationType(assocTypeUri).update(model);
429            } catch (Exception e) {
430                throw new RuntimeException("Updating association type failed (" + model + ")", e);
431            }
432        }
433    
434        @Override
435        public void deleteAssociationType(String assocTypeUri) {
436            try {
437                getAssociationType(assocTypeUri).delete();
438            } catch (Exception e) {
439                throw new RuntimeException("Deleting association type \"" + assocTypeUri + "\" failed", e);
440            }
441        }
442    
443    
444    
445        // === Role Types ===
446    
447        @Override
448        public Topic createRoleType(TopicModel model) {
449            // check type URI argument
450            String typeUri = model.getTypeUri();
451            if (typeUri == null) {
452                model.setTypeUri("dm4.core.role_type");
453            } else {
454                if (!typeUri.equals("dm4.core.role_type")) {
455                    throw new IllegalArgumentException("A role type is supposed to be of type \"dm4.core.role_type\" " +
456                        "(found: \"" + typeUri + "\")");
457                }
458            }
459            //
460            return createTopic(model, URI_PREFIX_ROLE_TYPE);
461        }
462    
463    
464    
465        // === Plugins ===
466    
467        @Override
468        public Plugin getPlugin(String pluginUri) {
469            return pluginManager.getPlugin(pluginUri);
470        }
471    
472        @Override
473        public List<PluginInfo> getPluginInfo() {
474            return pluginManager.getPluginInfo();
475        }
476    
477    
478    
479        // === Events ===
480    
481        @Override
482        public void fireEvent(DeepaMehtaEvent event, Object... params) {
483            eventManager.fireEvent(event, params);
484        }
485    
486        @Override
487        public void deliverEvent(String pluginUri, DeepaMehtaEvent event, Object... params) {
488            eventManager.deliverEvent(pluginUri, event, params);
489        }
490    
491    
492    
493        // === Properties ===
494    
495        @Override
496        public List<Topic> getTopicsByProperty(String propUri, Object propValue) {
497            return instantiateTopics(storageDecorator.fetchTopicsByProperty(propUri, propValue));
498        }
499    
500        @Override
501        public List<Topic> getTopicsByPropertyRange(String propUri, Number from, Number to) {
502            return instantiateTopics(storageDecorator.fetchTopicsByPropertyRange(propUri, from, to));
503        }
504    
505        @Override
506        public List<Association> getAssociationsByProperty(String propUri, Object propValue) {
507            return instantiateAssociations(storageDecorator.fetchAssociationsByProperty(propUri, propValue));
508        }
509    
510        @Override
511        public List<Association> getAssociationsByPropertyRange(String propUri, Number from, Number to) {
512            return instantiateAssociations(storageDecorator.fetchAssociationsByPropertyRange(propUri, from, to));
513        }
514    
515    
516    
517        // === Misc ===
518    
519        @Override
520        public DeepaMehtaTransaction beginTx() {
521            return storageDecorator.beginTx();
522        }
523    
524        @Override
525        public TypeStorage getTypeStorage() {
526            return typeStorage;
527        }
528    
529        @Override
530        public Object getDatabaseVendorObject() {
531            return storageDecorator.getDatabaseVendorObject();
532        }
533    
534    
535    
536        // ----------------------------------------------------------------------------------------- Package Private Methods
537    
538    
539    
540        // === Helper ===
541    
542        void createTopicInstantiation(long topicId, String topicTypeUri) {
543            try {
544                AssociationModel assoc = new AssociationModel("dm4.core.instantiation",
545                    new TopicRoleModel(topicTypeUri, "dm4.core.type"),
546                    new TopicRoleModel(topicId, "dm4.core.instance"));
547                storageDecorator.storeAssociation(assoc);   // direct storage calls used here ### explain
548                storageDecorator.storeAssociationValue(assoc.getId(), assoc.getSimpleValue());
549                createAssociationInstantiation(assoc.getId(), assoc.getTypeUri());
550            } catch (Exception e) {
551                throw new RuntimeException("Associating topic " + topicId +
552                    " with topic type \"" + topicTypeUri + "\" failed", e);
553            }
554        }
555    
556        void createAssociationInstantiation(long assocId, String assocTypeUri) {
557            try {
558                AssociationModel assoc = new AssociationModel("dm4.core.instantiation",
559                    new TopicRoleModel(assocTypeUri, "dm4.core.type"),
560                    new AssociationRoleModel(assocId, "dm4.core.instance"));
561                storageDecorator.storeAssociation(assoc);   // direct storage calls used here ### explain
562                storageDecorator.storeAssociationValue(assoc.getId(), assoc.getSimpleValue());
563            } catch (Exception e) {
564                throw new RuntimeException("Associating association " + assocId +
565                    " with association type \"" + assocTypeUri + "\" failed", e);
566            }
567        }
568    
569        // ---
570    
571        /**
572         * Convenience method. ### to be dropped?
573         */
574        Association createAssociation(String typeUri, RoleModel roleModel1, RoleModel roleModel2) {
575            return createAssociation(new AssociationModel(typeUri, roleModel1, roleModel2));
576        }
577    
578        // ------------------------------------------------------------------------------------------------- Private Methods
579    
580    
581    
582        // === Instantiation ===
583    
584        /**
585         * Attaches this core service to a topic model fetched from storage layer.
586         */
587        Topic instantiateTopic(TopicModel model) {
588            return new AttachedTopic(model, this);
589        }
590    
591        private List<Topic> instantiateTopics(List<TopicModel> models) {
592            List<Topic> topics = new ArrayList();
593            for (TopicModel model : models) {
594                topics.add(instantiateTopic(model));
595            }
596            return topics;
597        }
598    
599        // ---
600    
601        RelatedTopic instantiateRelatedTopic(RelatedTopicModel model) {
602            return new AttachedRelatedTopic(model, this);
603        }
604    
605        ResultList<RelatedTopic> instantiateRelatedTopics(ResultList<RelatedTopicModel> models) {
606            List<RelatedTopic> relTopics = new ArrayList();
607            for (RelatedTopicModel model : models) {
608                relTopics.add(instantiateRelatedTopic(model));
609            }
610            return new ResultList<RelatedTopic>(models.getTotalCount(), relTopics);
611        }
612    
613        // ===
614    
615        /**
616         * Attaches this core service to an association fetched from storage layer.
617         */
618        Association instantiateAssociation(AssociationModel model) {
619            return new AttachedAssociation(model, this);
620        }
621    
622        List<Association> instantiateAssociations(List<AssociationModel> models) {
623            List<Association> assocs = new ArrayList();
624            for (AssociationModel model : models) {
625                assocs.add(instantiateAssociation(model));
626            }
627            return assocs;
628        }
629    
630        // ---
631    
632        RelatedAssociation instantiateRelatedAssociation(RelatedAssociationModel model) {
633            return new AttachedRelatedAssociation(model, this);
634        }
635    
636        ResultList<RelatedAssociation> instantiateRelatedAssociations(Iterable<RelatedAssociationModel> models) {
637            ResultList<RelatedAssociation> relAssocs = new ResultList();
638            for (RelatedAssociationModel model : models) {
639                relAssocs.add(instantiateRelatedAssociation(model));
640            }
641            return relAssocs;
642        }
643    
644    
645    
646        // === Factory ===
647    
648        private Topic createTopic(TopicModel model, String uriPrefix) {
649            try {
650                fireEvent(CoreEvent.PRE_CREATE_TOPIC, model);
651                Topic topic = topicFactory(model, uriPrefix);
652                fireEvent(CoreEvent.POST_CREATE_TOPIC, topic);
653                return topic;
654            } catch (Exception e) {
655                throw new RuntimeException("Creating topic failed (" + model + ")", e);
656            }
657        }
658    
659        // ---
660    
661        /**
662         * Factory method: creates a new topic in the DB according to the given topic model and returns a topic instance.
663         */
664        private Topic topicFactory(TopicModel model, String uriPrefix) {
665            // 1) store in DB
666            storageDecorator.storeTopic(model);
667            valueStorage.storeValue(model);
668            createTopicInstantiation(model.getId(), model.getTypeUri());
669            //
670            // 2) instantiate
671            Topic topic = new AttachedTopic(model, this);
672            //
673            // 3) set default URI
674            // If no URI is given the topic gets a default URI based on its ID, if requested.
675            // Note: this must be done *after* the topic is stored. The ID is not known before.
676            // Note: in case no URI was given: once stored a topic's URI is empty (not null).
677            if (uriPrefix != null && topic.getUri().equals("")) {
678                topic.setUri(uriPrefix + topic.getId());
679            }
680            //
681            return topic;
682        }
683    
684        /**
685         * Factory method: creates a new association in the DB according to the given association model and returns an
686         * association instance.
687         */
688        private Association associationFactory(AssociationModel model) {
689            // 1) store in DB
690            storageDecorator.storeAssociation(model);
691            valueStorage.storeValue(model);
692            createAssociationInstantiation(model.getId(), model.getTypeUri());
693            //
694            // 2) instantiate
695            return new AttachedAssociation(model, this);
696        }
697    
698        // ---
699    
700        /**
701         * Factory method: creates a new topic type in the DB according to the given topic type model
702         * and returns a topic type instance.
703         */
704        private TopicType topicTypeFactory(TopicTypeModel model) {
705            // 1) store in DB
706            topicFactory(model, URI_PREFIX_TOPIC_TYPE);         // store generic topic
707            typeStorage.storeType(model);                       // store type-specific parts
708            //
709            // 2) instantiate
710            TopicType topicType = new AttachedTopicType(model, this);
711            typeCache.putTopicType(topicType);
712            //
713            return topicType;
714        }
715    
716        /**
717         * Factory method: creates a new association type in the DB according to the given association type model
718         * and returns a topic type instance.
719         */
720        private AssociationType associationTypeFactory(AssociationTypeModel model) {
721            // 1) store in DB
722            topicFactory(model, URI_PREFIX_ASSOCIATION_TYPE);   // store generic topic
723            typeStorage.storeType(model);                       // store type-specific parts
724            //
725            // 2) instantiate
726            AssociationType assocType = new AttachedAssociationType(model, this);
727            typeCache.putAssociationType(assocType);
728            //
729            return assocType;
730        }
731    
732    
733    
734        // === Bootstrap ===
735    
736        /**
737         * Setups the database:
738         *   1) initializes the database.
739         *   2) in case of a clean install: sets up the bootstrap content.
740         *   3) runs the core migrations.
741         */
742        private void setupDB() {
743            DeepaMehtaTransaction tx = beginTx();
744            try {
745                logger.info("----- Setting up the database -----");
746                boolean isCleanInstall = storageDecorator.init();
747                if (isCleanInstall) {
748                    setupBootstrapContent();
749                }
750                migrationManager.runCoreMigrations(isCleanInstall);
751                tx.success();
752                tx.finish();
753                logger.info("----- Setting up the database complete -----");
754            } catch (Exception e) {
755                logger.warning("ROLLBACK!");
756                // Note: we don't put finish() in a finally clause here because
757                // in case of error the database has to be shut down.
758                tx.finish();
759                storageDecorator.shutdown();
760                throw new RuntimeException("Setting up the database failed", e);
761            }
762        }
763    
764        private void setupBootstrapContent() {
765            try {
766                // Create meta types "Topic Type" and "Association Type" -- needed to create topic types and
767                // asscociation types
768                TopicModel t = new TopicModel("dm4.core.topic_type", "dm4.core.meta_type",
769                    new SimpleValue("Topic Type"));
770                TopicModel a = new TopicModel("dm4.core.assoc_type", "dm4.core.meta_type",
771                    new SimpleValue("Association Type"));
772                _createTopic(t);
773                _createTopic(a);
774                // Create topic types "Data Type" and "Role Type"
775                // ### Note: the topic type "Data Type" depends on the data type "Text" and the data type "Text" in turn
776                // depends on the topic type "Data Type". To resolve this circle we use a low-level (storage) call here
777                // and postpone the data type association.
778                TopicModel dataType = new TopicTypeModel("dm4.core.data_type", "Data Type", "dm4.core.text");
779                TopicModel roleType = new TopicTypeModel("dm4.core.role_type", "Role Type", "dm4.core.text");
780                _createTopic(dataType);
781                _createTopic(roleType);
782                // Create data type "Text"
783                TopicModel text = new TopicModel("dm4.core.text", "dm4.core.data_type", new SimpleValue("Text"));
784                _createTopic(text);
785                // Create role types "Default", "Type", and "Instance"
786                TopicModel deflt = new TopicModel("dm4.core.default",  "dm4.core.role_type", new SimpleValue("Default"));
787                TopicModel type  = new TopicModel("dm4.core.type",     "dm4.core.role_type", new SimpleValue("Type"));
788                TopicModel inst  = new TopicModel("dm4.core.instance", "dm4.core.role_type", new SimpleValue("Instance"));
789                _createTopic(deflt);
790                _createTopic(type);
791                _createTopic(inst);
792                // Create association type "Aggregation" -- needed to associate topic/association types with data types
793                TopicModel aggregation = new AssociationTypeModel("dm4.core.aggregation", "Aggregation", "dm4.core.text");
794                _createTopic(aggregation);
795                // Create association type "Instantiation" -- needed to associate topics with topic types
796                TopicModel instn = new AssociationTypeModel("dm4.core.instantiation", "Instantiation", "dm4.core.text");
797                _createTopic(instn);
798                //
799                // 1) Postponed topic type association
800                //
801                // Note: createTopicInstantiation() creates the associations by *low-level* (storage) calls.
802                // That's why the associations can be created *before* their type (here: "dm4.core.instantiation")
803                // is fully constructed (the type's data type is not yet associated => step 2).
804                createTopicInstantiation(t.getId(), t.getTypeUri());
805                createTopicInstantiation(a.getId(), a.getTypeUri());
806                createTopicInstantiation(dataType.getId(), dataType.getTypeUri());
807                createTopicInstantiation(roleType.getId(), roleType.getTypeUri());
808                createTopicInstantiation(text.getId(), text.getTypeUri());
809                createTopicInstantiation(deflt.getId(), deflt.getTypeUri());
810                createTopicInstantiation(type.getId(), type.getTypeUri());
811                createTopicInstantiation(inst.getId(), inst.getTypeUri());
812                createTopicInstantiation(aggregation.getId(), aggregation.getTypeUri());
813                createTopicInstantiation(instn.getId(), instn.getTypeUri());
814                //
815                // 2) Postponed data type association
816                //
817                // Note: associateDataType() creates the association by a *high-level* (service) call.
818                // This requires the association type (here: dm4.core.aggregation) to be fully constructed already.
819                // That's why the topic type associations (step 1) must be performed *before* the data type associations.
820                // ### FIXDOC: not true anymore
821                //
822                // Note: at time of the first associateDataType() call the required association type (dm4.core.aggregation)
823                // is *not* fully constructed yet! (it gets constructed through this very call). This works anyway because
824                // the data type assigning association is created *before* the association type is fetched.
825                // (see AttachedAssociation.store(): storage.storeAssociation() is called before getType()
826                // in AttachedDeepaMehtaObject.store().)
827                // ### FIXDOC: not true anymore
828                //
829                // Important is that associateDataType("dm4.core.aggregation") is the first call here.
830                // ### FIXDOC: not true anymore
831                //
832                // Note: _associateDataType() creates the data type assigning association by a *low-level* (storage) call.
833                // A high-level (service) call would fail while setting the association's value. The involved getType()
834                // would fail (not because the association is missed -- it's created meanwhile, but)
835                // because this involves fetching the association including its value. The value doesn't exist yet,
836                // because its setting forms the begin of this vicious circle.
837                _associateDataType("dm4.core.meta_type",  "dm4.core.text");
838                _associateDataType("dm4.core.topic_type", "dm4.core.text");
839                _associateDataType("dm4.core.assoc_type", "dm4.core.text");
840                _associateDataType("dm4.core.data_type",  "dm4.core.text");
841                _associateDataType("dm4.core.role_type",  "dm4.core.text");
842                //
843                _associateDataType("dm4.core.aggregation",   "dm4.core.text");
844                _associateDataType("dm4.core.instantiation", "dm4.core.text");
845            } catch (Exception e) {
846                throw new RuntimeException("Setting up the bootstrap content failed", e);
847            }
848        }
849    
850        // ---
851    
852        /**
853         * Low-level method that stores a topic without its "Instantiation" association.
854         * Needed for bootstrapping.
855         */
856        private void _createTopic(TopicModel model) {
857            storageDecorator.storeTopic(model);
858            storageDecorator.storeTopicValue(model.getId(), model.getSimpleValue());
859        }
860    
861        /**
862         * Low-level method that stores an (data type) association without its "Instantiation" association.
863         * Needed for bootstrapping.
864         */
865        private void _associateDataType(String typeUri, String dataTypeUri) {
866            AssociationModel assoc = new AssociationModel("dm4.core.aggregation",
867                new TopicRoleModel(typeUri,     "dm4.core.type"),
868                new TopicRoleModel(dataTypeUri, "dm4.core.default"));
869            storageDecorator.storeAssociation(assoc);
870            storageDecorator.storeAssociationValue(assoc.getId(), assoc.getSimpleValue());
871        }
872    
873        // ---
874    
875        private void bootstrapTypeCache() {
876            typeCache.putTopicType(new AttachedTopicType(new TopicTypeModel("dm4.core.meta_meta_type",
877                "dm4.core.meta_meta_meta_type", "Meta Meta Type", "dm4.core.text"), this));
878        }
879    }