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