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.Topic;
007import de.deepamehta.core.TopicType;
008import de.deepamehta.core.model.AssociationModel;
009import de.deepamehta.core.model.AssociationTypeModel;
010import de.deepamehta.core.model.DeepaMehtaObjectModel;
011import de.deepamehta.core.model.RelatedAssociationModel;
012import de.deepamehta.core.model.RelatedTopicModel;
013import de.deepamehta.core.model.RoleModel;
014import de.deepamehta.core.model.SimpleValue;
015import de.deepamehta.core.model.TopicModel;
016import de.deepamehta.core.model.TopicTypeModel;
017import de.deepamehta.core.service.accesscontrol.AccessControlException;
018import de.deepamehta.core.storage.spi.DeepaMehtaStorage;
019
020import java.util.ArrayList;
021import java.util.Iterator;
022import java.util.List;
023import java.util.logging.Logger;
024
025
026
027/**
028 * Adds access control on top of vendor specific storage.
029 */
030public final 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        try {
169            TopicModelImpl model = fetchTopic(updateModel.getId());
170            updateTopic(model, updateModel);
171            //
172            // Note: POST_UPDATE_TOPIC_REQUEST is fired only once per update request.
173            // On the other hand TopicModel's update() method is called multiple times while updating the child topics
174            // (see ChildTopicsModelImpl).
175            em.fireEvent(CoreEvent.POST_UPDATE_TOPIC_REQUEST, model.instantiate());
176        } catch (Exception e) {
177            throw new RuntimeException("Updating topic " + updateModel.getId() + " failed", e);
178        }
179    }
180
181    void updateTopic(TopicModelImpl topic, TopicModelImpl updateModel) {
182        try {
183            topic.checkWriteAccess();
184            topic.update(updateModel);
185        } catch (Exception e) {
186            throw new RuntimeException("Updating topic " + topic.getId() + " failed", e);
187        }
188    }
189
190    // ---
191
192    void deleteTopic(long topicId) {
193        deleteTopic(fetchTopic(topicId));
194    }
195
196    void deleteTopic(TopicModelImpl topic) {
197        try {
198            topic.checkWriteAccess();
199            topic.delete();
200        } catch (Exception e) {
201            throw new RuntimeException("Deleting topic " + topic.getId() + " failed", e);
202        }
203    }
204
205
206
207    // === Associations ===
208
209    Association getAssociation(long assocId) {
210        try {
211            return checkReadAccessAndInstantiate(fetchAssociation(assocId));
212        } catch (Exception e) {
213            throw new RuntimeException("Fetching association " + assocId + " failed", e);
214        }
215    }
216
217    Association getAssociationByValue(String key, SimpleValue value) {
218        try {
219            AssociationModelImpl assoc = fetchAssociation(key, value);
220            return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null;
221            // Note: inside a conditional operator the type witness is required (at least in Java 6)
222        } catch (Exception e) {
223            throw new RuntimeException("Fetching association failed (key=\"" + key + "\", value=\"" + value + "\")", e);
224        }
225    }
226
227    List<Association> getAssociationsByValue(String key, SimpleValue value) {
228        try {
229            return checkReadAccessAndInstantiate(fetchAssociations(key, value));
230        } catch (Exception e) {
231            throw new RuntimeException("Fetching associationss failed (key=\"" + key + "\", value=\"" + value + "\")",
232                e);
233        }
234    }
235
236    Association getAssociation(String assocTypeUri, long topic1Id, long topic2Id, String roleTypeUri1,
237                                                                                  String roleTypeUri2) {
238        String info = "assocTypeUri=\"" + assocTypeUri + "\", topic1Id=" + topic1Id + ", topic2Id=" + topic2Id +
239            ", roleTypeUri1=\"" + roleTypeUri1 + "\", roleTypeUri2=\"" + roleTypeUri2 + "\"";
240        try {
241            AssociationModelImpl assoc = fetchAssociation(assocTypeUri, topic1Id, topic2Id, roleTypeUri1, roleTypeUri2);
242            return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null;
243            // Note: inside a conditional operator the type witness is required (at least in Java 6)
244        } catch (Exception e) {
245            throw new RuntimeException("Fetching association failed (" + info + ")", e);
246        }
247    }
248
249    Association getAssociationBetweenTopicAndAssociation(String assocTypeUri, long topicId, long assocId,
250                                                         String topicRoleTypeUri, String assocRoleTypeUri) {
251        String info = "assocTypeUri=\"" + assocTypeUri + "\", topicId=" + topicId + ", assocId=" + assocId +
252            ", topicRoleTypeUri=\"" + topicRoleTypeUri + "\", assocRoleTypeUri=\"" + assocRoleTypeUri + "\"";
253        logger.info(info);
254        try {
255            AssociationModelImpl assoc = fetchAssociationBetweenTopicAndAssociation(assocTypeUri, topicId, assocId,
256                topicRoleTypeUri, assocRoleTypeUri);
257            return assoc != null ? this.<Association>checkReadAccessAndInstantiate(assoc) : null;
258            // Note: inside a conditional operator the type witness is required (at least in Java 6)
259        } catch (Exception e) {
260            throw new RuntimeException("Fetching association failed (" + info + ")", e);
261        }
262    }
263
264    // ---
265
266    List<Association> getAssociationsByType(String assocTypeUri) {
267        try {
268            return checkReadAccessAndInstantiate(_getAssociationType(assocTypeUri).getAllInstances());
269        } catch (Exception e) {
270            throw new RuntimeException("Fetching associations by type failed (assocTypeUri=\"" + assocTypeUri + "\")",
271                e);
272        }
273    }
274
275    List<Association> getAssociations(long topic1Id, long topic2Id) {
276        return getAssociations(null, topic1Id, topic2Id);   // assocTypeUri=null
277    }
278
279    List<Association> getAssociations(String assocTypeUri, long topic1Id, long topic2Id) {
280        return getAssociations(assocTypeUri, topic1Id, topic2Id, null, null);   // roleTypeUri1=null, roleTypeUri2=null
281    }
282
283    List<Association> getAssociations(String assocTypeUri, long topic1Id, long topic2Id, String roleTypeUri1,
284                                                                                         String roleTypeUri2) {
285        return instantiate(_getAssociations(assocTypeUri, topic1Id, topic2Id, roleTypeUri1, roleTypeUri2));
286    }
287
288    /**
289     * Fetches from DB and filters READables. No instantiation.
290     */
291    Iterable<AssociationModelImpl> _getAssociations(String assocTypeUri, long topic1Id, long topic2Id,
292                                                    String roleTypeUri1, String roleTypeUri2) {
293        logger.fine("assocTypeUri=\"" + assocTypeUri + "\", topic1Id=" + topic1Id + ", topic2Id=" + topic2Id +
294            ", roleTypeUri1=\"" + roleTypeUri1 + "\", roleTypeUri2=\"" + roleTypeUri2 + "\"");
295        try {
296            return filterReadables(fetchAssociations(assocTypeUri, topic1Id, topic2Id, roleTypeUri1, roleTypeUri2));
297        } catch (Exception e) {
298            throw new RuntimeException("Fetching associations between topics " + topic1Id + " and " + topic2Id +
299                " failed (assocTypeUri=\"" + assocTypeUri + "\", roleTypeUri1=\"" + roleTypeUri1 +
300                "\", roleTypeUri2=\"" + roleTypeUri2 + "\")", e);
301        }
302    }
303
304    // ---
305
306    Iterable<Association> getAllAssociations() {
307        return new AssociationIterable(this);
308    }
309
310    long[] getPlayerIds(long assocId) {
311        return fetchPlayerIds(assocId);
312    }
313
314    // ---
315
316    /**
317     * Convenience.
318     */
319    AssociationImpl createAssociation(String typeUri, RoleModel roleModel1, RoleModel roleModel2) {
320        return createAssociation(mf.newAssociationModel(typeUri, roleModel1, roleModel2));
321    }
322
323    /**
324     * Creates a new association in the DB.
325     */
326    AssociationImpl createAssociation(AssociationModelImpl model) {
327        try {
328            em.fireEvent(CoreEvent.PRE_CREATE_ASSOCIATION, model);
329            //
330            model.preCreate();
331            //
332            // 1) store in DB
333            storeAssociation(model);
334            valueStorage.storeValue(model);
335            createAssociationInstantiation(model.getId(), model.getTypeUri());
336            // 2) instantiate
337            AssociationImpl assoc = model.instantiate();
338            //
339            model.postCreate();
340            //
341            em.fireEvent(CoreEvent.POST_CREATE_ASSOCIATION, assoc);
342            return assoc;
343        } catch (Exception e) {
344            throw new RuntimeException("Creating association failed (" + model + ")", e);
345        }
346    }
347
348    // ---
349
350    void updateAssociation(AssociationModelImpl updateModel) {
351        try {
352            AssociationModelImpl model = fetchAssociation(updateModel.getId());
353            updateAssociation(model, updateModel);
354            //
355            // Note: there is no possible POST_UPDATE_ASSOCIATION_REQUEST event to fire here (compare to updateTopic()).
356            // It would be equivalent to POST_UPDATE_ASSOCIATION. Per request exactly one association is updated.
357            // Its childs are always topics (never associations).
358        } catch (Exception e) {
359            throw new RuntimeException("Updating association " + updateModel.getId() + " failed", e);
360        }
361    }
362
363    void updateAssociation(AssociationModelImpl assoc, AssociationModelImpl updateModel) {
364        try {
365            checkAssociationWriteAccess(assoc.getId());
366            assoc.update(updateModel);
367        } catch (Exception e) {
368            throw new RuntimeException("Updating association " + assoc.getId() + " failed", e);
369        }
370    }
371
372    // ---
373
374    void deleteAssociation(long assocId) {
375        deleteAssociation(fetchAssociation(assocId));
376    }
377
378    void deleteAssociation(AssociationModelImpl assoc) {
379        try {
380            checkAssociationWriteAccess(assoc.getId());
381            assoc.delete();
382        } catch (Exception e) {
383            throw new RuntimeException("Deleting association " + assoc.getId() + " failed", e);
384        }
385    }
386
387
388
389    // ===
390
391    void createTopicInstantiation(long topicId, String topicTypeUri) {
392        try {
393            AssociationModel assoc = mf.newAssociationModel("dm4.core.instantiation",
394                mf.newTopicRoleModel(topicTypeUri, "dm4.core.type"),
395                mf.newTopicRoleModel(topicId, "dm4.core.instance"));
396            storeAssociation(assoc);   // direct storage calls used here ### explain
397            storeAssociationValue(assoc.getId(), assoc.getSimpleValue());
398            createAssociationInstantiation(assoc.getId(), assoc.getTypeUri());
399        } catch (Exception e) {
400            throw new RuntimeException("Associating topic " + topicId +
401                " with topic type \"" + topicTypeUri + "\" failed", e);
402        }
403    }
404
405    void createAssociationInstantiation(long assocId, String assocTypeUri) {
406        try {
407            AssociationModel assoc = mf.newAssociationModel("dm4.core.instantiation",
408                mf.newTopicRoleModel(assocTypeUri, "dm4.core.type"),
409                mf.newAssociationRoleModel(assocId, "dm4.core.instance"));
410            storeAssociation(assoc);   // direct storage calls used here ### explain
411            storeAssociationValue(assoc.getId(), assoc.getSimpleValue());
412        } catch (Exception e) {
413            throw new RuntimeException("Associating association " + assocId +
414                " with association type \"" + assocTypeUri + "\" failed", e);
415        }
416    }
417
418
419
420    // === Types ===
421
422    TopicTypeImpl getTopicType(String uri) {
423        return checkReadAccessAndInstantiate(_getTopicType(uri));
424    }
425
426    TopicTypeImpl getTopicTypeImplicitly(long topicId) {
427        checkTopicReadAccess(topicId);
428        return _getTopicType(typeUri(topicId)).instantiate();
429    }
430
431    // ---
432
433    AssociationTypeImpl getAssociationType(String uri) {
434        return checkReadAccessAndInstantiate(_getAssociationType(uri));
435    }
436
437    AssociationTypeImpl getAssociationTypeImplicitly(long assocId) {
438        checkAssociationReadAccess(assocId);
439        return _getAssociationType(typeUri(assocId)).instantiate();
440    }
441
442    // ---
443
444    List<TopicType> getAllTopicTypes() {
445        try {
446            List<TopicType> topicTypes = new ArrayList();
447            for (String uri : getTopicTypeUris()) {
448                topicTypes.add(_getTopicType(uri).instantiate());
449            }
450            return topicTypes;
451        } catch (Exception e) {
452            throw new RuntimeException("Fetching all topic types failed", e);
453        }
454    }
455
456    List<AssociationType> getAllAssociationTypes() {
457        try {
458            List<AssociationType> assocTypes = new ArrayList();
459            for (String uri : getAssociationTypeUris()) {
460                assocTypes.add(_getAssociationType(uri).instantiate());
461            }
462            return assocTypes;
463        } catch (Exception e) {
464            throw new RuntimeException("Fetching all association types failed", e);
465        }
466    }
467
468    // ---
469
470    TopicTypeImpl createTopicType(TopicTypeModelImpl model) {
471        try {
472            em.fireEvent(CoreEvent.PRE_CREATE_TOPIC_TYPE, model);
473            //
474            // store in DB
475            createType(model, URI_PREFIX_TOPIC_TYPE);
476            //
477            TopicTypeImpl topicType = model.instantiate();
478            em.fireEvent(CoreEvent.INTRODUCE_TOPIC_TYPE, topicType);
479            //
480            return topicType;
481        } catch (Exception e) {
482            throw new RuntimeException("Creating topic type \"" + model.getUri() + "\" failed", e);
483        }
484    }
485
486    AssociationTypeImpl createAssociationType(AssociationTypeModelImpl model) {
487        try {
488            em.fireEvent(CoreEvent.PRE_CREATE_ASSOCIATION_TYPE, model);
489            //
490            // store in DB
491            createType(model, URI_PREFIX_ASSOCIATION_TYPE);
492            //
493            AssociationTypeImpl assocType = model.instantiate();
494            em.fireEvent(CoreEvent.INTRODUCE_ASSOCIATION_TYPE, assocType);
495            //
496            return assocType;
497        } catch (Exception e) {
498            throw new RuntimeException("Creating association type \"" + model.getUri() + "\" failed", e);
499        }
500    }
501
502    // ---
503
504    void updateTopicType(TopicTypeModelImpl updateModel) {
505        try {
506            // Note: type lookup is by ID. The URI might have changed, the ID does not.
507            TopicModelImpl topic = fetchTopic(updateModel.getId());
508            topic.checkWriteAccess();
509            _getTopicType(topic.getUri()).update(updateModel);
510        } catch (Exception e) {
511            throw new RuntimeException("Updating topic type failed (" + updateModel + ")", e);
512        }
513    }
514
515    void updateAssociationType(AssociationTypeModelImpl updateModel) {
516        try {
517            // Note: type lookup is by ID. The URI might have changed, the ID does not.
518            TopicModelImpl topic = fetchTopic(updateModel.getId());
519            topic.checkWriteAccess();
520            _getAssociationType(topic.getUri()).update(updateModel);
521        } catch (Exception e) {
522            throw new RuntimeException("Updating association type failed (" + updateModel + ")", e);
523        }
524    }
525
526    // ---
527
528    void deleteTopicType(String topicTypeUri) {
529        try {
530            TypeModelImpl type = _getTopicType(topicTypeUri);
531            type.checkWriteAccess();
532            type.delete();
533            // ### 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            TypeModelImpl type = _getAssociationType(assocTypeUri);
542            type.checkWriteAccess();
543            type.delete();
544            // ### TODO: delete view config topics
545        } catch (Exception e) {
546            throw new RuntimeException("Deleting association type \"" + assocTypeUri + "\" failed", e);
547        }
548    }
549
550    // ---
551
552    Topic createRoleType(TopicModelImpl model) {
553        // check type URI argument
554        String typeUri = model.getTypeUri();
555        if (typeUri == null) {
556            model.setTypeUri("dm4.core.role_type");
557        } else {
558            if (!typeUri.equals("dm4.core.role_type")) {
559                throw new IllegalArgumentException("A role type is supposed to be of type \"dm4.core.role_type\" " +
560                    "(found: \"" + typeUri + "\")");
561            }
562        }
563        //
564        return createTopic(model, URI_PREFIX_ROLE_TYPE);
565    }
566
567    // ---
568
569    /**
570     * Type cache direct access. No permission check.
571     */
572    TopicTypeModelImpl _getTopicType(String uri) {
573        return typeStorage.getTopicType(uri);
574    }
575
576    /**
577     * Type cache direct access. No permission check.
578     */
579    AssociationTypeModelImpl _getAssociationType(String uri) {
580        return typeStorage.getAssociationType(uri);
581    }
582
583
584
585    // === Generic Object ===
586
587    DeepaMehtaObject getObject(long id) {
588        return checkReadAccessAndInstantiate(fetchObject(id));
589    }
590
591
592
593    // === Properties ===
594
595    List<Topic> getTopicsByProperty(String propUri, Object propValue) {
596        return checkReadAccessAndInstantiate(fetchTopicsByProperty(propUri, propValue));
597    }
598
599    List<Topic> getTopicsByPropertyRange(String propUri, Number from, Number to) {
600        return checkReadAccessAndInstantiate(fetchTopicsByPropertyRange(propUri, from, to));
601    }
602
603    List<Association> getAssociationsByProperty(String propUri, Object propValue) {
604        return checkReadAccessAndInstantiate(fetchAssociationsByProperty(propUri, propValue));
605    }
606
607    List<Association> getAssociationsByPropertyRange(String propUri, Number from, Number to) {
608        return checkReadAccessAndInstantiate(fetchAssociationsByPropertyRange(propUri, from, to));
609    }
610
611    // ------------------------------------------------------------------------------------------------- Private Methods
612
613
614
615    // === Access Control / Instantiation ===
616
617    // These methods 1) instantiate objects from models, and 2) check the READ permission for each model.
618    // Call these methods when passing objects fetched from the DB to the user.
619    // ### TODO: make these private?
620
621    <O> O checkReadAccessAndInstantiate(DeepaMehtaObjectModelImpl model) {
622        model.checkReadAccess();
623        return (O) model.instantiate();
624    }
625
626    <O> List<O> checkReadAccessAndInstantiate(Iterable<? extends DeepaMehtaObjectModelImpl> models) {
627        return instantiate(filterReadables(models));
628    }
629
630    // ---
631
632    private <M extends DeepaMehtaObjectModelImpl> Iterable<M> filterReadables(Iterable<M> models) {
633        Iterator<? extends DeepaMehtaObjectModelImpl> i = models.iterator();
634        while (i.hasNext()) {
635            if (!hasReadAccess(i.next())) {
636                i.remove();
637            }
638        }
639        return models;
640    }
641
642    boolean hasReadAccess(DeepaMehtaObjectModelImpl model) {
643        try {
644            model.checkReadAccess();
645            return true;
646        } catch (AccessControlException e) {
647            return false;
648        }
649    }
650
651    // ---
652
653    /**
654     * @throws  AccessControlException  if the current user has no permission.
655     */
656    void checkTopicReadAccess(long topicId) {
657        em.fireEvent(CoreEvent.CHECK_TOPIC_READ_ACCESS, topicId);
658    }
659
660    /**
661     * @throws  AccessControlException  if the current user has no permission.
662     */
663    void checkAssociationReadAccess(long assocId) {
664        em.fireEvent(CoreEvent.CHECK_ASSOCIATION_READ_ACCESS, assocId);
665    }
666
667    // ---
668
669    /**
670     * @throws  AccessControlException  if the current user has no permission.
671     */
672    void checkTopicWriteAccess(long topicId) {
673        em.fireEvent(CoreEvent.CHECK_TOPIC_WRITE_ACCESS, topicId);
674    }
675
676    /**
677     * @throws  AccessControlException  if the current user has no permission.
678     */
679    void checkAssociationWriteAccess(long assocId) {
680        em.fireEvent(CoreEvent.CHECK_ASSOCIATION_WRITE_ACCESS, assocId);
681    }
682
683
684
685    // === Instantiation ===
686
687    <O> List<O> instantiate(Iterable<? extends DeepaMehtaObjectModelImpl> models) {
688        List<O> objects = new ArrayList();
689        for (DeepaMehtaObjectModelImpl model : models) {
690            objects.add((O) model.instantiate());
691        }
692        return objects;
693    }
694
695
696
697    // ===
698
699    private List<String> getTopicTypeUris() {
700        try {
701            List<String> topicTypeUris = new ArrayList();
702            // add meta types
703            topicTypeUris.add("dm4.core.topic_type");
704            topicTypeUris.add("dm4.core.assoc_type");
705            topicTypeUris.add("dm4.core.meta_type");
706            // add regular types
707            for (TopicModel topicType : filterReadables(fetchTopics("type_uri", new SimpleValue(
708                                                                    "dm4.core.topic_type")))) {
709                topicTypeUris.add(topicType.getUri());
710            }
711            return topicTypeUris;
712        } catch (Exception e) {
713            throw new RuntimeException("Fetching list of topic type URIs failed", e);
714        }
715    }
716
717    private List<String> getAssociationTypeUris() {
718        try {
719            List<String> assocTypeUris = new ArrayList();
720            for (TopicModel assocType : filterReadables(fetchTopics("type_uri", new SimpleValue(
721                                                                    "dm4.core.assoc_type")))) {
722                assocTypeUris.add(assocType.getUri());
723            }
724            return assocTypeUris;
725        } catch (Exception e) {
726            throw new RuntimeException("Fetching list of association type URIs failed", e);
727        }
728    }
729
730    // ---
731
732    private void createType(TypeModelImpl model, String uriPrefix) {
733        // Note: the type topic is instantiated explicitly on a `TopicModel` (which is freshly created from the
734        // `TypeModel`). Creating the type topic from the `TypeModel` directly would fail as topic creation implies
735        // topic instantiation, and due to the polymorphic `instantiate()` method a `Type` object would be instantiated
736        // (instead a `Topic` object). But instantiating a type implies per-user type projection, that is removing the
737        // assoc defs not readable by the current user. But at the time the type topic is stored in the DB its assoc
738        // defs are not yet stored, and the readability check would fail.
739        TopicModelImpl typeTopic = mf.newTopicModel(model);
740        createTopic(typeTopic, uriPrefix);      // create generic topic
741        //
742        model.id = typeTopic.id;
743        model.uri = typeTopic.uri;
744        //
745        typeStorage.storeType(model);           // store type-specific parts
746    }
747
748    private String typeUri(long objectId) {
749        return (String) fetchProperty(objectId, "type_uri");
750    }
751
752    private void bootstrapTypeCache() {
753        TopicTypeModelImpl metaMetaType = mf.newTopicTypeModel("dm4.core.meta_meta_type", "Meta Meta Type",
754            "dm4.core.text");
755        metaMetaType.setTypeUri("dm4.core.meta_meta_meta_type");
756        typeStorage.putInTypeCache(metaMetaType);
757    }
758}