001package systems.dmx.core.impl;
002
003import systems.dmx.core.model.AssociationDefinitionModel;
004import systems.dmx.core.model.ChildTopicsModel;
005import systems.dmx.core.model.DMXObjectModel;
006import systems.dmx.core.model.TopicModel;
007import systems.dmx.core.model.TopicRoleModel;
008import systems.dmx.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("dmx.core.parent_type")).getTopicUri();
074    }
075
076    @Override
077    public String getChildTypeUri() {
078        return ((TopicRoleModel) getRoleModel("dmx.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 getViewConfig() {
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 setViewConfig(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("parentCardinalityUri", parentCardinalityUri)
120                .put("childCardinalityUri", childCardinalityUri)
121                .put("viewConfigTopics", viewConfig.toJSONArray());
122        } catch (Exception e) {
123            throw new RuntimeException("Serialization failed", e);
124        }
125    }
126
127    // ----------------------------------------------------------------------------------------- Package Private Methods
128
129
130
131    @Override
132    String className() {
133        return "association definition";
134    }
135
136    @Override
137    AssociationDefinitionImpl instantiate() {
138        return new AssociationDefinitionImpl(this, pl);
139    }
140
141    @Override
142    AssociationDefinitionModelImpl createModelWithChildTopics(ChildTopicsModel childTopics) {
143        return mf.newAssociationDefinitionModel(typeUri, childTopics);
144    }
145
146
147
148    // === Core Internal Hooks ===
149
150    /**
151     * 2 assoc def specific tasks must be performed once the underlying association is updated:
152     *   - Update the assoc def's cardinality (in type cache + DB). Cardinality is technically not part of the type
153     *     model. So, it is not handled by the generic (model-driven) object update procedure.
154     *   - Possibly rehash the assoc def in type cache. Rehashing is required if the custom assoc type has changed.
155     * <p>
156     * Pre condition: these 3 assoc def parts are already up-to-date through the generic (model-driven) object update
157     * procedure:
158     *   - Assoc Def type (type URI).
159     *   - Custom assoc type (child topics).
160     *   - "Include in Label" flag (child topics).
161     * <p>
162     * Called when update() is called on an AssociationDefinitionModel object. This is in 2 cases:
163     *   - Edit a type interactively (a type topic is selected).
164     *   - Programmatically call getChildTopics().set() on an AssociationDefinitionModel object, e.g. from a migration.
165     * <p>
166     * <i>Not</i> called when an association which also acts as an assoc def is edited interactively (an association is
167     * selected). In this case:
168     *   - Cardinality doesn't need to be updated as Cardinality can't be edited interactively through an association.
169     *   - Rehashing is already performed in TypeModelImpl#_updateAssocDef (called from AssociationModelImpl#postUpdate)
170     *
171     * @param   updateModel
172     *              the update data/instructions.
173     *              Note: on post-update time updateModel and this (assoc def) model may differ at least because
174     *                a) updateModel might contain only certain assoc def parts; this is called a "partial update"
175     *                b) updateModel might contain refs and deletion-refs; this model never contains refs
176     */
177    @Override
178    void postUpdate(DMXObjectModel updateModel, DMXObjectModel oldObject) {
179        super.postUpdate(updateModel, oldObject);
180        //
181        updateCardinality((AssociationDefinitionModel) updateModel);
182    }
183
184
185
186    // === Update (memory + DB) ===
187
188    void updateParentCardinalityUri(String parentCardinalityUri) {
189        setParentCardinalityUri(parentCardinalityUri);                      // update memory
190        pl.typeStorage.storeParentCardinalityUri(id, parentCardinalityUri); // update DB
191    }
192
193    void updateChildCardinalityUri(String childCardinalityUri) {
194        setChildCardinalityUri(childCardinalityUri);                        // update memory
195        pl.typeStorage.storeChildCardinalityUri(id, childCardinalityUri);   // update DB
196    }
197
198
199
200    // === Identity Configuration ===
201
202    final boolean isIdentityAttr() {
203        TopicModel isIdentityAttr = getChildTopicsModel().getTopicOrNull("dmx.core.identity_attr");
204        if (isIdentityAttr == null) {
205            // ### TODO: should a isIdentityAttr topic always exist?
206            // throw new RuntimeException("Assoc def \"" + getAssocDefUri() + "\" has no \"Identity Attribute\" topic");
207            return false;
208        }
209        return isIdentityAttr.getSimpleValue().booleanValue();
210    }
211
212
213
214    // === Label Configuration ===
215
216    final boolean includeInLabel() {
217        TopicModel includeInLabel = getChildTopicsModel().getTopicOrNull("dmx.core.include_in_label");
218        if (includeInLabel == null) {
219            // ### TODO: should a includeInLabel topic always exist?
220            // throw new RuntimeException("Assoc def \"" + getAssocDefUri() + "\" has no \"Include in Label\" topic");
221            return false;
222        }
223        return includeInLabel.getSimpleValue().booleanValue();
224    }
225
226
227
228    // === Access Control ===
229
230    boolean isReadable() {
231        try {
232            // 1) check assoc def
233            if (!pl.hasReadAccess(this)) {
234                logger.info("### Assoc def \"" + getAssocDefUri() + "\" not READable");
235                return false;
236            }
237            // Note: there is no need to explicitly check READability for the assoc def's child type.
238            // If the child type is not READable the entire assoc def is not READable as well.
239            //
240            // 2) check custom assoc type, if set
241            TopicModelImpl assocType = getCustomAssocType();
242            if (assocType != null && !pl.hasReadAccess(assocType)) {
243                logger.info("### Assoc def \"" + getAssocDefUri() + "\" not READable (custom assoc type not READable)");
244                return false;
245            }
246            //
247            return true;
248        } catch (Exception e) {
249            throw new RuntimeException("Checking assoc def READability failed (" + this + ")", e);
250        }
251    }
252
253    // ------------------------------------------------------------------------------------------------- Private Methods
254
255
256
257    // === Update ===
258
259    private void updateCardinality(AssociationDefinitionModel newAssocDef) {
260        updateParentCardinality(newAssocDef.getParentCardinalityUri());
261        updateChildCardinality(newAssocDef.getChildCardinalityUri());
262    }
263
264    // ---
265
266    private void updateParentCardinality(String newParentCardinalityUri) {
267        // abort if no update is requested
268        if (newParentCardinalityUri == null) {
269            return;
270        }
271        //
272        String parentCardinalityUri = getParentCardinalityUri();
273        if (!parentCardinalityUri.equals(newParentCardinalityUri)) {
274            logger.info("### Changing parent cardinality URI: \"" + parentCardinalityUri + "\" -> \"" +
275                newParentCardinalityUri + "\"");
276            updateParentCardinalityUri(newParentCardinalityUri);
277        }
278    }
279
280    private void updateChildCardinality(String newChildCardinalityUri) {
281        // abort if no update is requested
282        if (newChildCardinalityUri == null) {
283            return;
284        }
285        //
286        String childCardinalityUri = getChildCardinalityUri();
287        if (!childCardinalityUri.equals(newChildCardinalityUri)) {
288            logger.info("### Changing child cardinality URI: \"" + childCardinalityUri + "\" -> \"" +
289                newChildCardinalityUri + "\"");
290            updateChildCardinalityUri(newChildCardinalityUri);
291        }
292    }
293
294
295
296    // ===
297
298    private TopicModelImpl getCustomAssocType() {
299        return getChildTopicsModel().getTopicOrNull("dmx.core.assoc_type#dmx.core.custom_assoc_type");
300    }
301
302    private String defaultInstanceLevelAssocTypeUri() {
303        if (typeUri.equals("dmx.core.aggregation_def")) {
304            return "dmx.core.aggregation";
305        } else if (typeUri.equals("dmx.core.composition_def")) {
306            return "dmx.core.composition";
307        } else {
308            throw new RuntimeException("Unexpected association type URI: \"" + typeUri + "\"");
309        }
310    }
311}