001package de.deepamehta.core.impl;
002
003import de.deepamehta.core.AssociationDefinition;
004import de.deepamehta.core.DeepaMehtaObject;
005import de.deepamehta.core.JSONEnabled;
006import de.deepamehta.core.Type;
007import de.deepamehta.core.ViewConfiguration;
008import de.deepamehta.core.model.AssociationDefinitionModel;
009import de.deepamehta.core.model.IndexMode;
010import de.deepamehta.core.model.RoleModel;
011import de.deepamehta.core.model.TypeModel;
012import de.deepamehta.core.service.Directive;
013import de.deepamehta.core.service.Directives;
014import de.deepamehta.core.util.SequencedHashMap;
015
016import org.codehaus.jettison.json.JSONObject;
017
018import java.util.Collection;
019import java.util.Iterator;
020import java.util.List;
021import java.util.logging.Logger;
022
023
024
025abstract class AttachedType extends AttachedTopic implements Type {
026
027    // ---------------------------------------------------------------------------------------------- Instance Variables
028
029    private SequencedHashMap<String, AssociationDefinition> assocDefs;  // Attached object cache
030    private ViewConfiguration viewConfig;                               // Attached object cache
031
032    private Logger logger = Logger.getLogger(getClass().getName());
033
034    // ---------------------------------------------------------------------------------------------------- Constructors
035
036    AttachedType(TypeModel model, EmbeddedService dms) {
037        super(model, dms);
038        // init attached object cache
039        initAssocDefs();
040        initViewConfig();
041    }
042
043    // -------------------------------------------------------------------------------------------------- Public Methods
044
045
046
047    // ******************************************
048    // *** AttachedDeepaMehtaObject Overrides ***
049    // ******************************************
050
051
052
053    // === Updating ===
054
055    @Override
056    public void update(TypeModel model) {
057        boolean uriChanged = hasUriChanged(model.getUri());
058        if (uriChanged) {
059            _removeFromTypeCache();
060        }
061        //
062        super.update(model);
063        //
064        if (uriChanged) {
065            putInTypeCache();   // abstract
066        }
067        //
068        updateDataTypeUri(model.getDataTypeUri());
069        updateAssocDefs(model.getAssocDefs());
070        updateSequence(model.getAssocDefs());
071        updateLabelConfig(model.getLabelConfig());
072    }
073
074
075
076    // === Deletion ===
077
078    @Override
079    public void delete() {
080        String operation = "Deleting " + className() + " \"" + getUri() + "\" (named \"" + getSimpleValue() + "\")";
081        try {
082            logger.info(operation);
083            //
084            int size = getAllInstances().size();
085            if (size > 0) {
086                throw new RuntimeException(size + " \"" + getSimpleValue() + "\" instances still exist");
087            }
088            //
089            super.delete();   // delete type topic
090            //
091            _removeFromTypeCache();
092        } catch (Exception e) {
093            throw new RuntimeException(operation + " failed", e);
094        }
095    }
096
097
098
099    // ***************************
100    // *** Type Implementation ***
101    // ***************************
102
103
104
105    // === Model ===
106
107    // --- Data Type ---
108
109    @Override
110    public String getDataTypeUri() {
111        return getModel().getDataTypeUri();
112    }
113
114    @Override
115    public void setDataTypeUri(String dataTypeUri) {
116        // update memory
117        getModel().setDataTypeUri(dataTypeUri);
118        // update DB
119        storeDataTypeUri(dataTypeUri);
120    }
121
122    // --- Index Modes ---
123
124    @Override
125    public List<IndexMode> getIndexModes() {
126        return getModel().getIndexModes();
127    }
128
129    @Override
130    public void addIndexMode(IndexMode indexMode) {
131        // update memory
132        getModel().addIndexMode(indexMode);
133        // update DB
134        dms.typeStorage.storeIndexMode(getUri(), indexMode);
135        indexAllInstances(indexMode);
136    }
137
138    // --- Association Definitions ---
139
140    @Override
141    public Collection<AssociationDefinition> getAssocDefs() {
142        return assocDefs.values();
143    }
144
145    @Override
146    public AssociationDefinition getAssocDef(String childTypeUri) {
147        AssociationDefinition assocDef = assocDefs.get(childTypeUri);
148        if (assocDef == null) {
149            throw new RuntimeException("Schema violation: association definition \"" +
150                childTypeUri + "\" not found in " + this);
151        }
152        return assocDef;
153    }
154
155    @Override
156    public boolean hasAssocDef(String childTypeUri) {
157        return assocDefs.get(childTypeUri) != null;
158    }
159
160    @Override
161    public Type addAssocDef(AssociationDefinitionModel assocDef) {
162        return addAssocDefBefore(assocDef, null);   // beforeChildTypeUri=null
163    }
164
165    @Override
166    public Type addAssocDefBefore(AssociationDefinitionModel assocDef, String beforeChildTypeUri) {
167        // Note: the last assoc def must be determined *before* the memory is updated
168        AssociationDefinitionModel lastAssocDef = lastAssocDef();
169        // 1) update memory
170        getModel().addAssocDefBefore(assocDef, beforeChildTypeUri); // update model
171        _addAssocDefBefore(assocDef, beforeChildTypeUri);           // update attached object cache
172        // 2) update DB
173        dms.typeStorage.storeAssociationDefinition(assocDef);
174        // update sequence
175        long assocDefId = assocDef.getId();
176        if (beforeChildTypeUri == null) {
177            // append at end
178            dms.typeStorage.appendToSequence(getId(), assocDefId, lastAssocDef);
179        } else if (firstAssocDef().getId() == assocDefId) {
180            // insert at start
181            dms.typeStorage.insertAtSequenceStart(getId(), assocDefId);
182        } else {
183            // insert in the middle
184            long beforeAssocDefId = getAssocDef(beforeChildTypeUri).getId();
185            dms.typeStorage.insertIntoSequence(assocDefId, beforeAssocDefId);
186        }
187        return this;
188    }
189
190    @Override
191    public void updateAssocDef(AssociationDefinitionModel assocDef) {
192        // update memory
193        getModel().updateAssocDef(assocDef);    // update model
194        _addAssocDef(assocDef);                 // update attached object cache
195        // update DB
196        // ### Note: the DB is not updated here! In case of interactive assoc type change the association is
197        // already updated in DB. => See interface comment.
198    }
199
200    @Override
201    public Type removeAssocDef(String childTypeUri) {
202        // We trigger deleting an association definition by deleting the underlying association. This mimics deleting an
203        // association definition interactively in the webclient. Updating this type definition's memory and DB sequence
204        // is triggered then by the Type Editor plugin's preDeleteAssociation() hook.
205        // This way deleting an association definition works for both cases: 1) interactive deletion (when the user
206        // deletes an association), and 2) programmatical deletion (e.g. from a migration).
207        getAssocDef(childTypeUri).delete();
208        return this;
209    }
210
211    // --- Label Configuration ---
212
213    @Override
214    public List<String> getLabelConfig() {
215        return getModel().getLabelConfig();
216    }
217
218    @Override
219    public void setLabelConfig(List<String> labelConfig) {
220        // update memory
221        getModel().setLabelConfig(labelConfig);
222        // update DB
223        dms.typeStorage.updateLabelConfig(labelConfig, getAssocDefs());
224    }
225
226    // --- View Configuration ---
227
228    @Override
229    public ViewConfiguration getViewConfig() {
230        return viewConfig;
231    }
232
233    // FIXME: to be dropped
234    @Override
235    public Object getViewConfig(String typeUri, String settingUri) {
236        return getModel().getViewConfig(typeUri, settingUri);
237    }
238
239    // ---
240
241    @Override
242    public TypeModel getModel() {
243        return (TypeModel) super.getModel();
244    }
245
246
247
248    // ----------------------------------------------------------------------------------------- Package Private Methods
249
250    abstract void putInTypeCache();
251
252    abstract void removeFromTypeCache();
253
254    // ---
255
256    abstract Directive getDeleteTypeDirective();
257
258    abstract List<? extends DeepaMehtaObject> getAllInstances();
259
260    // ---
261
262    void removeAssocDefFromMemoryAndRebuildSequence(String childTypeUri) {
263        // update memory
264        getModel().removeAssocDef(childTypeUri);    // update model
265        _removeAssocDef(childTypeUri);              // update attached object cache
266        // update DB
267        dms.typeStorage.rebuildSequence(this);
268    }
269
270    // ------------------------------------------------------------------------------------------------- Private Methods
271
272
273
274    // === Update ===
275
276    private boolean hasUriChanged(String newUri) {
277        return newUri != null && !getUri().equals(newUri);
278    }
279
280    // ---
281
282    private void updateDataTypeUri(String newDataTypeUri) {
283        if (newDataTypeUri != null) {
284            String dataTypeUri = getDataTypeUri();
285            if (!dataTypeUri.equals(newDataTypeUri)) {
286                logger.info("### Changing data type URI from \"" + dataTypeUri + "\" -> \"" + newDataTypeUri + "\"");
287                setDataTypeUri(newDataTypeUri);
288            }
289        }
290    }
291
292    private void storeDataTypeUri(String dataTypeUri) {
293        // remove current assignment
294        getRelatedTopic("dm4.core.aggregation", "dm4.core.type", "dm4.core.default", "dm4.core.data_type")
295            .getRelatingAssociation().delete();
296        // create new assignment
297        dms.typeStorage.storeDataType(getUri(), dataTypeUri);
298    }
299
300    // ---
301
302    private void indexAllInstances(IndexMode indexMode) {
303        List<? extends DeepaMehtaObject> objects = getAllInstances();
304        //
305        String str = "\"" + getSimpleValue() + "\" (" + getUri() + ") instances";
306        if (getIndexModes().size() > 0) {
307            if (objects.size() > 0) {
308                logger.info("### Indexing " + objects.size() + " " + str + " (indexMode=" + indexMode + ")");
309            } else {
310                logger.info("### Indexing " + str + " ABORTED -- no instances in DB");
311            }
312        } else {
313            logger.info("### Indexing " + str + " ABORTED -- no index mode set");
314        }
315        //
316        for (DeepaMehtaObject obj : objects) {
317            dms.valueStorage.indexSimpleValue(obj.getModel(), indexMode);
318        }
319    }
320
321    // ---
322
323    private void updateAssocDefs(Collection<AssociationDefinitionModel> newAssocDefs) {
324        for (AssociationDefinitionModel assocDef : newAssocDefs) {
325            getAssocDef(assocDef.getChildTypeUri()).update(assocDef);
326        }
327    }
328
329    // ---
330
331    private void updateSequence(Collection<AssociationDefinitionModel> newAssocDefs) {
332        if (!hasSequenceChanged(newAssocDefs)) {
333            return;
334        }
335        logger.info("### Changing assoc def sequence");
336        // update memory
337        getModel().removeAllAssocDefs();
338        for (AssociationDefinitionModel assocDef : newAssocDefs) {
339            getModel().addAssocDef(assocDef);
340        }
341        initAssocDefs();    // attached object cache
342        // update DB
343        dms.typeStorage.rebuildSequence(this);
344    }
345
346    private boolean hasSequenceChanged(Collection<AssociationDefinitionModel> newAssocDefs) {
347        Collection<AssociationDefinition> assocDefs = getAssocDefs();
348        if (assocDefs.size() != newAssocDefs.size()) {
349            throw new RuntimeException("adding/removing of assoc defs not yet supported via updateTopicType() call");
350        }
351        //
352        Iterator<AssociationDefinitionModel> i = newAssocDefs.iterator();
353        for (AssociationDefinition assocDef : assocDefs) {
354            AssociationDefinitionModel newAssocDef = i.next();
355            if (!assocDef.getChildTypeUri().equals(newAssocDef.getChildTypeUri())) {
356                return true;
357            }
358        }
359        //
360        return false;
361    }
362
363    // ---
364
365    private void updateLabelConfig(List<String> newLabelConfig) {
366        if (!getLabelConfig().equals(newLabelConfig)) {
367            logger.info("### Changing label configuration");
368            setLabelConfig(newLabelConfig);
369        }
370    }
371
372
373
374    // === Helper ===
375
376    /**
377     * Returns the last association definition of this type or
378     * <code>null</code> if there are no association definitions.
379     *
380     * ### TODO: move to class TypeModel?
381     */
382    private AssociationDefinitionModel lastAssocDef() {
383        AssociationDefinitionModel lastAssocDef = null;
384        for (AssociationDefinitionModel assocDef : getModel().getAssocDefs()) {
385            lastAssocDef = assocDef;
386        }
387        return lastAssocDef;
388    }
389
390    private AssociationDefinitionModel firstAssocDef() {
391        return getModel().getAssocDefs().iterator().next();
392    }
393
394    // --- Attached Object Cache ---
395
396    // ### FIXME: make it private
397    protected void initAssocDefs() {
398        this.assocDefs = new SequencedHashMap();
399        for (AssociationDefinitionModel model : getModel().getAssocDefs()) {
400            _addAssocDef(model);
401        }
402    }
403
404    private void _addAssocDef(AssociationDefinitionModel model) {
405        _addAssocDefBefore(model, null);    // beforeChildTypeUri=null
406    }
407
408    /**
409     * @param   beforeChildTypeUri  the assoc def <i>before</i> the assoc def is inserted into the sequence.
410     *                              If <code>null</code> the assoc def is appended at the end.
411     */
412    private void _addAssocDefBefore(AssociationDefinitionModel model, String beforeChildTypeUri) {
413        assocDefs.putBefore(model.getChildTypeUri(), new AttachedAssociationDefinition(model, dms), beforeChildTypeUri);
414    }
415
416    private void _removeAssocDef(String childTypeUri) {
417        // error check
418        getAssocDef(childTypeUri);
419        //
420        assocDefs.remove(childTypeUri);
421    }
422
423    // ---
424
425    private void initViewConfig() {
426        RoleModel configurable = dms.typeStorage.createConfigurableType(getId());   // ### type ID is uninitialized
427        this.viewConfig = new AttachedViewConfiguration(configurable, getModel().getViewConfigModel(), dms);
428    }
429
430
431
432    // ===
433
434    /**
435     * Removes this type from type cache and adds a DELETE TYPE directive to the given set of directives.
436     */
437    private void _removeFromTypeCache() {
438        removeFromTypeCache();                      // abstract
439        addDeleteTypeDirective();
440    }
441
442    private void addDeleteTypeDirective() {
443        Directive dir = getDeleteTypeDirective();   // abstract
444        Directives.get().add(dir, new JSONWrapper("uri", getUri()));
445    }
446
447    // ------------------------------------------------------------------------------------------------- Private Classes
448
449    private class JSONWrapper implements JSONEnabled {
450
451        private JSONObject wrapped;
452
453        private JSONWrapper(String key, Object value) {
454            try {
455                wrapped = new JSONObject();
456                wrapped.put(key, value);
457            } catch (Exception e) {
458                throw new RuntimeException("Constructing a JSONWrapper failed", e);
459            }
460        }
461
462        @Override
463        public JSONObject toJSON() {
464            return wrapped;
465        }
466    }
467}