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 newModel) {
168        long topicId = newModel.getId();
169        try {
170            checkTopicWriteAccess(topicId);
171            //
172            TopicModelImpl model = fetchTopic(topicId);
173            model.update(newModel);
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    Iterable<AssociationModelImpl> _getAssociations(String assocTypeUri, long topic1Id, long topic2Id,
278                                                    String roleTypeUri1, String roleTypeUri2) {
279        logger.fine("assocTypeUri=\"" + assocTypeUri + "\", topic1Id=" + topic1Id + ", topic2Id=" + topic2Id +
280            ", roleTypeUri1=\"" + roleTypeUri1 + "\", roleTypeUri2=\"" + roleTypeUri2 + "\"");
281        try {
282            return filterReadables(fetchAssociations(assocTypeUri, topic1Id, topic2Id, roleTypeUri1, roleTypeUri2));
283        } catch (Exception e) {
284            throw new RuntimeException("Fetching associations between topics " + topic1Id + " and " + topic2Id +
285                " failed (assocTypeUri=\"" + assocTypeUri + "\", roleTypeUri1=\"" + roleTypeUri1 +
286                "\", roleTypeUri2=\"" + roleTypeUri2 + "\")", e);
287        }
288    }
289
290    // ---
291
292    Iterable<Association> getAllAssociations() {
293        return new AssociationIterable(this);
294    }
295
296    long[] getPlayerIds(long assocId) {
297        return fetchPlayerIds(assocId);
298    }
299
300    // ---
301
302    /**
303     * Convenience.
304     */
305    AssociationImpl createAssociation(String typeUri, RoleModel roleModel1, RoleModel roleModel2) {
306        return createAssociation(mf.newAssociationModel(typeUri, roleModel1, roleModel2));
307    }
308
309    /**
310     * Creates a new association in the DB.
311     */
312    AssociationImpl createAssociation(AssociationModelImpl model) {
313        try {
314            em.fireEvent(CoreEvent.PRE_CREATE_ASSOCIATION, model);
315            //
316            model.preCreate();
317            //
318            // 1) store in DB
319            storeAssociation(model);
320            valueStorage.storeValue(model);
321            createAssociationInstantiation(model.getId(), model.getTypeUri());
322            // 2) instantiate
323            AssociationImpl assoc = model.instantiate();
324            //
325            model.postCreate();
326            //
327            em.fireEvent(CoreEvent.POST_CREATE_ASSOCIATION, assoc);
328            return assoc;
329        } catch (Exception e) {
330            throw new RuntimeException("Creating association failed (" + model + ")", e);
331        }
332    }
333
334    // ---
335
336    void updateAssociation(AssociationModelImpl newModel) {
337        long assocId = newModel.getId();
338        try {
339            checkAssociationWriteAccess(assocId);
340            //
341            AssociationModelImpl model = fetchAssociation(assocId);
342            model.update(newModel);
343            //
344            // Note: there is no possible POST_UPDATE_ASSOCIATION_REQUEST event to fire here (compare to updateTopic()).
345            // It would be equivalent to POST_UPDATE_ASSOCIATION. Per request exactly one association is updated.
346            // Its childs are always topics (never associations).
347        } catch (Exception e) {
348            throw new RuntimeException("Updating association " + assocId + " failed", e);
349        }
350    }
351
352    void deleteAssociation(long assocId) {
353        try {
354            checkAssociationWriteAccess(assocId);
355            //
356            fetchAssociation(assocId).delete();
357        } catch (Exception e) {
358            throw new RuntimeException("Deleting association " + assocId + " failed", e);
359        }
360    }
361
362
363
364    // ===
365
366    void createTopicInstantiation(long topicId, String topicTypeUri) {
367        try {
368            AssociationModel assoc = mf.newAssociationModel("dm4.core.instantiation",
369                mf.newTopicRoleModel(topicTypeUri, "dm4.core.type"),
370                mf.newTopicRoleModel(topicId, "dm4.core.instance"));
371            storeAssociation(assoc);   // direct storage calls used here ### explain
372            storeAssociationValue(assoc.getId(), assoc.getSimpleValue());
373            createAssociationInstantiation(assoc.getId(), assoc.getTypeUri());
374        } catch (Exception e) {
375            throw new RuntimeException("Associating topic " + topicId +
376                " with topic type \"" + topicTypeUri + "\" failed", e);
377        }
378    }
379
380    void createAssociationInstantiation(long assocId, String assocTypeUri) {
381        try {
382            AssociationModel assoc = mf.newAssociationModel("dm4.core.instantiation",
383                mf.newTopicRoleModel(assocTypeUri, "dm4.core.type"),
384                mf.newAssociationRoleModel(assocId, "dm4.core.instance"));
385            storeAssociation(assoc);   // direct storage calls used here ### explain
386            storeAssociationValue(assoc.getId(), assoc.getSimpleValue());
387        } catch (Exception e) {
388            throw new RuntimeException("Associating association " + assocId +
389                " with association type \"" + assocTypeUri + "\" failed", e);
390        }
391    }
392
393
394
395    // === Types ===
396
397    TopicType getTopicType(String uri) {
398        TopicTypeModelImpl topicType = _getTopicType(uri);
399        if (!uri.equals("dm4.core.meta_meta_type")) {
400            checkReadAccess(topicType);
401        }
402        return topicType.instantiate();
403    }
404
405    TopicType getTopicTypeImplicitly(long topicId) {
406        checkTopicReadAccess(topicId);
407        return _getTopicType(typeUri(topicId)).instantiate();
408    }
409
410    // ---
411
412    AssociationType getAssociationType(String uri) {
413        return checkReadAccessAndInstantiate(_getAssociationType(uri));
414    }
415
416    AssociationType getAssociationTypeImplicitly(long assocId) {
417        checkAssociationReadAccess(assocId);
418        return _getAssociationType(typeUri(assocId)).instantiate();
419    }
420
421    // ---
422
423    List<TopicType> getAllTopicTypes() {
424        try {
425            List<TopicType> topicTypes = new ArrayList();
426            for (String uri : getTopicTypeUris()) {
427                topicTypes.add(_getTopicType(uri).instantiate());
428            }
429            return topicTypes;
430        } catch (Exception e) {
431            throw new RuntimeException("Fetching all topic types failed", e);
432        }
433    }
434
435    List<AssociationType> getAllAssociationTypes() {
436        try {
437            List<AssociationType> assocTypes = new ArrayList();
438            for (String uri : getAssociationTypeUris()) {
439                assocTypes.add(_getAssociationType(uri).instantiate());
440            }
441            return assocTypes;
442        } catch (Exception e) {
443            throw new RuntimeException("Fetching all association types failed", e);
444        }
445    }
446
447    // ---
448
449    TopicType createTopicType(TopicTypeModelImpl model) {
450        try {
451            // store in DB
452            createTypeTopic(model, URI_PREFIX_TOPIC_TYPE);
453            //
454            TopicType topicType = model.instantiate();
455            em.fireEvent(CoreEvent.INTRODUCE_TOPIC_TYPE, topicType);
456            //
457            return topicType;
458        } catch (Exception e) {
459            throw new RuntimeException("Creating topic type \"" + model.getUri() + "\" failed", e);
460        }
461    }
462
463    AssociationType createAssociationType(AssociationTypeModelImpl model) {
464        try {
465            // store in DB
466            createTypeTopic(model, URI_PREFIX_ASSOCIATION_TYPE);
467            //
468            AssociationType assocType = model.instantiate();
469            em.fireEvent(CoreEvent.INTRODUCE_ASSOCIATION_TYPE, assocType);
470            //
471            return assocType;
472        } catch (Exception e) {
473            throw new RuntimeException("Creating association type \"" + model.getUri() + "\" failed", e);
474        }
475    }
476
477    // ---
478
479    void updateTopicType(TopicTypeModelImpl newModel) {
480        try {
481            // Note: type lookup is by ID. The URI might have changed, the ID does not.
482            // ### FIXME: access control
483            String topicTypeUri = fetchTopic(newModel.getId()).getUri();
484            _getTopicType(topicTypeUri).update(newModel);
485        } catch (Exception e) {
486            throw new RuntimeException("Updating topic type failed (" + newModel + ")", e);
487        }
488    }
489
490    void updateAssociationType(AssociationTypeModelImpl newModel) {
491        try {
492            // Note: type lookup is by ID. The URI might have changed, the ID does not.
493            // ### FIXME: access control
494            String assocTypeUri = fetchTopic(newModel.getId()).getUri();
495            _getAssociationType(assocTypeUri).update(newModel);
496        } catch (Exception e) {
497            throw new RuntimeException("Updating association type failed (" + newModel + ")", e);
498        }
499    }
500
501    // ---
502
503    void deleteTopicType(String topicTypeUri) {
504        try {
505            _getTopicType(topicTypeUri).delete();           // ### TODO: delete view config topics
506        } catch (Exception e) {
507            throw new RuntimeException("Deleting topic type \"" + topicTypeUri + "\" failed", e);
508        }
509    }
510
511    void deleteAssociationType(String assocTypeUri) {
512        try {
513            _getAssociationType(assocTypeUri).delete();     // ### TODO: delete view config topics
514        } catch (Exception e) {
515            throw new RuntimeException("Deleting association type \"" + assocTypeUri + "\" failed", e);
516        }
517    }
518
519    // ---
520
521    Topic createRoleType(TopicModelImpl model) {
522        // check type URI argument
523        String typeUri = model.getTypeUri();
524        if (typeUri == null) {
525            model.setTypeUri("dm4.core.role_type");
526        } else {
527            if (!typeUri.equals("dm4.core.role_type")) {
528                throw new IllegalArgumentException("A role type is supposed to be of type \"dm4.core.role_type\" " +
529                    "(found: \"" + typeUri + "\")");
530            }
531        }
532        //
533        return createTopic(model, URI_PREFIX_ROLE_TYPE);
534    }
535
536    // ---
537
538    TopicTypeModelImpl _getTopicType(String uri) {
539        return typeStorage.getTopicType(uri);
540    }
541
542    AssociationTypeModelImpl _getAssociationType(String uri) {
543        return typeStorage.getAssociationType(uri);
544    }
545
546
547
548    // === Generic Object ===
549
550    DeepaMehtaObject getObject(long id) {
551        return checkReadAccessAndInstantiate(fetchObject(id));
552    }
553
554
555
556    // === Properties ===
557
558    List<Topic> getTopicsByProperty(String propUri, Object propValue) {
559        return checkReadAccessAndInstantiate(fetchTopicsByProperty(propUri, propValue));
560    }
561
562    List<Topic> getTopicsByPropertyRange(String propUri, Number from, Number to) {
563        return checkReadAccessAndInstantiate(fetchTopicsByPropertyRange(propUri, from, to));
564    }
565
566    List<Association> getAssociationsByProperty(String propUri, Object propValue) {
567        return checkReadAccessAndInstantiate(fetchAssociationsByProperty(propUri, propValue));
568    }
569
570    List<Association> getAssociationsByPropertyRange(String propUri, Number from, Number to) {
571        return checkReadAccessAndInstantiate(fetchAssociationsByPropertyRange(propUri, from, to));
572    }
573
574    // ------------------------------------------------------------------------------------------------- Private Methods
575
576
577
578    // === Access Control / Instantiation ===
579
580    // These methods 1) instantiate objects from models, and 2) check the READ permission for each model.
581    // Call these methods when passing objects fetched from the DB to the user.
582    // ### TODO: make these private?
583
584    <O> O checkReadAccessAndInstantiate(DeepaMehtaObjectModelImpl model) {
585        checkReadAccess(model);
586        return (O) model.instantiate();
587    }
588
589    <O> List<O> checkReadAccessAndInstantiate(Iterable<? extends DeepaMehtaObjectModelImpl> models) {
590        return instantiate(filterReadables(models));
591    }
592
593    // ---
594
595    private <M extends DeepaMehtaObjectModelImpl> Iterable<M> filterReadables(Iterable<M> models) {
596        Iterator<? extends DeepaMehtaObjectModelImpl> i = models.iterator();
597        while (i.hasNext()) {
598            if (!hasReadAccess(i.next())) {
599                i.remove();
600            }
601        }
602        return models;
603    }
604
605    boolean hasReadAccess(DeepaMehtaObjectModelImpl model) {
606        try {
607            checkReadAccess(model);
608            return true;
609        } catch (AccessControlException e) {
610            return false;
611        }
612    }
613
614    /**
615     * @throws  AccessControlException
616     */
617    private void checkReadAccess(DeepaMehtaObjectModelImpl model) {
618        em.fireEvent(model.getReadAccessEvent(), model.getId());
619    }
620
621    // ---
622
623    private void checkTopicReadAccess(long topicId) {
624        em.fireEvent(CoreEvent.CHECK_TOPIC_READ_ACCESS, topicId);
625    }
626
627    private void checkAssociationReadAccess(long assocId) {
628        em.fireEvent(CoreEvent.CHECK_ASSOCIATION_READ_ACCESS, assocId);
629    }
630
631    // ---
632
633    private void checkTopicWriteAccess(long topicId) {
634        em.fireEvent(CoreEvent.CHECK_TOPIC_WRITE_ACCESS, topicId);
635    }
636
637    private void checkAssociationWriteAccess(long assocId) {
638        em.fireEvent(CoreEvent.CHECK_ASSOCIATION_WRITE_ACCESS, assocId);
639    }
640
641
642
643    // === Instantiation ===
644
645    <O> List<O> instantiate(Iterable<? extends DeepaMehtaObjectModelImpl> models) {
646        List<O> objects = new ArrayList();
647        for (DeepaMehtaObjectModelImpl model : models) {
648            objects.add((O) model.instantiate());
649        }
650        return objects;
651    }
652
653
654
655    // ===
656
657    private List<String> getTopicTypeUris() {
658        try {
659            List<String> topicTypeUris = new ArrayList();
660            // add meta types
661            topicTypeUris.add("dm4.core.topic_type");
662            topicTypeUris.add("dm4.core.assoc_type");
663            topicTypeUris.add("dm4.core.meta_type");
664            topicTypeUris.add("dm4.core.meta_meta_type");
665            // add regular types
666            for (TopicModel topicType : filterReadables(fetchTopics("type_uri", new SimpleValue(
667                                                                    "dm4.core.topic_type")))) {
668                topicTypeUris.add(topicType.getUri());
669            }
670            return topicTypeUris;
671        } catch (Exception e) {
672            throw new RuntimeException("Fetching list of topic type URIs failed", e);
673        }
674    }
675
676    private List<String> getAssociationTypeUris() {
677        try {
678            List<String> assocTypeUris = new ArrayList();
679            for (TopicModel assocType : filterReadables(fetchTopics("type_uri", new SimpleValue(
680                                                                    "dm4.core.assoc_type")))) {
681                assocTypeUris.add(assocType.getUri());
682            }
683            return assocTypeUris;
684        } catch (Exception e) {
685            throw new RuntimeException("Fetching list of association type URIs failed", e);
686        }
687    }
688
689    // ---
690
691    private void createTypeTopic(TypeModelImpl model, String uriPrefix) {
692        // Note: the type topic is instantiated explicitly on a `TopicModel` (which is freshly created from the
693        // `TypeModel`). Creating the type topic from the `TypeModel` directly would fail as topic creation implies
694        // topic instantiation, and due to the polymorphic `instantiate()` method a `Type` object would be instantiated
695        // (instead a `Topic` object). But instantiating a type newly implies per-user type projection, that is removing
696        // the assoc defs not readable by the current user. But at the time the type topic is stored in the DB its assoc
697        // defs are not yet stored, and the readability check would fail.
698        TopicModelImpl typeTopic = mf.newTopicModel(model);
699        createTopic(typeTopic, uriPrefix);      // create generic topic
700        //
701        model.id = typeTopic.id;
702        model.uri = typeTopic.uri;
703        //
704        typeStorage.storeType(model);           // store type-specific parts
705    }
706
707    private String typeUri(long objectId) {
708        return (String) fetchProperty(objectId, "type_uri");
709    }
710
711    private void bootstrapTypeCache() {
712        TopicTypeModelImpl metaMetaType = mf.newTopicTypeModel("dm4.core.meta_meta_type", "Meta Meta Type",
713            "dm4.core.text");
714        metaMetaType.setTypeUri("dm4.core.meta_meta_meta_type");
715        typeStorage.putInTypeCache(metaMetaType);
716    }
717}