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