001package de.deepamehta.core.impl;
002
003import de.deepamehta.core.Association;
004import de.deepamehta.core.AssociationType;
005import de.deepamehta.core.DeepaMehtaObject;
006import de.deepamehta.core.RelatedAssociation;
007import de.deepamehta.core.RelatedTopic;
008import de.deepamehta.core.Topic;
009import de.deepamehta.core.TopicType;
010import de.deepamehta.core.model.AssociationModel;
011import de.deepamehta.core.model.AssociationTypeModel;
012import de.deepamehta.core.model.DeepaMehtaObjectModel;
013import de.deepamehta.core.model.RelatedAssociationModel;
014import de.deepamehta.core.model.RelatedTopicModel;
015import de.deepamehta.core.model.RoleModel;
016import de.deepamehta.core.model.SimpleValue;
017import de.deepamehta.core.model.TopicModel;
018import de.deepamehta.core.model.TopicTypeModel;
019import de.deepamehta.core.service.accesscontrol.AccessControlException;
020import de.deepamehta.core.storage.spi.DeepaMehtaStorage;
021
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Iterator;
025import java.util.List;
026import java.util.logging.Logger;
027
028
029
030public class PersistenceLayer extends StorageDecorator {
031
032    // ------------------------------------------------------------------------------------------------------- Constants
033
034    private static final String URI_PREFIX_TOPIC_TYPE       = "domain.project.topic_type_";
035    private static final String URI_PREFIX_ASSOCIATION_TYPE = "domain.project.assoc_type_";
036    private static final String URI_PREFIX_ROLE_TYPE        = "domain.project.role_type_";
037
038    // ---------------------------------------------------------------------------------------------- Instance Variables
039
040    TypeStorage typeStorage;
041    ValueStorage valueStorage;
042
043    EventManager em;
044    ModelFactoryImpl mf;
045
046    private final Logger logger = Logger.getLogger(getClass().getName());
047
048    // ---------------------------------------------------------------------------------------------------- Constructors
049
050    public PersistenceLayer(DeepaMehtaStorage storage) {
051        super(storage);
052        // Note: mf must be initialzed before the type storage is instantiated
053        this.em = new EventManager();
054        this.mf = (ModelFactoryImpl) storage.getModelFactory();
055        //
056        this.typeStorage = new TypeStorage(this);
057        this.valueStorage = new ValueStorage(this);
058        //
059        // Note: this is a constructor side effect. This is a cyclic dependency. This is nasty.
060        // ### TODO: explain why we do it.
061        mf.pl = this;
062        //
063        bootstrapTypeCache();
064    }
065
066    // ----------------------------------------------------------------------------------------- Package Private Methods
067
068
069
070    // === Topics ===
071
072    Topic getTopic(long topicId) {
073        try {
074            return checkReadAccessAndInstantiate(fetchTopic(topicId));
075        } catch (Exception e) {
076            throw new RuntimeException("Fetching topic " + topicId + " failed", e);
077        }
078    }
079
080    TopicImpl getTopicByUri(String uri) {
081        return getTopicByValue("uri", new SimpleValue(uri));
082    }
083
084    TopicImpl getTopicByValue(String key, SimpleValue value) {
085        try {
086            TopicModelImpl topic = fetchTopic(key, value);
087            return topic != null ? this.<TopicImpl>checkReadAccessAndInstantiate(topic) : null;
088            // Note: inside a conditional operator the type witness is required (at least in Java 6)
089        } catch (Exception e) {
090            throw new RuntimeException("Fetching topic failed (key=\"" + key + "\", value=\"" + value + "\")", e);
091        }
092    }
093
094    List<Topic> getTopicsByValue(String key, SimpleValue value) {
095        try {
096            return checkReadAccessAndInstantiate(fetchTopics(key, value));
097        } catch (Exception e) {
098            throw new RuntimeException("Fetching topics failed (key=\"" + key + "\", value=\"" + value + "\")", e);
099        }
100    }
101
102    List<Topic> getTopicsByType(String topicTypeUri) {
103        try {
104            return checkReadAccessAndInstantiate(_getTopicType(topicTypeUri).getAllInstances());
105        } catch (Exception e) {
106            throw new RuntimeException("Fetching topics by type failed (topicTypeUri=\"" + topicTypeUri + "\")", e);
107        }
108    }
109
110    List<Topic> searchTopics(String searchTerm, String fieldUri) {
111        try {
112            return checkReadAccessAndInstantiate(queryTopics(fieldUri, new SimpleValue(searchTerm)));
113        } catch (Exception e) {
114            throw new RuntimeException("Searching topics failed (searchTerm=\"" + searchTerm + "\", fieldUri=\"" +
115                fieldUri + "\")", e);
116        }
117    }
118
119    Iterable<Topic> getAllTopics() {
120        return new TopicIterable(this);
121    }
122
123    // ---
124
125    /**
126     * Convenience.
127     */
128    TopicImpl createTopic(TopicModelImpl model) {
129        return createTopic(model, null);    // uriPrefix=null
130    }
131
132    /**
133     * Creates a new topic in the DB.
134     */
135    TopicImpl createTopic(TopicModelImpl model, String uriPrefix) {
136        try {
137            em.fireEvent(CoreEvent.PRE_CREATE_TOPIC, model);
138            //
139            model.preCreate();
140            //
141            // 1) store in DB
142            storeTopic(model);
143            valueStorage.storeValue(model);
144            createTopicInstantiation(model.getId(), model.getTypeUri());
145            // 2) set default URI
146            // If no URI is given the topic gets a default URI based on its ID, if requested.
147            // Note: this must be done *after* the topic is stored. The ID is not known before.
148            // Note: in case no URI was given: once stored a topic's URI is empty (not null).
149            if (uriPrefix != null && model.getUri().equals("")) {
150                model.updateUri(uriPrefix + model.getId());     // update memory + DB
151            }
152            // 3) instantiate
153            TopicImpl topic = model.instantiate();
154            //
155            model.postCreate();
156            //
157            em.fireEvent(CoreEvent.POST_CREATE_TOPIC, topic);
158            return topic;
159        } catch (Exception e) {
160            throw new RuntimeException("Creating topic " + model.getId() + " failed (typeUri=\"" + model.getTypeUri() +
161                "\")", e);
162        }
163    }
164
165    // ---
166
167    void updateTopic(TopicModelImpl updateModel) {
168        long topicId = updateModel.getId();
169        try {
170            checkTopicWriteAccess(topicId);
171            //
172            TopicModelImpl model = fetchTopic(topicId);
173            model.update(updateModel);
174            //
175            // Note: POST_UPDATE_TOPIC_REQUEST is fired only once per update request.
176            // On the other hand TopicModel's update() method is called multiple times while updating the child topics
177            // (see ChildTopicsModelImpl).
178            em.fireEvent(CoreEvent.POST_UPDATE_TOPIC_REQUEST, model.instantiate());
179        } catch (Exception e) {
180            throw new RuntimeException("Updating topic " + topicId + " failed", e);
181        }
182    }
183
184    void deleteTopic(long topicId) {
185        try {
186            checkTopicWriteAccess(topicId);
187            //
188            fetchTopic(topicId).delete();
189        } catch (Exception e) {
190            throw new RuntimeException("Deleting topic " + topicId + " failed", e);
191        }
192    }
193
194
195
196    // === Associations ===
197
198    Association getAssociation(long assocId) {
199        try {
200            return checkReadAccessAndInstantiate(fetchAssociation(assocId));
201        } catch (Exception e) {
202            throw new RuntimeException("Fetching association " + assocId + " failed", e);
203        }
204    }
205
206    Association getAssociationByValue(String key, SimpleValue value) {
207        try {
208            AssociationModelImpl assoc = fetchAssociation(key, value);
209            return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null;
210            // Note: inside a conditional operator the type witness is required (at least in Java 6)
211        } catch (Exception e) {
212            throw new RuntimeException("Fetching association failed (key=\"" + key + "\", value=\"" + value + "\")", e);
213        }
214    }
215
216    List<Association> getAssociationsByValue(String key, SimpleValue value) {
217        try {
218            return checkReadAccessAndInstantiate(fetchAssociations(key, value));
219        } catch (Exception e) {
220            throw new RuntimeException("Fetching associationss failed (key=\"" + key + "\", value=\"" + value + "\")",
221                e);
222        }
223    }
224
225    Association getAssociation(String assocTypeUri, long topic1Id, long topic2Id, String roleTypeUri1,
226                                                                                  String roleTypeUri2) {
227        String info = "assocTypeUri=\"" + assocTypeUri + "\", topic1Id=" + topic1Id + ", topic2Id=" + topic2Id +
228            ", roleTypeUri1=\"" + roleTypeUri1 + "\", roleTypeUri2=\"" + roleTypeUri2 + "\"";
229        try {
230            AssociationModelImpl assoc = fetchAssociation(assocTypeUri, topic1Id, topic2Id, roleTypeUri1, roleTypeUri2);
231            return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null;
232            // Note: inside a conditional operator the type witness is required (at least in Java 6)
233        } catch (Exception e) {
234            throw new RuntimeException("Fetching association failed (" + info + ")", e);
235        }
236    }
237
238    Association getAssociationBetweenTopicAndAssociation(String assocTypeUri, long topicId, long assocId,
239                                                         String topicRoleTypeUri, String assocRoleTypeUri) {
240        String info = "assocTypeUri=\"" + assocTypeUri + "\", topicId=" + topicId + ", assocId=" + assocId +
241            ", topicRoleTypeUri=\"" + topicRoleTypeUri + "\", assocRoleTypeUri=\"" + assocRoleTypeUri + "\"";
242        logger.info(info);
243        try {
244            AssociationModelImpl assoc = fetchAssociationBetweenTopicAndAssociation(assocTypeUri, topicId, assocId,
245                topicRoleTypeUri, assocRoleTypeUri);
246            return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null;
247            // Note: inside a conditional operator the type witness is required (at least in Java 6)
248        } catch (Exception e) {
249            throw new RuntimeException("Fetching association failed (" + info + ")", e);
250        }
251    }
252
253    // ---
254
255    List<Association> getAssociationsByType(String assocTypeUri) {
256        try {
257            return checkReadAccessAndInstantiate(_getAssociationType(assocTypeUri).getAllInstances());
258        } catch (Exception e) {
259            throw new RuntimeException("Fetching associations by type failed (assocTypeUri=\"" + assocTypeUri + "\")",
260                e);
261        }
262    }
263
264    List<Association> getAssociations(long topic1Id, long topic2Id) {
265        return getAssociations(null, topic1Id, topic2Id);   // assocTypeUri=null
266    }
267
268    List<Association> getAssociations(String assocTypeUri, long topic1Id, long topic2Id) {
269        return getAssociations(assocTypeUri, topic1Id, topic2Id, null, null);   // roleTypeUri1=null, roleTypeUri2=null
270    }
271
272    List<Association> getAssociations(String assocTypeUri, long topic1Id, long topic2Id, String roleTypeUri1,
273                                                                                         String roleTypeUri2) {
274        return instantiate(_getAssociations(assocTypeUri, topic1Id, topic2Id, roleTypeUri1, roleTypeUri2));
275    }
276
277    /**
278     * Fetches from DB and filters READables. No instantiation.
279     */
280    Iterable<AssociationModelImpl> _getAssociations(String assocTypeUri, long topic1Id, long topic2Id,
281                                                    String roleTypeUri1, String roleTypeUri2) {
282        logger.fine("assocTypeUri=\"" + assocTypeUri + "\", topic1Id=" + topic1Id + ", topic2Id=" + topic2Id +
283            ", roleTypeUri1=\"" + roleTypeUri1 + "\", roleTypeUri2=\"" + roleTypeUri2 + "\"");
284        try {
285            return filterReadables(fetchAssociations(assocTypeUri, topic1Id, topic2Id, roleTypeUri1, roleTypeUri2));
286        } catch (Exception e) {
287            throw new RuntimeException("Fetching associations between topics " + topic1Id + " and " + topic2Id +
288                " failed (assocTypeUri=\"" + assocTypeUri + "\", roleTypeUri1=\"" + roleTypeUri1 +
289                "\", roleTypeUri2=\"" + roleTypeUri2 + "\")", e);
290        }
291    }
292
293    // ---
294
295    Iterable<Association> getAllAssociations() {
296        return new AssociationIterable(this);
297    }
298
299    long[] getPlayerIds(long assocId) {
300        return fetchPlayerIds(assocId);
301    }
302
303    // ---
304
305    /**
306     * Convenience.
307     */
308    AssociationImpl createAssociation(String typeUri, RoleModel roleModel1, RoleModel roleModel2) {
309        return createAssociation(mf.newAssociationModel(typeUri, roleModel1, roleModel2));
310    }
311
312    /**
313     * Creates a new association in the DB.
314     */
315    AssociationImpl createAssociation(AssociationModelImpl model) {
316        try {
317            em.fireEvent(CoreEvent.PRE_CREATE_ASSOCIATION, model);
318            //
319            model.preCreate();
320            //
321            // 1) store in DB
322            storeAssociation(model);
323            valueStorage.storeValue(model);
324            createAssociationInstantiation(model.getId(), model.getTypeUri());
325            // 2) instantiate
326            AssociationImpl assoc = model.instantiate();
327            //
328            model.postCreate();
329            //
330            em.fireEvent(CoreEvent.POST_CREATE_ASSOCIATION, assoc);
331            return assoc;
332        } catch (Exception e) {
333            throw new RuntimeException("Creating association failed (" + model + ")", e);
334        }
335    }
336
337    // ---
338
339    void updateAssociation(AssociationModelImpl updateModel) {
340        long assocId = updateModel.getId();
341        try {
342            checkAssociationWriteAccess(assocId);
343            //
344            AssociationModelImpl model = fetchAssociation(assocId);
345            model.update(updateModel);
346            //
347            // Note: there is no possible POST_UPDATE_ASSOCIATION_REQUEST event to fire here (compare to updateTopic()).
348            // It would be equivalent to POST_UPDATE_ASSOCIATION. Per request exactly one association is updated.
349            // Its childs are always topics (never associations).
350        } catch (Exception e) {
351            throw new RuntimeException("Updating association " + assocId + " failed", e);
352        }
353    }
354
355    void deleteAssociation(long assocId) {
356        try {
357            checkAssociationWriteAccess(assocId);
358            //
359            fetchAssociation(assocId).delete();
360        } catch (Exception e) {
361            throw new RuntimeException("Deleting association " + assocId + " failed", e);
362        }
363    }
364
365
366
367    // ===
368
369    void createTopicInstantiation(long topicId, String topicTypeUri) {
370        try {
371            AssociationModel assoc = mf.newAssociationModel("dm4.core.instantiation",
372                mf.newTopicRoleModel(topicTypeUri, "dm4.core.type"),
373                mf.newTopicRoleModel(topicId, "dm4.core.instance"));
374            storeAssociation(assoc);   // direct storage calls used here ### explain
375            storeAssociationValue(assoc.getId(), assoc.getSimpleValue());
376            createAssociationInstantiation(assoc.getId(), assoc.getTypeUri());
377        } catch (Exception e) {
378            throw new RuntimeException("Associating topic " + topicId +
379                " with topic type \"" + topicTypeUri + "\" failed", e);
380        }
381    }
382
383    void createAssociationInstantiation(long assocId, String assocTypeUri) {
384        try {
385            AssociationModel assoc = mf.newAssociationModel("dm4.core.instantiation",
386                mf.newTopicRoleModel(assocTypeUri, "dm4.core.type"),
387                mf.newAssociationRoleModel(assocId, "dm4.core.instance"));
388            storeAssociation(assoc);   // direct storage calls used here ### explain
389            storeAssociationValue(assoc.getId(), assoc.getSimpleValue());
390        } catch (Exception e) {
391            throw new RuntimeException("Associating association " + assocId +
392                " with association type \"" + assocTypeUri + "\" failed", e);
393        }
394    }
395
396
397
398    // === Types ===
399
400    TopicTypeImpl getTopicType(String uri) {
401        return checkReadAccessAndInstantiate(_getTopicType(uri));
402    }
403
404    TopicTypeImpl getTopicTypeImplicitly(long topicId) {
405        checkTopicReadAccess(topicId);
406        return _getTopicType(typeUri(topicId)).instantiate();
407    }
408
409    // ---
410
411    AssociationTypeImpl getAssociationType(String uri) {
412        return checkReadAccessAndInstantiate(_getAssociationType(uri));
413    }
414
415    AssociationTypeImpl getAssociationTypeImplicitly(long assocId) {
416        checkAssociationReadAccess(assocId);
417        return _getAssociationType(typeUri(assocId)).instantiate();
418    }
419
420    // ---
421
422    List<TopicType> getAllTopicTypes() {
423        try {
424            List<TopicType> topicTypes = new ArrayList();
425            for (String uri : getTopicTypeUris()) {
426                topicTypes.add(_getTopicType(uri).instantiate());
427            }
428            return topicTypes;
429        } catch (Exception e) {
430            throw new RuntimeException("Fetching all topic types failed", e);
431        }
432    }
433
434    List<AssociationType> getAllAssociationTypes() {
435        try {
436            List<AssociationType> assocTypes = new ArrayList();
437            for (String uri : getAssociationTypeUris()) {
438                assocTypes.add(_getAssociationType(uri).instantiate());
439            }
440            return assocTypes;
441        } catch (Exception e) {
442            throw new RuntimeException("Fetching all association types failed", e);
443        }
444    }
445
446    // ---
447
448    TopicTypeImpl createTopicType(TopicTypeModelImpl model) {
449        try {
450            em.fireEvent(CoreEvent.PRE_CREATE_TOPIC_TYPE, model);
451            //
452            // store in DB
453            createType(model, URI_PREFIX_TOPIC_TYPE);
454            //
455            TopicTypeImpl topicType = model.instantiate();
456            em.fireEvent(CoreEvent.INTRODUCE_TOPIC_TYPE, topicType);
457            //
458            return topicType;
459        } catch (Exception e) {
460            throw new RuntimeException("Creating topic type \"" + model.getUri() + "\" failed", e);
461        }
462    }
463
464    AssociationTypeImpl createAssociationType(AssociationTypeModelImpl model) {
465        try {
466            em.fireEvent(CoreEvent.PRE_CREATE_ASSOCIATION_TYPE, model);
467            //
468            // store in DB
469            createType(model, URI_PREFIX_ASSOCIATION_TYPE);
470            //
471            AssociationTypeImpl assocType = model.instantiate();
472            em.fireEvent(CoreEvent.INTRODUCE_ASSOCIATION_TYPE, assocType);
473            //
474            return assocType;
475        } catch (Exception e) {
476            throw new RuntimeException("Creating association type \"" + model.getUri() + "\" failed", e);
477        }
478    }
479
480    // ---
481
482    void updateTopicType(TopicTypeModelImpl updateModel) {
483        try {
484            // Note: type lookup is by ID. The URI might have changed, the ID does not.
485            // ### FIXME: access control
486            String topicTypeUri = fetchTopic(updateModel.getId()).getUri();
487            _getTopicType(topicTypeUri).update(updateModel);
488        } catch (Exception e) {
489            throw new RuntimeException("Updating topic type failed (" + updateModel + ")", e);
490        }
491    }
492
493    void updateAssociationType(AssociationTypeModelImpl updateModel) {
494        try {
495            // Note: type lookup is by ID. The URI might have changed, the ID does not.
496            // ### FIXME: access control
497            String assocTypeUri = fetchTopic(updateModel.getId()).getUri();
498            _getAssociationType(assocTypeUri).update(updateModel);
499        } catch (Exception e) {
500            throw new RuntimeException("Updating association type failed (" + updateModel + ")", e);
501        }
502    }
503
504    // ---
505
506    void deleteTopicType(String topicTypeUri) {
507        try {
508            _getTopicType(topicTypeUri).delete();           // ### TODO: delete view config topics
509        } catch (Exception e) {
510            throw new RuntimeException("Deleting topic type \"" + topicTypeUri + "\" failed", e);
511        }
512    }
513
514    void deleteAssociationType(String assocTypeUri) {
515        try {
516            _getAssociationType(assocTypeUri).delete();     // ### TODO: delete view config topics
517        } catch (Exception e) {
518            throw new RuntimeException("Deleting association type \"" + assocTypeUri + "\" failed", e);
519        }
520    }
521
522    // ---
523
524    Topic createRoleType(TopicModelImpl model) {
525        // check type URI argument
526        String typeUri = model.getTypeUri();
527        if (typeUri == null) {
528            model.setTypeUri("dm4.core.role_type");
529        } else {
530            if (!typeUri.equals("dm4.core.role_type")) {
531                throw new IllegalArgumentException("A role type is supposed to be of type \"dm4.core.role_type\" " +
532                    "(found: \"" + typeUri + "\")");
533            }
534        }
535        //
536        return createTopic(model, URI_PREFIX_ROLE_TYPE);
537    }
538
539    // ---
540
541    TopicTypeModelImpl _getTopicType(String uri) {
542        return typeStorage.getTopicType(uri);
543    }
544
545    AssociationTypeModelImpl _getAssociationType(String uri) {
546        return typeStorage.getAssociationType(uri);
547    }
548
549
550
551    // === Generic Object ===
552
553    DeepaMehtaObject getObject(long id) {
554        return checkReadAccessAndInstantiate(fetchObject(id));
555    }
556
557
558
559    // === Properties ===
560
561    List<Topic> getTopicsByProperty(String propUri, Object propValue) {
562        return checkReadAccessAndInstantiate(fetchTopicsByProperty(propUri, propValue));
563    }
564
565    List<Topic> getTopicsByPropertyRange(String propUri, Number from, Number to) {
566        return checkReadAccessAndInstantiate(fetchTopicsByPropertyRange(propUri, from, to));
567    }
568
569    List<Association> getAssociationsByProperty(String propUri, Object propValue) {
570        return checkReadAccessAndInstantiate(fetchAssociationsByProperty(propUri, propValue));
571    }
572
573    List<Association> getAssociationsByPropertyRange(String propUri, Number from, Number to) {
574        return checkReadAccessAndInstantiate(fetchAssociationsByPropertyRange(propUri, from, to));
575    }
576
577    // ------------------------------------------------------------------------------------------------- Private Methods
578
579
580
581    // === Access Control / Instantiation ===
582
583    // These methods 1) instantiate objects from models, and 2) check the READ permission for each model.
584    // Call these methods when passing objects fetched from the DB to the user.
585    // ### TODO: make these private?
586
587    <O> O checkReadAccessAndInstantiate(DeepaMehtaObjectModelImpl model) {
588        model.checkReadAccess();
589        return (O) model.instantiate();
590    }
591
592    <O> List<O> checkReadAccessAndInstantiate(Iterable<? extends DeepaMehtaObjectModelImpl> models) {
593        return instantiate(filterReadables(models));
594    }
595
596    // ---
597
598    private <M extends DeepaMehtaObjectModelImpl> Iterable<M> filterReadables(Iterable<M> models) {
599        Iterator<? extends DeepaMehtaObjectModelImpl> i = models.iterator();
600        while (i.hasNext()) {
601            if (!hasReadAccess(i.next())) {
602                i.remove();
603            }
604        }
605        return models;
606    }
607
608    boolean hasReadAccess(DeepaMehtaObjectModelImpl model) {
609        try {
610            model.checkReadAccess();
611            return true;
612        } catch (AccessControlException e) {
613            return false;
614        }
615    }
616
617    // ---
618
619    void checkTopicReadAccess(long topicId) {
620        em.fireEvent(CoreEvent.CHECK_TOPIC_READ_ACCESS, topicId);
621    }
622
623    void checkAssociationReadAccess(long assocId) {
624        em.fireEvent(CoreEvent.CHECK_ASSOCIATION_READ_ACCESS, assocId);
625    }
626
627    // ---
628
629    private void checkTopicWriteAccess(long topicId) {
630        em.fireEvent(CoreEvent.CHECK_TOPIC_WRITE_ACCESS, topicId);
631    }
632
633    private void checkAssociationWriteAccess(long assocId) {
634        em.fireEvent(CoreEvent.CHECK_ASSOCIATION_WRITE_ACCESS, assocId);
635    }
636
637
638
639    // === Instantiation ===
640
641    <O> List<O> instantiate(Iterable<? extends DeepaMehtaObjectModelImpl> models) {
642        List<O> objects = new ArrayList();
643        for (DeepaMehtaObjectModelImpl model : models) {
644            objects.add((O) model.instantiate());
645        }
646        return objects;
647    }
648
649
650
651    // ===
652
653    private List<String> getTopicTypeUris() {
654        try {
655            List<String> topicTypeUris = new ArrayList();
656            // add meta types
657            topicTypeUris.add("dm4.core.topic_type");
658            topicTypeUris.add("dm4.core.assoc_type");
659            topicTypeUris.add("dm4.core.meta_type");
660            // add regular types
661            for (TopicModel topicType : filterReadables(fetchTopics("type_uri", new SimpleValue(
662                                                                    "dm4.core.topic_type")))) {
663                topicTypeUris.add(topicType.getUri());
664            }
665            return topicTypeUris;
666        } catch (Exception e) {
667            throw new RuntimeException("Fetching list of topic type URIs failed", e);
668        }
669    }
670
671    private List<String> getAssociationTypeUris() {
672        try {
673            List<String> assocTypeUris = new ArrayList();
674            for (TopicModel assocType : filterReadables(fetchTopics("type_uri", new SimpleValue(
675                                                                    "dm4.core.assoc_type")))) {
676                assocTypeUris.add(assocType.getUri());
677            }
678            return assocTypeUris;
679        } catch (Exception e) {
680            throw new RuntimeException("Fetching list of association type URIs failed", e);
681        }
682    }
683
684    // ---
685
686    private void createType(TypeModelImpl model, String uriPrefix) {
687        // Note: the type topic is instantiated explicitly on a `TopicModel` (which is freshly created from the
688        // `TypeModel`). Creating the type topic from the `TypeModel` directly would fail as topic creation implies
689        // topic instantiation, and due to the polymorphic `instantiate()` method a `Type` object would be instantiated
690        // (instead a `Topic` object). But instantiating a type implies per-user type projection, that is removing the
691        // assoc defs not readable by the current user. But at the time the type topic is stored in the DB its assoc
692        // defs are not yet stored, and the readability check would fail.
693        TopicModelImpl typeTopic = mf.newTopicModel(model);
694        createTopic(typeTopic, uriPrefix);      // create generic topic
695        //
696        model.id = typeTopic.id;
697        model.uri = typeTopic.uri;
698        //
699        typeStorage.storeType(model);           // store type-specific parts
700    }
701
702    private String typeUri(long objectId) {
703        return (String) fetchProperty(objectId, "type_uri");
704    }
705
706    private void bootstrapTypeCache() {
707        TopicTypeModelImpl metaMetaType = mf.newTopicTypeModel("dm4.core.meta_meta_type", "Meta Meta Type",
708            "dm4.core.text");
709        metaMetaType.setTypeUri("dm4.core.meta_meta_meta_type");
710        typeStorage.putInTypeCache(metaMetaType);
711    }
712}