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