001package de.deepamehta.core.impl;
002
003import de.deepamehta.core.model.AssociationDefinitionModel;
004import de.deepamehta.core.model.AssociationModel;
005import de.deepamehta.core.model.ChildTopicsModel;
006import de.deepamehta.core.model.DeepaMehtaObjectModel;
007import de.deepamehta.core.model.RelatedTopicModel;
008import de.deepamehta.core.model.SimpleValue;
009import de.deepamehta.core.model.TopicModel;
010import de.deepamehta.core.model.TopicReferenceModel;
011import de.deepamehta.core.model.TypeModel;
012
013import java.util.List;
014import java.util.logging.Logger;
015
016
017
018/**
019 * Helper for storing/fetching simple values and composite value models.
020 *
021 * ### TODO: unify with DeepaMehtaObjectModelImpl and then drop this class?
022 */
023class ValueStorage {
024
025    // ---------------------------------------------------------------------------------------------- Instance Variables
026
027    private PersistenceLayer pl;
028
029    private Logger logger = Logger.getLogger(getClass().getName());
030
031    // ---------------------------------------------------------------------------------------------------- Constructors
032
033    ValueStorage(PersistenceLayer pl) {
034        this.pl = pl;
035    }
036
037    // ----------------------------------------------------------------------------------------- Package Private Methods
038
039    /**
040     * Fetches the child topic models (recursively) of the given parent object model and updates it in-place.
041     * ### TODO: recursion is required in some cases (e.g. when fetching a topic through REST API) but is possibly
042     * overhead in others (e.g. when updating composite structures).
043     */
044    void fetchChildTopics(DeepaMehtaObjectModel parent) {
045        for (AssociationDefinitionModel assocDef : ((DeepaMehtaObjectModelImpl) parent).getType().getAssocDefs()) {
046            fetchChildTopics(parent, assocDef);
047        }
048    }
049
050    /**
051     * Fetches the child topic models (recursively) of the given parent object model and updates it in-place.
052     * ### TODO: recursion is required in some cases (e.g. when fetching a topic through REST API) but is possibly
053     * overhead in others (e.g. when updating composite structures).
054     * <p>
055     * Works for both, "one" and "many" association definitions.
056     *
057     * @param   assocDef    The child topic models according to this association definition are fetched.
058     */
059    void fetchChildTopics(DeepaMehtaObjectModel parent, AssociationDefinitionModel assocDef) {
060        try {
061            ChildTopicsModel childTopics = parent.getChildTopicsModel();
062            String cardinalityUri = assocDef.getChildCardinalityUri();
063            String assocDefUri    = assocDef.getAssocDefUri();
064            if (cardinalityUri.equals("dm4.core.one")) {
065                RelatedTopicModel childTopic = fetchChildTopic(parent.getId(), assocDef);
066                // Note: topics just created have no child topics yet
067                if (childTopic != null) {
068                    childTopics.put(assocDefUri, childTopic);
069                    fetchChildTopics(childTopic);    // recursion
070                }
071            } else if (cardinalityUri.equals("dm4.core.many")) {
072                for (RelatedTopicModel childTopic : fetchChildTopics(parent.getId(), assocDef)) {
073                    childTopics.add(assocDefUri, childTopic);
074                    fetchChildTopics(childTopic);    // recursion
075                }
076            } else {
077                throw new RuntimeException("\"" + cardinalityUri + "\" is an unexpected cardinality URI");
078            }
079        } catch (Exception e) {
080            throw new RuntimeException("Fetching the \"" + assocDef.getAssocDefUri() + "\" child topics of object " +
081                parent.getId() + " failed", e);
082        }
083    }
084
085    // ---
086
087    /**
088     * Stores and indexes the specified model's value, either a simple value or a composite value (child topics).
089     * Depending on the model type's data type dispatches either to storeSimpleValue() or to storeChildTopics().
090     * <p>
091     * Called to store the initial value of a newly created topic/association.
092     */
093    void storeValue(DeepaMehtaObjectModelImpl model) {
094        if (model.getType().getDataTypeUri().equals("dm4.core.composite")) {
095            storeChildTopics(model);
096            model.calculateLabelAndUpdate();
097        } else {
098            model.storeSimpleValue();
099        }
100    }
101
102    // ------------------------------------------------------------------------------------------------- Private Methods
103
104    /**
105     * Stores the composite value (child topics) of the specified topic or association model.
106     * Called to store the initial value of a newly created topic/association.
107     * <p>
108     * Note: the given model can contain childs not defined in the type definition.
109     * Only the childs defined in the type definition are stored.
110     */
111    private void storeChildTopics(DeepaMehtaObjectModelImpl parent) {
112        ChildTopicsModelImpl model = null;
113        try {
114            model = parent.getChildTopicsModel();
115            for (AssociationDefinitionModel assocDef : parent.getType().getAssocDefs()) {
116                String assocDefUri    = assocDef.getAssocDefUri();
117                String cardinalityUri = assocDef.getChildCardinalityUri();
118                if (cardinalityUri.equals("dm4.core.one")) {
119                    RelatedTopicModelImpl childTopic = model.getTopicOrNull(assocDefUri);
120                    if (childTopic != null) {   // skip if not contained in create request
121                        storeChildTopic(childTopic, parent, assocDef);
122                    }
123                } else if (cardinalityUri.equals("dm4.core.many")) {
124                    List<RelatedTopicModelImpl> childTopics = model.getTopicsOrNull(assocDefUri);
125                    if (childTopics != null) {  // skip if not contained in create request
126                        for (RelatedTopicModelImpl childTopic : childTopics) {
127                            storeChildTopic(childTopic, parent, assocDef);
128                        }
129                    }
130                } else {
131                    throw new RuntimeException("\"" + cardinalityUri + "\" is an unexpected cardinality URI");
132                }
133            }
134        } catch (Exception e) {
135            throw new RuntimeException("Storing the child topics of object " + parent.getId() + " failed (" +
136                model + ")", e);
137        }
138    }
139
140    private void storeChildTopic(RelatedTopicModelImpl childTopic, DeepaMehtaObjectModel parent,
141                                                                   AssociationDefinitionModel assocDef) {
142        if (childTopic instanceof TopicReferenceModel) {
143            resolveReference((TopicReferenceModel) childTopic);
144        } else {
145            pl.createTopic(childTopic);
146        }
147        associateChildTopic(parent, childTopic, assocDef);
148    }
149
150    // ---
151
152    /**
153     * Replaces a reference with the real thing.
154     */
155    void resolveReference(TopicReferenceModel topicRef) {
156        topicRef.set(fetchReferencedTopic(topicRef));
157    }
158
159    private DeepaMehtaObjectModel fetchReferencedTopic(TopicReferenceModel topicRef) {
160        // Note: the resolved topic must be fetched including its child topics.
161        // They might be required for label calculation and/or at client-side.
162        if (topicRef.isReferenceById()) {
163            return pl.fetchTopic(topicRef.getId()).loadChildTopics();
164        } else if (topicRef.isReferenceByUri()) {
165            TopicModelImpl topic = pl.fetchTopic("uri", new SimpleValue(topicRef.getUri()));
166            if (topic == null) {
167                throw new RuntimeException("Topic with URI \"" + topicRef.getUri() + "\" not found");
168            }
169            return topic.loadChildTopics();
170        } else {
171            throw new RuntimeException("Invalid topic reference (" + topicRef + ")");
172        }
173    }
174
175    // ---
176
177    /**
178     * Creates an association between the given parent object ("Parent" role) and the child topic ("Child" role).
179     * The association type is taken from the given association definition.
180     */
181    void associateChildTopic(DeepaMehtaObjectModel parent, RelatedTopicModel childTopic,
182                                                           AssociationDefinitionModel assocDef) {
183        AssociationModel assoc = childTopic.getRelatingAssociation();
184        assoc.setTypeUri(assocDef.getInstanceLevelAssocTypeUri());
185        assoc.setRoleModel1(parent.createRoleModel("dm4.core.parent"));
186        assoc.setRoleModel2(childTopic.createRoleModel("dm4.core.child"));
187        pl.createAssociation((AssociationModelImpl) assoc);
188    }
189
190
191
192    // === Helper ===
193
194    /**
195     * Fetches and returns a child topic or <code>null</code> if no such topic extists.
196     */
197    private RelatedTopicModel fetchChildTopic(long parentId, AssociationDefinitionModel assocDef) {
198        return pl.fetchRelatedTopic(
199            parentId,
200            assocDef.getInstanceLevelAssocTypeUri(),
201            "dm4.core.parent", "dm4.core.child",
202            assocDef.getChildTypeUri()
203        );
204    }
205
206    private List<RelatedTopicModelImpl> fetchChildTopics(long parentId, AssociationDefinitionModel assocDef) {
207        return pl.fetchRelatedTopics(
208            parentId,
209            assocDef.getInstanceLevelAssocTypeUri(),
210            "dm4.core.parent", "dm4.core.child",
211            assocDef.getChildTypeUri()
212        );
213    }
214}