001    package de.deepamehta.core.impl;
002    
003    import de.deepamehta.core.Association;
004    import de.deepamehta.core.AssociationType;
005    import de.deepamehta.core.AssociationDefinition;
006    import de.deepamehta.core.RelatedAssociation;
007    import de.deepamehta.core.RelatedTopic;
008    import de.deepamehta.core.Topic;
009    import de.deepamehta.core.TopicType;
010    import de.deepamehta.core.Type;
011    import de.deepamehta.core.model.AssociationDefinitionModel;
012    import de.deepamehta.core.model.AssociationModel;
013    import de.deepamehta.core.model.AssociationRoleModel;
014    import de.deepamehta.core.model.AssociationTypeModel;
015    import de.deepamehta.core.model.IndexMode;
016    import de.deepamehta.core.model.RelatedAssociationModel;
017    import de.deepamehta.core.model.RelatedTopicModel;
018    import de.deepamehta.core.model.RoleModel;
019    import de.deepamehta.core.model.SimpleValue;
020    import de.deepamehta.core.model.TopicModel;
021    import de.deepamehta.core.model.TopicRoleModel;
022    import de.deepamehta.core.model.TopicTypeModel;
023    import de.deepamehta.core.model.TypeModel;
024    import de.deepamehta.core.model.ViewConfigurationModel;
025    import de.deepamehta.core.service.ResultList;
026    import de.deepamehta.core.service.TypeStorage;
027    import de.deepamehta.core.util.DeepaMehtaUtils;
028    
029    import static java.util.Arrays.asList;
030    import java.util.ArrayList;
031    import java.util.Collection;
032    import java.util.HashMap;
033    import java.util.List;
034    import java.util.Map;
035    import java.util.logging.Logger;
036    
037    
038    
039    /**
040     * Storage-impl agnostic support for fetching/storing type models.
041     */
042    class TypeStorageImpl implements TypeStorage {
043    
044        // ---------------------------------------------------------------------------------------------- Instance Variables
045    
046        private Map<String, TypeModel> typeCache = new HashMap();
047    
048        private EmbeddedService dms;
049    
050        private Logger logger = Logger.getLogger(getClass().getName());
051    
052        // ---------------------------------------------------------------------------------------------------- Constructors
053    
054        TypeStorageImpl(EmbeddedService dms) {
055            this.dms = dms;
056        }
057    
058        // --------------------------------------------------------------------------------------------------------- Methods
059    
060    
061    
062        // === Type Model Cache ===
063    
064        private TypeModel getType(String typeUri) {
065            return typeCache.get(typeUri);
066        }
067    
068        private void putInTypeCache(TypeModel type) {
069            typeCache.put(type.getUri(), type);
070        }
071    
072        // ---
073    
074        TopicTypeModel getTopicType(String topicTypeUri) {
075            TopicTypeModel topicType = (TopicTypeModel) getType(topicTypeUri);
076            return topicType != null ? topicType : fetchTopicType(topicTypeUri);
077        }
078    
079        AssociationTypeModel getAssociationType(String assocTypeUri) {
080            AssociationTypeModel assocType = (AssociationTypeModel) getType(assocTypeUri);
081            return assocType != null ? assocType : fetchAssociationType(assocTypeUri);
082        }
083    
084    
085    
086        // === Types ===
087    
088        // --- Fetch ---
089    
090        // ### TODO: unify with next method
091        private TopicTypeModel fetchTopicType(String topicTypeUri) {
092            Topic typeTopic = dms.getTopic("uri", new SimpleValue(topicTypeUri));
093            checkTopicType(topicTypeUri, typeTopic);
094            //
095            // 1) fetch type components
096            String dataTypeUri = fetchDataTypeTopic(typeTopic.getId(), topicTypeUri, "topic type").getUri();
097            List<IndexMode> indexModes = fetchIndexModes(typeTopic.getId());
098            List<AssociationDefinitionModel> assocDefs = fetchAssociationDefinitions(typeTopic);
099            List<String> labelConfig = fetchLabelConfig(assocDefs);
100            ViewConfigurationModel viewConfig = fetchTypeViewConfig(typeTopic);
101            //
102            // 2) build type model
103            TopicTypeModel topicType = new TopicTypeModel(typeTopic.getModel(), dataTypeUri, indexModes,
104                assocDefs, labelConfig, viewConfig);
105            //
106            // 3) put in type cache
107            putInTypeCache(topicType);
108            //
109            return topicType;
110        }
111    
112        // ### TODO: unify with previous method
113        private AssociationTypeModel fetchAssociationType(String assocTypeUri) {
114            Topic typeTopic = dms.getTopic("uri", new SimpleValue(assocTypeUri));
115            checkAssociationType(assocTypeUri, typeTopic);
116            //
117            // 1) fetch type components
118            String dataTypeUri = fetchDataTypeTopic(typeTopic.getId(), assocTypeUri, "association type").getUri();
119            List<IndexMode> indexModes = fetchIndexModes(typeTopic.getId());
120            List<AssociationDefinitionModel> assocDefs = fetchAssociationDefinitions(typeTopic);
121            List<String> labelConfig = fetchLabelConfig(assocDefs);
122            ViewConfigurationModel viewConfig = fetchTypeViewConfig(typeTopic);
123            //
124            // 2) build type model
125            AssociationTypeModel assocType = new AssociationTypeModel(typeTopic.getModel(), dataTypeUri, indexModes,
126                assocDefs, labelConfig, viewConfig);
127            //
128            // 3) put in type cache
129            putInTypeCache(assocType);
130            //
131            return assocType;
132        }
133    
134        // ---
135    
136        private void checkTopicType(String topicTypeUri, Topic typeTopic) {
137            if (typeTopic == null) {
138                throw new RuntimeException("Topic type \"" + topicTypeUri + "\" not found in DB");
139            } else if (!typeTopic.getTypeUri().equals("dm4.core.topic_type") &&
140                       !typeTopic.getTypeUri().equals("dm4.core.meta_type") &&
141                       !typeTopic.getTypeUri().equals("dm4.core.meta_meta_type")) {
142                throw new RuntimeException("URI \"" + topicTypeUri + "\" refers to a \"" + typeTopic.getTypeUri() +
143                    "\" when the caller expects a \"dm4.core.topic_type\"");
144            }
145        }
146    
147        private void checkAssociationType(String assocTypeUri, Topic typeTopic) {
148            if (typeTopic == null) {
149                throw new RuntimeException("Association type \"" + assocTypeUri + "\" not found in DB");
150            } else if (!typeTopic.getTypeUri().equals("dm4.core.assoc_type")) {
151                throw new RuntimeException("URI \"" + assocTypeUri + "\" refers to a \"" + typeTopic.getTypeUri() +
152                    "\" when the caller expects a \"dm4.core.assoc_type\"");
153            }
154        }
155    
156        // --- Store ---
157    
158        /**
159         * Stores the type-specific parts of the given type model.
160         * Prerequisite: the generic topic parts are stored already.
161         * <p>
162         * Called to store a newly created topic type or association type.
163         */
164        void storeType(TypeModel type) {
165            // 1) put in type cache
166            // Note: an association type must be put in type cache *before* storing its association definitions.
167            // Consider creation of association type "Composition Definition": it has a composition definition itself.
168            putInTypeCache(type);
169            //
170            // 2) store type-specific parts
171            storeDataType(type.getUri(), type.getDataTypeUri());
172            storeIndexModes(type.getUri(), type.getIndexModes());
173            storeAssocDefs(type.getUri(), type.getAssocDefs());
174            storeLabelConfig(type.getLabelConfig(), type.getAssocDefs());
175            storeViewConfig(createConfigurableType(type.getId()), type.getViewConfigModel());
176        }
177    
178    
179    
180        // === Data Type ===
181    
182        // --- Fetch ---
183    
184        private RelatedTopicModel fetchDataTypeTopic(long typeId, String typeUri, String className) {
185            try {
186                RelatedTopicModel dataType = dms.storageDecorator.fetchTopicRelatedTopic(typeId, "dm4.core.aggregation",
187                    "dm4.core.type", "dm4.core.default", "dm4.core.data_type");
188                if (dataType == null) {
189                    throw new RuntimeException("No data type topic is associated to " + className + " \"" + typeUri + "\"");
190                }
191                return dataType;
192            } catch (Exception e) {
193                throw new RuntimeException("Fetching the data type topic of " + className + " \"" + typeUri + "\" failed",
194                    e);
195            }
196        }
197    
198        // --- Store ---
199    
200        // ### TODO: compare to low-level method EmbeddedService._associateDataType(). Remove structural similarity.
201        void storeDataType(String typeUri, String dataTypeUri) {
202            try {
203                dms.createAssociation("dm4.core.aggregation",
204                    new TopicRoleModel(typeUri,     "dm4.core.type"),
205                    new TopicRoleModel(dataTypeUri, "dm4.core.default"));
206            } catch (Exception e) {
207                throw new RuntimeException("Associating type \"" + typeUri + "\" with data type \"" +
208                    dataTypeUri + "\" failed", e);
209            }
210        }
211    
212    
213    
214        // === Index Modes ===
215    
216        // --- Fetch ---
217    
218        private List<IndexMode> fetchIndexModes(long typeId) {
219            ResultList<RelatedTopicModel> indexModes = dms.storageDecorator.fetchTopicRelatedTopics(typeId,
220                "dm4.core.aggregation", "dm4.core.type", "dm4.core.default", "dm4.core.index_mode", 0);
221            return IndexMode.fromTopics(indexModes.getItems());
222        }
223    
224        // --- Store ---
225    
226        private void storeIndexModes(String typeUri, List<IndexMode> indexModes) {
227            for (IndexMode indexMode : indexModes) {
228                storeIndexMode(typeUri, indexMode);
229            }
230        }
231    
232        void storeIndexMode(String typeUri, IndexMode indexMode) {
233            dms.createAssociation("dm4.core.aggregation",
234                new TopicRoleModel(typeUri,           "dm4.core.type"),
235                new TopicRoleModel(indexMode.toUri(), "dm4.core.default"));
236        }
237    
238    
239    
240        // === Association Definitions ===
241    
242        @Override
243        public void removeAssociationDefinitionFromMemoryAndRebuildSequence(Type type, String childTypeUri) {
244            ((AttachedType) type).removeAssocDefFromMemoryAndRebuildSequence(childTypeUri);
245        }
246    
247        // --- Fetch ---
248    
249        private List<AssociationDefinitionModel> fetchAssociationDefinitions(Topic typeTopic) {
250            Map<Long, AssociationDefinitionModel> assocDefs = fetchAssociationDefinitionsUnsorted(typeTopic);
251            List<RelatedAssociationModel> sequence = fetchSequence(typeTopic);
252            // error check
253            if (assocDefs.size() != sequence.size()) {
254                throw new RuntimeException("DB inconsistency: type \"" + typeTopic.getUri() + "\" has " +
255                    assocDefs.size() + " association definitions but in sequence are " + sequence.size());
256            }
257            //
258            return sortAssocDefs(assocDefs, DeepaMehtaUtils.idList(sequence));
259        }
260    
261        private Map<Long, AssociationDefinitionModel> fetchAssociationDefinitionsUnsorted(Topic typeTopic) {
262            Map<Long, AssociationDefinitionModel> assocDefs = new HashMap();
263            //
264            // 1) fetch child topic types
265            // Note: we must set fetchRelatingComposite to false here. Fetching the composite of association type
266            // Composition Definition would cause an endless recursion. Composition Definition is defined through
267            // Composition Definition itself (child types "Include in Label", "Ordered"). ### FIXDOC: this is obsolete
268            // Note: "othersTopicTypeUri" is set to null. We want consider "dm4.core.topic_type" and "dm4.core.meta_type"
269            // as well (the latter required e.g. by dm4-mail) ### TODO: add a getRelatedTopics() method that takes a list
270            // of topic types.
271            ResultList<RelatedTopic> childTypes = typeTopic.getRelatedTopics(asList("dm4.core.aggregation_def",
272                "dm4.core.composition_def"), "dm4.core.parent_type", "dm4.core.child_type", null, 0);
273                // othersTopicTypeUri=null, maxResultSize=0
274            //
275            // 2) create association definitions
276            // Note: the returned map is an intermediate, hashed by ID. The actual type model is
277            // subsequently build from it by sorting the assoc def's according to the sequence IDs.
278            for (RelatedTopic childType : childTypes) {
279                AssociationDefinitionModel assocDef = fetchAssociationDefinition(childType.getRelatingAssociation(),
280                    typeTopic.getUri(), childType.getUri());
281                assocDefs.put(assocDef.getId(), assocDef);
282            }
283            return assocDefs;
284        }
285    
286        // ---
287    
288        @Override
289        public AssociationDefinitionModel fetchAssociationDefinition(Association assoc) {
290            return fetchAssociationDefinition(assoc, fetchParentType(assoc).getUri(), fetchChildType(assoc).getUri());
291        }
292    
293        private AssociationDefinitionModel fetchAssociationDefinition(Association assoc, String parentTypeUri,
294                                                                                         String childTypeUri) {
295            try {
296                long assocId = assoc.getId();
297                return new AssociationDefinitionModel(
298                    assocId, assoc.getUri(), assoc.getTypeUri(),
299                    parentTypeUri, childTypeUri,
300                    fetchParentCardinality(assocId).getUri(), fetchChildCardinality(assocId).getUri(),
301                    fetchAssocDefViewConfig(assoc)
302                );
303            } catch (Exception e) {
304                throw new RuntimeException("Fetching association definition failed (parentTypeUri=\"" + parentTypeUri +
305                    "\", childTypeUri=" + childTypeUri + ", " + assoc + ")", e);
306            }
307        }
308    
309        // ---
310    
311        private List<AssociationDefinitionModel> sortAssocDefs(Map<Long, AssociationDefinitionModel> assocDefs,
312                                                               List<Long> sequence) {
313            List<AssociationDefinitionModel> sortedAssocDefs = new ArrayList();
314            for (long assocDefId : sequence) {
315                AssociationDefinitionModel assocDef = assocDefs.get(assocDefId);
316                // error check
317                if (assocDef == null) {
318                    throw new RuntimeException("DB inconsistency: ID " + assocDefId +
319                        " is in sequence but not in the type's association definitions");
320                }
321                sortedAssocDefs.add(assocDef);
322            }
323            return sortedAssocDefs;
324        }
325    
326        // --- Store ---
327    
328        private void storeAssocDefs(String typeUri, Collection<AssociationDefinitionModel> assocDefs) {
329            for (AssociationDefinitionModel assocDef : assocDefs) {
330                storeAssociationDefinition(assocDef);
331            }
332            storeSequence(typeUri, assocDefs);
333        }
334    
335        void storeAssociationDefinition(AssociationDefinitionModel assocDef) {
336            try {
337                // Note: creating the underlying association is conditional. It exists already for
338                // an interactively created association definition. Its ID is already set.
339                if (assocDef.getId() == -1) {
340                    dms.createAssociation(assocDef);
341                }
342                // Note: the assoc def ID is known only after creating the association
343                long assocDefId = assocDef.getId();
344                // cardinality
345                associateParentCardinality(assocDefId, assocDef.getParentCardinalityUri());
346                associateChildCardinality(assocDefId, assocDef.getChildCardinalityUri());
347                //
348                storeViewConfig(createConfigurableAssocDef(assocDefId), assocDef.getViewConfigModel());
349            } catch (Exception e) {
350                throw new RuntimeException("Storing association definition \"" + assocDef.getChildTypeUri() +
351                    "\" of type \"" + assocDef.getParentTypeUri() + "\" failed", e);
352            }
353        }
354    
355    
356    
357        // === Parent Type / Child Type ===
358    
359        // --- Fetch ---
360    
361        @Override
362        public Topic fetchParentType(Association assoc) {
363            Topic parentTypeTopic = assoc.getTopic("dm4.core.parent_type");
364            // error check
365            if (parentTypeTopic == null) {
366                throw new RuntimeException("Invalid association definition: topic role dm4.core.parent_type " +
367                    "is missing in " + assoc);
368            }
369            //
370            return parentTypeTopic;
371        }
372    
373        @Override
374        public Topic fetchChildType(Association assoc) {
375            Topic childTypeTopic = assoc.getTopic("dm4.core.child_type");
376            // error check
377            if (childTypeTopic == null) {
378                throw new RuntimeException("Invalid association definition: topic role dm4.core.child_type " +
379                    "is missing in " + assoc);
380            }
381            //
382            return childTypeTopic;
383        }
384    
385    
386    
387        // === Cardinality ===
388    
389        // --- Fetch ---
390    
391        // ### TODO: pass Association instead ID?
392        private RelatedTopicModel fetchParentCardinality(long assocDefId) {
393            RelatedTopicModel parentCard = dms.storageDecorator.fetchAssociationRelatedTopic(assocDefId,
394                "dm4.core.aggregation", "dm4.core.assoc_def", "dm4.core.parent_cardinality", "dm4.core.cardinality");
395            // error check
396            if (parentCard == null) {
397                throw new RuntimeException("Invalid association definition: parent cardinality is missing (assocDefId=" +
398                    assocDefId + ")");
399            }
400            //
401            return parentCard;
402        }
403    
404        // ### TODO: pass Association instead ID?
405        private RelatedTopicModel fetchChildCardinality(long assocDefId) {
406            RelatedTopicModel childCard = dms.storageDecorator.fetchAssociationRelatedTopic(assocDefId,
407                "dm4.core.aggregation", "dm4.core.assoc_def", "dm4.core.child_cardinality", "dm4.core.cardinality");
408            // error check
409            if (childCard == null) {
410                throw new RuntimeException("Invalid association definition: child cardinality is missing (assocDefId=" +
411                    assocDefId + ")");
412            }
413            //
414            return childCard;
415        }
416    
417        // --- Store ---
418    
419        void storeParentCardinalityUri(long assocDefId, String parentCardinalityUri) {
420            // remove current assignment
421            long assocId = fetchParentCardinality(assocDefId).getRelatingAssociation().getId();
422            dms.deleteAssociation(assocId);
423            // create new assignment
424            associateParentCardinality(assocDefId, parentCardinalityUri);
425        }
426    
427        void storeChildCardinalityUri(long assocDefId, String childCardinalityUri) {
428            // remove current assignment
429            long assocId = fetchChildCardinality(assocDefId).getRelatingAssociation().getId();
430            dms.deleteAssociation(assocId);
431            // create new assignment
432            associateChildCardinality(assocDefId, childCardinalityUri);
433        }
434    
435        // ---
436    
437        private void associateParentCardinality(long assocDefId, String parentCardinalityUri) {
438            dms.createAssociation("dm4.core.aggregation",
439                new TopicRoleModel(parentCardinalityUri, "dm4.core.parent_cardinality"),
440                new AssociationRoleModel(assocDefId, "dm4.core.assoc_def"));
441        }
442    
443        private void associateChildCardinality(long assocDefId, String childCardinalityUri) {
444            dms.createAssociation("dm4.core.aggregation",
445                new TopicRoleModel(childCardinalityUri, "dm4.core.child_cardinality"),
446                new AssociationRoleModel(assocDefId, "dm4.core.assoc_def"));
447        }
448    
449    
450    
451        // === Sequence ===
452    
453        // --- Fetch ---
454    
455        // Note: the sequence is fetched in 2 situations:
456        // 1) When fetching a type's association definitions.
457        //    In this situation we don't have a Type object at hand but a sole type topic.
458        // 2) When deleting a sequence in order to rebuild it.
459        private List<RelatedAssociationModel> fetchSequence(Topic typeTopic) {
460            try {
461                List<RelatedAssociationModel> sequence = new ArrayList();
462                // find sequence start
463                RelatedAssociation assocDef = typeTopic.getRelatedAssociation("dm4.core.aggregation", "dm4.core.type",
464                    "dm4.core.sequence_start", null);     // othersAssocTypeUri=null
465                // fetch sequence segments
466                if (assocDef != null) {
467                    sequence.add(assocDef.getModel());
468                    while ((assocDef = assocDef.getRelatedAssociation("dm4.core.sequence", "dm4.core.predecessor",
469                        "dm4.core.successor", null)) != null) {
470                        //
471                        sequence.add(assocDef.getModel());
472                    }
473                }
474                //
475                return sequence;
476            } catch (Exception e) {
477                throw new RuntimeException("Fetching sequence for type \"" + typeTopic.getUri() + "\" failed", e);
478            }
479        }
480    
481        // --- Store ---
482    
483        private void storeSequence(String typeUri, Collection<AssociationDefinitionModel> assocDefs) {
484            logger.fine("### Storing " + assocDefs.size() + " sequence segments for type \"" + typeUri + "\"");
485            AssociationDefinitionModel predecessor = null;
486            for (AssociationDefinitionModel assocDef : assocDefs) {
487                appendToSequence(typeUri, assocDef, predecessor);
488                predecessor = assocDef;
489            }
490        }
491    
492        void appendToSequence(String typeUri, AssociationDefinitionModel assocDef, AssociationDefinitionModel predecessor) {
493            if (predecessor == null) {
494                storeSequenceStart(typeUri, assocDef.getId());
495            } else {
496                storeSequenceSegment(predecessor.getId(), assocDef.getId());
497            }
498        }
499    
500        private void storeSequenceStart(String typeUri, long assocDefId) {
501            dms.createAssociation("dm4.core.aggregation",
502                new TopicRoleModel(typeUri, "dm4.core.type"),
503                new AssociationRoleModel(assocDefId, "dm4.core.sequence_start"));
504        }
505    
506        private void storeSequenceSegment(long predAssocDefId, long succAssocDefId) {
507            dms.createAssociation("dm4.core.sequence",
508                new AssociationRoleModel(predAssocDefId, "dm4.core.predecessor"),
509                new AssociationRoleModel(succAssocDefId, "dm4.core.successor"));
510        }
511    
512        // ---
513    
514        void rebuildSequence(Type type) {
515            deleteSequence(type);
516            storeSequence(type.getUri(), type.getModel().getAssocDefs());
517        }
518    
519        private void deleteSequence(Topic typeTopic) {
520            List<RelatedAssociationModel> sequence = fetchSequence(typeTopic);
521            logger.info("### Deleting " + sequence.size() + " sequence segments of type \"" + typeTopic.getUri() + "\"");
522            for (RelatedAssociationModel assoc : sequence) {
523                long assocId = assoc.getRelatingAssociation().getId();
524                dms.deleteAssociation(assocId);
525            }
526        }
527    
528    
529    
530        // === Label Configuration ===
531    
532        // --- Fetch ---
533    
534        private List<String> fetchLabelConfig(List<AssociationDefinitionModel> assocDefs) {
535            List<String> labelConfig = new ArrayList();
536            for (AssociationDefinitionModel assocDef : assocDefs) {
537                RelatedTopicModel includeInLabel = fetchLabelConfigTopic(assocDef.getId());
538                if (includeInLabel != null && includeInLabel.getSimpleValue().booleanValue()) {
539                    labelConfig.add(assocDef.getChildTypeUri());
540                }
541            }
542            return labelConfig;
543        }
544    
545        private RelatedTopicModel fetchLabelConfigTopic(long assocDefId) {
546            return dms.storageDecorator.fetchAssociationRelatedTopic(assocDefId, "dm4.core.composition",
547                "dm4.core.parent", "dm4.core.child", "dm4.core.include_in_label");
548        }
549    
550        // --- Store ---
551    
552        void storeLabelConfig(List<String> labelConfig, Collection<AssociationDefinitionModel> assocDefs) {
553            for (AssociationDefinitionModel assocDef : assocDefs) {
554                boolean includeInLabel = labelConfig.contains(assocDef.getChildTypeUri());
555                new AttachedAssociationDefinition(assocDef, dms).getChildTopics()
556                    .set("dm4.core.include_in_label", includeInLabel);
557            }
558        }
559    
560    
561    
562        // === View Configurations ===
563    
564        // --- Fetch ---
565    
566        private ViewConfigurationModel fetchTypeViewConfig(Topic typeTopic) {
567            try {
568                // Note: othersTopicTypeUri=null, the view config's topic type is unknown (it is client-specific)
569                ResultList<RelatedTopic> configTopics = typeTopic.getRelatedTopics("dm4.core.aggregation",
570                    "dm4.core.type", "dm4.core.view_config", null, 0).loadChildTopics();
571                return new ViewConfigurationModel(DeepaMehtaUtils.toTopicModels(configTopics));
572            } catch (Exception e) {
573                throw new RuntimeException("Fetching view configuration for type \"" + typeTopic.getUri() +
574                    "\" failed", e);
575            }
576        }
577    
578        private ViewConfigurationModel fetchAssocDefViewConfig(Association assocDef) {
579            try {
580                // Note: othersTopicTypeUri=null, the view config's topic type is unknown (it is client-specific)
581                ResultList<RelatedTopic> configTopics = assocDef.getRelatedTopics("dm4.core.aggregation",
582                    "dm4.core.assoc_def", "dm4.core.view_config", null, 0).loadChildTopics();
583                return new ViewConfigurationModel(DeepaMehtaUtils.toTopicModels(configTopics));
584            } catch (Exception e) {
585                throw new RuntimeException("Fetching view configuration for association definition " + assocDef.getId() +
586                    " failed", e);
587            }
588        }
589    
590        // ---
591    
592        private RelatedTopicModel fetchTypeViewConfigTopic(long typeId, String configTypeUri) {
593            // Note: the child topics are not fetched as they are not needed
594            return dms.storageDecorator.fetchTopicRelatedTopic(typeId, "dm4.core.aggregation",
595                "dm4.core.type", "dm4.core.view_config", configTypeUri);
596        }
597    
598        private RelatedTopicModel fetchAssocDefViewConfigTopic(long assocDefId, String configTypeUri) {
599            // Note: the child topics are not fetched as they are not needed
600            return dms.storageDecorator.fetchAssociationRelatedTopic(assocDefId, "dm4.core.aggregation",
601                "dm4.core.assoc_def", "dm4.core.view_config", configTypeUri);
602        }
603    
604        // ---
605    
606        private TopicModel fetchViewConfigTopic(RoleModel configurable, String configTypeUri) {
607            if (configurable instanceof TopicRoleModel) {
608                long typeId = configurable.getPlayerId();
609                return fetchTypeViewConfigTopic(typeId, configTypeUri);
610            } else if (configurable instanceof AssociationRoleModel) {
611                long assocDefId = configurable.getPlayerId();
612                return fetchAssocDefViewConfigTopic(assocDefId, configTypeUri);
613            } else {
614                throw new RuntimeException("Unexpected configurable: " + configurable);
615            }
616        }
617    
618        // --- Store ---
619    
620        private void storeViewConfig(RoleModel configurable, ViewConfigurationModel viewConfig) {
621            try {
622                for (TopicModel configTopic : viewConfig.getConfigTopics()) {
623                    storeViewConfigTopic(configurable, configTopic);
624                }
625            } catch (Exception e) {
626                throw new RuntimeException("Storing view configuration failed (configurable=" + configurable + ")", e);
627            }
628        }
629    
630        void storeViewConfigTopic(RoleModel configurable, TopicModel configTopic) {
631            Topic topic = dms.createTopic(configTopic);
632            dms.createAssociation("dm4.core.aggregation", configurable,
633                new TopicRoleModel(topic.getId(), "dm4.core.view_config"));
634        }
635    
636        // ---
637    
638        /**
639         * Prerequisite: for the configurable a config topic of type configTypeUri exists in the DB.
640         */
641        void storeViewConfigSetting(RoleModel configurable, String configTypeUri, String settingUri, Object value) {
642            try {
643                TopicModel configTopic = fetchViewConfigTopic(configurable, configTypeUri);
644                // ### TODO: do not create an attached topic here. Can we use the value storage?
645                new AttachedTopic(configTopic, dms).getChildTopics().set(settingUri, value);
646            } catch (Exception e) {
647                throw new RuntimeException("Storing view configuration setting failed (configurable=" + configurable +
648                    ", configTypeUri=\"" + configTypeUri + "\", settingUri=\"" + settingUri + "\", value=\"" + value +
649                    "\")", e);
650            }
651        }
652    
653        // --- Helper ---
654    
655        RoleModel createConfigurableType(long typeId) {
656            return new TopicRoleModel(typeId, "dm4.core.type");
657        }
658    
659        RoleModel createConfigurableAssocDef(long assocDefId) {
660            return new AssociationRoleModel(assocDefId, "dm4.core.assoc_def");
661        }
662    }