001package de.deepamehta.core.impl;
002
003import de.deepamehta.core.model.AssociationDefinitionModel;
004import de.deepamehta.core.model.ChildTopicsModel;
005import de.deepamehta.core.model.DeepaMehtaObjectModel;
006import de.deepamehta.core.model.TopicModel;
007import de.deepamehta.core.model.TopicRoleModel;
008import de.deepamehta.core.model.ViewConfigurationModel;
009
010import org.codehaus.jettison.json.JSONObject;
011
012import java.util.ArrayList;
013import java.util.Collection;
014import java.util.List;
015import java.util.logging.Logger;
016
017
018
019class AssociationDefinitionModelImpl extends AssociationModelImpl implements AssociationDefinitionModel {
020
021    // ---------------------------------------------------------------------------------------------- Instance Variables
022
023    private String parentCardinalityUri;
024    private String childCardinalityUri;
025
026    private ViewConfigurationModelImpl viewConfig;     // is never null
027
028    private Logger logger = Logger.getLogger(getClass().getName());
029
030    // ---------------------------------------------------------------------------------------------------- Constructors
031
032    /**
033     * Remains partially uninitialzed. Only usable as an update-model (not as a create-model).
034     */
035    AssociationDefinitionModelImpl(AssociationModelImpl assoc) {
036        this(assoc, null, null, null);
037    }
038
039    /**
040     * @param   assoc   the underlying association.
041     */
042    AssociationDefinitionModelImpl(AssociationModelImpl assoc, String parentCardinalityUri, String childCardinalityUri,
043                                                                                ViewConfigurationModelImpl viewConfig) {
044        super(assoc);
045        this.parentCardinalityUri = parentCardinalityUri;
046        this.childCardinalityUri  = childCardinalityUri;
047        this.viewConfig = viewConfig != null ? viewConfig : mf.newViewConfigurationModel();
048        // ### TODO: why null check? Compare to TypeModelImpl constructor -> see previous constructor
049    }
050
051    // -------------------------------------------------------------------------------------------------- Public Methods
052
053    @Override
054    public String getAssocDefUri() {
055        String customAssocTypeUri = getCustomAssocTypeUri();
056        return getChildTypeUri() + (customAssocTypeUri !=null ? "#" + customAssocTypeUri : "");
057    }
058
059    @Override
060    public String getCustomAssocTypeUri() {
061        TopicModel customAssocType = getCustomAssocType();
062        return customAssocType != null ? customAssocType.getUri() : null;
063    }
064
065    @Override
066    public String getInstanceLevelAssocTypeUri() {
067        String customAssocTypeUri = getCustomAssocTypeUri();
068        return customAssocTypeUri !=null ? customAssocTypeUri : defaultInstanceLevelAssocTypeUri();
069    }
070
071    @Override
072    public String getParentTypeUri() {
073        return ((TopicRoleModel) getRoleModel("dm4.core.parent_type")).getTopicUri();
074    }
075
076    @Override
077    public String getChildTypeUri() {
078        return ((TopicRoleModel) getRoleModel("dm4.core.child_type")).getTopicUri();
079    }
080
081    @Override
082    public String getParentCardinalityUri() {
083        return parentCardinalityUri;
084    }
085
086    @Override
087    public String getChildCardinalityUri() {
088        return childCardinalityUri;
089    }
090
091    @Override
092    public ViewConfigurationModelImpl getViewConfigModel() {
093        return viewConfig;
094    }
095
096    // ---
097
098    @Override
099    public void setParentCardinalityUri(String parentCardinalityUri) {
100        this.parentCardinalityUri = parentCardinalityUri;
101    }
102
103    @Override
104    public void setChildCardinalityUri(String childCardinalityUri) {
105        this.childCardinalityUri = childCardinalityUri;
106    }
107
108    @Override
109    public void setViewConfigModel(ViewConfigurationModel viewConfig) {
110        this.viewConfig = (ViewConfigurationModelImpl) viewConfig;
111    }
112
113    // ---
114
115    @Override
116    public JSONObject toJSON() {
117        try {
118            return super.toJSON()
119                .put("parent_cardinality_uri", parentCardinalityUri)
120                .put("child_cardinality_uri", childCardinalityUri)
121                .put("view_config_topics", viewConfig.toJSONArray());
122        } catch (Exception e) {
123            throw new RuntimeException("Serialization failed (" + this + ")", e);
124        }
125    }
126
127    // ---
128
129    @Override
130    public String toString() {
131        return "\n    association definition (" + super.toString() +
132            ",\n        parent cardinality=\"" + parentCardinalityUri +
133            "\",\n        child cardinality=\"" + childCardinalityUri +
134            "\",\n        " + viewConfig + ")\n";
135    }
136
137    // ----------------------------------------------------------------------------------------- Package Private Methods
138
139
140
141    @Override
142    String className() {
143        return "association definition";
144    }
145
146    @Override
147    AssociationDefinitionImpl instantiate() {
148        return new AssociationDefinitionImpl(this, pl);
149    }
150
151    @Override
152    AssociationDefinitionModelImpl createModelWithChildTopics(ChildTopicsModel childTopics) {
153        return mf.newAssociationDefinitionModel(childTopics);
154    }
155
156
157
158    // === Core Internal Hooks ===
159
160    /**
161     * 2 assoc def specific tasks must be performed once the underlying association is updated:
162     *   - Update the assoc def's cardinality (in type cache + DB). Cardinality is technically not part of the type
163     *     model. So, it is not handled by the generic (model-driven) object update procedure.
164     *   - Possibly rehash the assoc def in type cache. Rehashing is required if the custom assoc type has changed.
165     * <p>
166     * Pre condition: these 3 assoc def parts are already up-to-date through the generic (model-driven) object update
167     * procedure:
168     *   - Assoc Def type (type URI).
169     *   - Custom assoc type (child topics).
170     *   - "Include in Label" flag (child topics).
171     * <p>
172     * Called when update() is called on an AssociationDefinitionModel object. This is in 2 cases:
173     *   - Edit a type interactively (a type topic is selected).
174     *   - Programmatically call getChildTopics().set() on an AssociationDefinitionModel object, e.g. from a migration.
175     * <p>
176     * <i>Not</i> called when an association which also acts as an assoc def is edited interactively (an association is
177     * selected). In this case:
178     *   - Cardinality doesn't need to be updated as Cardinality can't be edited interactively through an association.
179     *   - Rehashing is already performed in TypeModelImpl#_updateAssocDef (called from AssociationModelImpl#postUpdate)
180     *
181     * @param   updateModel
182     *              the update data/instructions.
183     *              Note: on post-update time updateModel and this (assoc def) model may differ at least because
184     *                a) updateModel might contain only certain assoc def parts; this is called a "partial update"
185     *                b) updateModel might contain refs and deletion-refs; this model never contains refs
186     */
187    @Override
188    void postUpdate(DeepaMehtaObjectModel updateModel, DeepaMehtaObjectModel oldObject) {
189        super.postUpdate(updateModel, oldObject);
190        //
191        updateCardinality((AssociationDefinitionModel) updateModel);
192    }
193
194
195
196    // === Update (memory + DB) ===
197
198    void updateParentCardinalityUri(String parentCardinalityUri) {
199        setParentCardinalityUri(parentCardinalityUri);                      // update memory
200        pl.typeStorage.storeParentCardinalityUri(id, parentCardinalityUri); // update DB
201    }
202
203    void updateChildCardinalityUri(String childCardinalityUri) {
204        setChildCardinalityUri(childCardinalityUri);                        // update memory
205        pl.typeStorage.storeChildCardinalityUri(id, childCardinalityUri);   // update DB
206    }
207
208
209
210    // === Label Configuration ===
211
212    final boolean includeInLabel() {
213        TopicModel includeInLabel = getChildTopicsModel().getTopicOrNull("dm4.core.include_in_label");
214        if (includeInLabel == null) {
215            throw new RuntimeException("Assoc def \"" + getAssocDefUri() + "\" has no \"Include in Label\" topic");
216        }
217        return includeInLabel.getSimpleValue().booleanValue();
218    }
219
220
221
222    // === Access Control ===
223
224    boolean isReadable() {
225        try {
226            // 1) check assoc def
227            if (!pl.hasReadAccess(this)) {
228                logger.info("### Assoc def \"" + getAssocDefUri() + "\" not READable");
229                return false;
230            }
231            // Note: there is no need to explicitly check READability for the assoc def's child type.
232            // If the child type is not READable the entire assoc def is not READable as well.
233            //
234            // 2) check custom assoc type, if set
235            TopicModelImpl assocType = getCustomAssocType();
236            if (assocType != null && !pl.hasReadAccess(assocType)) {
237                logger.info("### Assoc def \"" + getAssocDefUri() + "\" not READable (custom assoc type not READable)");
238                return false;
239            }
240            //
241            return true;
242        } catch (Exception e) {
243            throw new RuntimeException("Checking assoc def READability failed (" + this + ")", e);
244        }
245    }
246
247    // ------------------------------------------------------------------------------------------------- Private Methods
248
249
250
251    // === Update ===
252
253    private void updateCardinality(AssociationDefinitionModel newAssocDef) {
254        updateParentCardinality(newAssocDef.getParentCardinalityUri());
255        updateChildCardinality(newAssocDef.getChildCardinalityUri());
256    }
257
258    // ---
259
260    private void updateParentCardinality(String newParentCardinalityUri) {
261        // abort if no update is requested
262        if (newParentCardinalityUri == null) {
263            return;
264        }
265        //
266        String parentCardinalityUri = getParentCardinalityUri();
267        if (!parentCardinalityUri.equals(newParentCardinalityUri)) {
268            logger.info("### Changing parent cardinality URI from \"" + parentCardinalityUri + "\" -> \"" +
269                newParentCardinalityUri + "\"");
270            updateParentCardinalityUri(newParentCardinalityUri);
271        }
272    }
273
274    private void updateChildCardinality(String newChildCardinalityUri) {
275        // abort if no update is requested
276        if (newChildCardinalityUri == null) {
277            return;
278        }
279        //
280        String childCardinalityUri = getChildCardinalityUri();
281        if (!childCardinalityUri.equals(newChildCardinalityUri)) {
282            logger.info("### Changing child cardinality URI from \"" + childCardinalityUri + "\" -> \"" +
283                newChildCardinalityUri + "\"");
284            updateChildCardinalityUri(newChildCardinalityUri);
285        }
286    }
287
288
289
290    // ===
291
292    private TopicModelImpl getCustomAssocType() {
293        return getChildTopicsModel().getTopicOrNull("dm4.core.assoc_type#dm4.core.custom_assoc_type");
294    }
295
296    private String defaultInstanceLevelAssocTypeUri() {
297        if (typeUri.equals("dm4.core.aggregation_def")) {
298            return "dm4.core.aggregation";
299        } else if (typeUri.equals("dm4.core.composition_def")) {
300            return "dm4.core.composition";
301        } else {
302            throw new RuntimeException("Unexpected association type URI: \"" + typeUri + "\"");
303        }
304    }
305}