001package de.deepamehta.core.impl;
002
003import de.deepamehta.core.Association;
004import de.deepamehta.core.AssociationType;
005import de.deepamehta.core.AssociationDefinition;
006import de.deepamehta.core.RelatedAssociation;
007import de.deepamehta.core.RelatedTopic;
008import de.deepamehta.core.Topic;
009import de.deepamehta.core.TopicType;
010import de.deepamehta.core.Type;
011import de.deepamehta.core.model.AssociationDefinitionModel;
012import de.deepamehta.core.model.AssociationModel;
013import de.deepamehta.core.model.AssociationRoleModel;
014import de.deepamehta.core.model.AssociationTypeModel;
015import de.deepamehta.core.model.IndexMode;
016import de.deepamehta.core.model.RelatedAssociationModel;
017import de.deepamehta.core.model.RelatedTopicModel;
018import de.deepamehta.core.model.RoleModel;
019import de.deepamehta.core.model.SimpleValue;
020import de.deepamehta.core.model.TopicModel;
021import de.deepamehta.core.model.TopicRoleModel;
022import de.deepamehta.core.model.TopicTypeModel;
023import de.deepamehta.core.model.TypeModel;
024import de.deepamehta.core.model.ViewConfigurationModel;
025import de.deepamehta.core.service.ResultList;
026import de.deepamehta.core.service.TypeStorage;
027import de.deepamehta.core.util.DeepaMehtaUtils;
028
029import static java.util.Arrays.asList;
030import java.util.ArrayList;
031import java.util.Collection;
032import java.util.HashMap;
033import java.util.List;
034import java.util.Map;
035import java.util.logging.Logger;
036
037
038
039/**
040 * Storage-impl agnostic support for fetching/storing type models.
041 */
042class 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}