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