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