001package de.deepamehta.core.impl;
002
003import de.deepamehta.core.JSONEnabled;
004import de.deepamehta.core.model.AssociationDefinitionModel;
005import de.deepamehta.core.model.AssociationModel;
006import de.deepamehta.core.model.DeepaMehtaObjectModel;
007import de.deepamehta.core.model.IndexMode;
008import de.deepamehta.core.model.TypeModel;
009import de.deepamehta.core.model.ViewConfigurationModel;
010import de.deepamehta.core.service.Directive;
011import de.deepamehta.core.service.Directives;
012import de.deepamehta.core.util.SequencedHashMap;
013
014import org.codehaus.jettison.json.JSONArray;
015import org.codehaus.jettison.json.JSONObject;
016
017import java.util.ArrayList;
018import java.util.Collection;
019import java.util.Iterator;
020import java.util.List;
021import java.util.logging.Logger;
022
023
024
025class TypeModelImpl extends TopicModelImpl implements TypeModel {
026
027    // ---------------------------------------------------------------------------------------------- Instance Variables
028
029    private String dataTypeUri;                         // may be null in models used for an update operation
030    private List<IndexMode> indexModes;
031    private SequencedHashMap<String, AssociationDefinitionModelImpl> assocDefs; // is never null, may be empty
032    private ViewConfigurationModelImpl viewConfig;                              // is never null
033
034    private Logger logger = Logger.getLogger(getClass().getName());
035
036    // ---------------------------------------------------------------------------------------------------- Constructors
037
038    TypeModelImpl(TopicModelImpl typeTopic, String dataTypeUri, List<IndexMode> indexModes,
039                  List<AssociationDefinitionModel> assocDefs, ViewConfigurationModelImpl viewConfig) {
040        super(typeTopic);
041        this.dataTypeUri = dataTypeUri;
042        this.indexModes  = indexModes;
043        this.assocDefs   = toMap(assocDefs);
044        this.viewConfig  = viewConfig;
045    }
046
047    TypeModelImpl(TypeModelImpl type) {
048        super(type);
049        this.dataTypeUri = type.getDataTypeUri();
050        this.indexModes  = type.getIndexModes();
051        this.assocDefs   = toMap(type.getAssocDefs());
052        this.viewConfig  = type.getViewConfig();
053    }
054
055    // -------------------------------------------------------------------------------------------------- Public Methods
056
057
058
059    // === Data Type ===
060
061    @Override
062    public String getDataTypeUri() {
063        return dataTypeUri;
064    }
065
066    @Override
067    public void setDataTypeUri(String dataTypeUri) {
068        this.dataTypeUri = dataTypeUri;
069    }
070
071
072
073    // === Index Modes ===
074
075    @Override
076    public List<IndexMode> getIndexModes() {
077        return indexModes;
078    }
079
080    @Override
081    public void addIndexMode(IndexMode indexMode) {
082        indexModes.add(indexMode);
083    }
084
085
086
087    // === Association Definitions ===
088
089    @Override
090    public Collection<AssociationDefinitionModelImpl> getAssocDefs() {
091        return assocDefs.values();
092    }
093
094    @Override
095    public AssociationDefinitionModelImpl getAssocDef(String assocDefUri) {
096        return getAssocDefOrThrow(assocDefUri);
097    }
098
099    @Override
100    public boolean hasAssocDef(String assocDefUri) {
101        return _getAssocDef(assocDefUri) != null;
102    }
103
104    /**
105     * @param   assocDef    the assoc def to add.
106     *                      Note: its ID might be uninitialized (-1).
107     */
108    @Override
109    public TypeModel addAssocDef(AssociationDefinitionModel assocDef) {
110        return addAssocDefBefore(assocDef, null);   // beforeAssocDefUri=null
111    }
112
113    /**
114     * @param   assocDef            the assoc def to add.
115     *                              Note: its ID might be uninitialized (-1).
116     * @param   beforeAssocDefUri   the URI of the assoc def <i>before</i> the given assoc def is inserted.
117     *                              If <code>null</code> the assoc def is appended at the end.
118     */
119    @Override
120    public TypeModel addAssocDefBefore(AssociationDefinitionModel assocDef, String beforeAssocDefUri) {
121        try {
122            // error check
123            String assocDefUri = assocDef.getAssocDefUri();
124            AssociationDefinitionModel existing = _getAssocDef(assocDefUri);
125            if (existing != null) {
126                throw new RuntimeException("Ambiguity: type \"" + uri + "\" has more than one \"" + assocDefUri +
127                    "\" assoc defs");
128            }
129            //
130            assocDefs.putBefore(assocDefUri, (AssociationDefinitionModelImpl) assocDef, beforeAssocDefUri);
131            return this;
132        } catch (Exception e) {
133            throw new RuntimeException("Adding an assoc def to type \"" + uri + "\" before \"" + beforeAssocDefUri +
134                "\" failed" + assocDef, e);
135        }
136    }
137
138    @Override
139    public AssociationDefinitionModel removeAssocDef(String assocDefUri) {
140        try {
141            AssociationDefinitionModel assocDef = assocDefs.remove(assocDefUri);
142            if (assocDef == null) {
143                throw new RuntimeException("Assoc def \"" + assocDefUri + "\" not found in " + assocDefs.keySet());
144            }
145            return assocDef;
146        } catch (Exception e) {
147            throw new RuntimeException("Removing assoc def \"" + assocDefUri + "\" from type \"" + uri + "\" failed",
148                e);
149        }
150    }
151
152
153
154    // === View Configuration ===
155
156    @Override
157    public ViewConfigurationModelImpl getViewConfig() {
158        return viewConfig;
159    }
160
161    @Override
162    public Object getViewConfigValue(String configTypeUri, String childTypeUri) {
163        return viewConfig.getConfigValue(configTypeUri, childTypeUri);
164    }
165
166    @Override
167    public void setViewConfig(ViewConfigurationModel viewConfig) {
168        this.viewConfig = (ViewConfigurationModelImpl) viewConfig;
169    }
170
171
172
173    // === Iterable Implementation ===
174
175    /**
176     * Returns an interator which iterates this TypeModel's assoc def URIs.
177     */
178    @Override
179    public Iterator<String> iterator() {
180        return assocDefs.keySet().iterator();
181    }
182
183
184
185    // ****************************
186    // *** TopicModel Overrides ***
187    // ****************************
188
189
190
191    @Override
192    public JSONObject toJSON() {
193        try {
194            return super.toJSON()
195                .put("data_type_uri", getDataTypeUri())
196                .put("index_mode_uris", toJSONArray(indexModes))
197                .put("assoc_defs", toJSONArray(assocDefs.values()))
198                .put("view_config_topics", getViewConfig().toJSONArray());
199        } catch (Exception e) {
200            throw new RuntimeException("Serialization failed (" + this + ")", e);
201        }
202    }
203
204
205
206    // ****************
207    // *** Java API ***
208    // ****************
209
210
211
212    @Override
213    public TypeModelImpl clone() {
214        try {
215            TypeModelImpl model = (TypeModelImpl) super.clone();
216            model.assocDefs = (SequencedHashMap) model.assocDefs.clone();
217            return model;
218        } catch (Exception e) {
219            throw new RuntimeException("Cloning a TypeModel failed", e);
220        }
221    }
222
223    @Override
224    public String toString() {
225        return "id=" + id + ", uri=\"" + uri + "\", value=\"" + value + "\", typeUri=\"" + typeUri +
226            "\", dataTypeUri=\"" + getDataTypeUri() + "\", indexModes=" + getIndexModes() + ", assocDefs=" +
227            getAssocDefs() + ", " + getViewConfig();
228    }
229
230    // ----------------------------------------------------------------------------------------- Package Private Methods
231
232
233
234    // === Abstract Methods ===
235
236    DeepaMehtaTypeImpl instantiate() {
237        throw new UnsupportedOperationException();
238    }
239
240    List<? extends DeepaMehtaObjectModelImpl> getAllInstances() {
241        throw new UnsupportedOperationException();
242    }
243
244    // ---
245
246    Directive getUpdateTypeDirective() {
247        throw new UnsupportedOperationException();
248    }
249
250    Directive getDeleteTypeDirective() {
251        throw new UnsupportedOperationException();
252    }
253
254
255
256    // === Core Internal Hooks ===
257
258    @Override
259    void preUpdate(DeepaMehtaObjectModel updateModel) {
260        // ### TODO: is it sufficient if we rehash (remove + add) at post-time?
261        if (uriChange(updateModel.getUri(), uri)) {
262            removeFromTypeCache();
263        }
264    }
265
266    @Override
267    void postUpdate(DeepaMehtaObjectModel updateModel, DeepaMehtaObjectModel oldObject) {
268        if (uriChange(updateModel.getUri(), oldObject.getUri())) {
269            putInTypeCache();
270        }
271        //
272        updateType((TypeModelImpl) updateModel);
273        //
274        // Note: the UPDATE_TOPIC_TYPE/UPDATE_ASSOCIATION_TYPE directive must be added *before* a possible UPDATE_TOPIC
275        // directive (added by DeepaMehtaObjectModelImpl.update()). In case of a changed type URI the webclient's type
276        // cache must be updated *before* the TopicTypeRenderer/AssociationTypeRenderer can render the type.
277        addUpdateTypeDirective();
278    }
279
280    // ---
281
282    @Override
283    void preDelete() {
284        // 1) check pre-condition
285        int size = getAllInstances().size();
286        if (size > 0) {
287            throw new RuntimeException(size + " \"" + value + "\" instances still exist");
288        }
289        // 2) delete all assoc defs
290        //
291        // Note 1: we use the preDelete() hook to delete the assoc defs *before* they would get deleted by the generic
292        // deletion logic (see "delete direct associations" in DeepaMehtaObjectModelImpl.delete()). The generic deletion
293        // logic deletes the direct associations in an *arbitrary* order. The "Sequence Start" association might get
294        // deleted *before* any other assoc def. When subsequently deleting an assoc def the sequence can't be rebuild
295        // as it is corrupted.
296        // Note 2: iterating with a for-loop here would cause ConcurrentModificationException. Deleting an assoc def
297        // implies rebuilding the sequence and that iterates with a for-loop already. Instead we must create a new
298        // iterator for every single assoc def.
299        String assocDefUri;
300        while ((assocDefUri = getFirstAssocDefUri()) != null) {
301            _getAssocDef(assocDefUri).delete();
302        }
303    }
304
305    @Override
306    void postDelete() {
307        super.postDelete();                     // ### TODO: needed?
308        //
309        removeFromTypeCache();
310    }
311
312
313
314    // === Update (memory + DB) ===
315
316    final void updateDataTypeUri(String dataTypeUri) {
317        setDataTypeUri(dataTypeUri);    // update memory
318        storeDataTypeUri();             // update DB
319    }
320
321    final void _addIndexMode(IndexMode indexMode) {
322        // update memory
323        addIndexMode(indexMode);
324        // update DB
325        pl.typeStorage.storeIndexMode(uri, indexMode);
326        indexAllInstances(indexMode);
327    }
328
329    // ---
330
331    final void _addAssocDefBefore(AssociationDefinitionModelImpl assocDef, String beforeAssocDefUri) {
332        try {
333            long lastAssocDefId = lastAssocDefId();     // must be determined *before* memory is updated
334            //
335            // 1) update memory
336            // Note: the assoc def's custom association type is stored as a child topic. The meta model extension that
337            // adds "Association Type" as a child to the "Composition Definition" and "Aggregation Definition"
338            // association types has itself a custom association type (named "Custom Association Type"), see migration
339            // 5. It would not be stored as storage is model driven and the (meta) model doesn't know about custom
340            // associations as this very concept is introduced only by the assoc def being added here. So, the model
341            // must be updated (in-memory) *before* the assoc def is stored.
342            addAssocDefBefore(assocDef, beforeAssocDefUri);
343            //
344            // 2) update DB
345            pl.typeStorage.storeAssociationDefinition(assocDef);
346            long beforeAssocDefId = beforeAssocDefUri != null ? getAssocDef(beforeAssocDefUri).getId() : -1;
347            long firstAssocDefId = firstAssocDefId();   // must be determined *after* memory is updated
348            pl.typeStorage.addAssocDefToSequence(getId(), assocDef.getId(), beforeAssocDefId, firstAssocDefId,
349                lastAssocDefId);
350        } catch (Exception e) {
351            throw new RuntimeException("Adding an assoc def to type \"" + uri + "\" before \"" + beforeAssocDefUri +
352                "\" failed" + assocDef, e);
353        }
354    }
355
356    final void _removeAssocDef(String assocDefUri) {
357        // We trigger deleting an association definition by deleting the underlying association. This mimics deleting an
358        // association definition interactively in the webclient. Updating this type definition's memory and DB sequence
359        // is triggered then by the Type Editor plugin's preDeleteAssociation() hook. ### FIXDOC
360        // This way deleting an association definition works for both cases: 1) interactive deletion (when the user
361        // deletes an association), and 2) programmatical deletion (e.g. from a migration).
362        getAssocDef(assocDefUri).delete();
363    }
364
365
366
367    // === Type Editor Support ===
368
369    final void _addAssocDef(AssociationModel assoc) {
370        _addAssocDefBefore(pl.typeStorage.newAssociationDefinition(assoc), null);    // beforeAssocDefUri=null
371        //
372        addUpdateTypeDirective();
373    }
374
375    /**
376     * Adjusts the type cache on post-update-assoc.
377     * Called from ApplicationModel's core internal postUpdate() hook, in 2 situations:
378     *   - An assoc def has been updated.
379     *   - An assoc def's underlying assoc has been updated.
380     *
381     * @param   assoc       the updated generic association. ### FIXDOC: might be an assoc def as well
382     */
383    final void _updateAssocDef(AssociationModel assoc, AssociationModel oldAssoc) {
384        String[] assocDefUris = findAssocDefUris(assoc.getId());
385        AssociationDefinitionModel assocDef = getAssocDef(assocDefUris[0]);
386        String oldAssocDefUri;
387        if (assoc == assocDef) {
388            // updated "via assoc def"
389            oldAssocDefUri = ((AssociationDefinitionModel) oldAssoc).getAssocDefUri();
390        } else {
391            // updated "via assoc"
392            oldAssocDefUri = assocDef.getAssocDefUri();
393            // transfer assoc model to assoc def model
394            assocDef.setTypeUri(assoc.getTypeUri());
395            assocDef.setSimpleValue(assoc.getSimpleValue());
396            assocDef.setChildTopicsModel(assoc.getChildTopicsModel());
397        }
398        // rehash if assoc def URI has changed (due to changed custom assoc type)
399        if (!assocDef.getAssocDefUri().equals(oldAssocDefUri)) {
400            rehashAssocDef(oldAssocDefUri, assocDefUris[1]);
401        }
402        //
403        addUpdateTypeDirective();
404    }
405
406    /**
407     * Removes an association from memory and rebuilds the sequence in DB. Note: the underlying
408     * association is *not* removed from DB.
409     * This method is called (by the Type Editor plugin's preDeleteAssociation() hook) when the
410     * deletion of an association that represents an association definition is imminent. ### FIXDOC
411     */
412    final void _removeAssocDefFromMemoryAndRebuildSequence(AssociationModel assoc) {
413        String[] assocDefUris = findAssocDefUris(assoc.getId());
414        String assocDefUri = getAssocDef(assocDefUris[0]).getAssocDefUri();
415        // update memory
416        removeAssocDef(assocDefUri);
417        // update DB
418        pl.typeStorage.rebuildSequence(this);
419        //
420        addUpdateTypeDirective();
421    }
422
423
424
425    // === Label Configuration ===
426
427    final List<String> getLabelConfig() {
428        try {
429            List<String> labelConfig = new ArrayList();
430            for (String assocDefUri : this) {
431                if (getAssocDef(assocDefUri).includeInLabel()) {
432                    labelConfig.add(assocDefUri);
433                }
434            }
435            return labelConfig;
436        } catch (Exception e) {
437            throw new RuntimeException("Calculating the label configuration for type \"" + uri + "\" failed", e);
438        }
439    }
440
441
442
443    // === Access Control ===
444
445    final <M extends TypeModelImpl> M filterReadableAssocDefs() {
446        try {
447            Iterator<String> i = iterator();
448            while (i.hasNext()) {
449                String assocDefUri = i.next();
450                if (!_getAssocDef(assocDefUri).isReadable()) {
451                    i.remove();
452                }
453            }
454            return (M) this;
455        } catch (Exception e) {
456            throw new RuntimeException("Filtering readable assoc defs of type \"" + uri + "\" failed", e);
457        }
458    }
459
460
461
462    // ------------------------------------------------------------------------------------------------- Private Methods
463
464    private void addUpdateTypeDirective() {
465        Directives.get().add(getUpdateTypeDirective(), instantiate());
466    }
467
468
469
470    // === Update (memory + DB) ===
471
472    private void updateType(TypeModelImpl updateModel) {
473        _updateDataTypeUri(updateModel.getDataTypeUri());
474        _updateAssocDefs(updateModel.getAssocDefs());
475        _updateSequence(updateModel.getAssocDefs());
476    }
477
478    // ---
479
480    private void _updateDataTypeUri(String newDataTypeUri) {
481        if (newDataTypeUri != null) {
482            String dataTypeUri = getDataTypeUri();
483            if (!dataTypeUri.equals(newDataTypeUri)) {
484                logger.info("### Changing data type URI from \"" + dataTypeUri + "\" -> \"" + newDataTypeUri + "\"");
485                updateDataTypeUri(newDataTypeUri);
486            }
487        }
488    }
489
490    private void _updateAssocDefs(Collection<AssociationDefinitionModelImpl> newAssocDefs) {
491        for (AssociationDefinitionModelImpl assocDef : newAssocDefs) {
492            // Note: if the assoc def's custom association type was changed the assoc def URI changes as well.
493            // So we must identify the assoc def to update **by ID**.
494            // ### TODO: drop updateAssocDef() and rehash here (that is remove + add).
495            String[] assocDefUris = findAssocDefUris(assocDef.getId());
496            getAssocDef(assocDefUris[0]).update(assocDef);
497        }
498    }
499
500    private void _updateSequence(Collection<AssociationDefinitionModelImpl> newAssocDefs) {
501        try {
502            // ### TODO: only update sequence if there is a change request
503            //
504            // update memory
505            logger.info("##### Updating sequence (" + newAssocDefs.size() + "/" + getAssocDefs().size() +
506                " assoc defs)");
507            rehashAssocDefs(newAssocDefs);
508            // update DB
509            pl.typeStorage.rebuildSequence(this);
510        } catch (Exception e) {
511            throw new RuntimeException("Updating the assoc def sequence failed", e);
512        }
513    }
514
515
516
517    // === Store (DB only) ===
518
519    private void storeDataTypeUri() {
520        // remove current assignment
521        getRelatedTopic("dm4.core.aggregation", "dm4.core.type", "dm4.core.default", "dm4.core.data_type")
522            .getRelatingAssociation().delete();
523        // create new assignment
524        pl.typeStorage.storeDataType(uri, dataTypeUri);
525    }
526
527    private void indexAllInstances(IndexMode indexMode) {
528        List<? extends DeepaMehtaObjectModelImpl> objects = getAllInstances();
529        //
530        String str = "\"" + value + "\" (" + uri + ") instances";
531        if (indexModes.size() > 0) {
532            if (objects.size() > 0) {
533                logger.info("### Indexing " + objects.size() + " " + str + " (indexMode=" + indexMode + ")");
534            } else {
535                logger.info("### Indexing " + str + " ABORTED -- no instances in DB");
536            }
537        } else {
538            logger.info("### Indexing " + str + " ABORTED -- no index mode set");
539        }
540        //
541        for (DeepaMehtaObjectModelImpl obj : objects) {
542            obj.indexSimpleValue(indexMode);
543        }
544    }
545
546
547
548    // === Association Definitions (memory access) ===
549
550    /**
551     * Finds an assoc def by ID and returns its URI (at index 0). Returns the URI of the next-in-sequence
552     * assoc def as well (at index 1), or null if the found assoc def is the last one.
553     */
554    private String[] findAssocDefUris(long assocDefId) {
555        if (assocDefId == -1) {
556            throw new IllegalArgumentException("findAssocDefUris() called with assocDefId=-1");
557        }
558        String[] assocDefUris = new String[2];
559        Iterator<String> i = iterator();
560        while (i.hasNext()) {
561            String assocDefUri = i.next();
562            long _assocDefId = checkAssocDefId(_getAssocDef(assocDefUri));
563            if (_assocDefId == assocDefId) {
564                assocDefUris[0] = assocDefUri;
565                if (i.hasNext()) {
566                    assocDefUris[1] = i.next();
567                }
568                break;
569            }
570        }
571        if (assocDefUris[0] == null) {
572            throw new RuntimeException("Assoc def with ID " + assocDefId + " not found in assoc defs of type \"" + uri +
573                "\" (" + assocDefs.keySet() + ")");
574        }
575        return assocDefUris;
576    }
577
578    // ### TODO: not called
579    private boolean hasSameAssocDefSequence(Collection<? extends AssociationDefinitionModel> assocDefs) {
580        Collection<? extends AssociationDefinitionModel> _assocDefs = getAssocDefs();
581        if (assocDefs.size() != _assocDefs.size()) {
582            return false;
583        }
584        //
585        Iterator<? extends AssociationDefinitionModel> i = assocDefs.iterator();
586        for (AssociationDefinitionModel _assocDef : _assocDefs) {
587            AssociationDefinitionModel assocDef = i.next();
588            // Note: if the assoc def's custom association type changed the assoc def URI changes as well.
589            // So we must identify the assoc defs to compare **by ID**.
590            long assocDefId  = checkAssocDefId(assocDef);
591            long _assocDefId = checkAssocDefId(_assocDef);
592            if (assocDefId != _assocDefId) {
593                return false;
594            }
595        }
596        //
597        return true;
598    }
599
600    // ---
601
602    private void rehashAssocDefs(Collection<AssociationDefinitionModelImpl> newAssocDefs) {
603        for (AssociationDefinitionModel assocDef : newAssocDefs) {
604            rehashAssocDef(assocDef.getAssocDefUri(), null);
605        }
606    }
607
608    private void rehashAssocDef(String assocDefUri, String beforeAssocDefUri) {
609        AssociationDefinitionModel assocDef = removeAssocDef(assocDefUri);
610        logger.info("### Rehashing assoc def \"" + assocDefUri + "\" -> \"" + assocDef.getAssocDefUri() +
611            "\" (put " + (beforeAssocDefUri != null ? "before \"" + beforeAssocDefUri + "\"" : "at end") + ")");
612        addAssocDefBefore(assocDef, beforeAssocDefUri);
613    }
614
615    // ---
616
617    private AssociationDefinitionModelImpl getAssocDefOrThrow(String assocDefUri) {
618        AssociationDefinitionModelImpl assocDef = _getAssocDef(assocDefUri);
619        if (assocDef == null) {
620            throw new RuntimeException("Assoc def \"" + assocDefUri + "\" not found in " + assocDefs.keySet());
621        }
622        return assocDef;
623    }
624
625    private AssociationDefinitionModelImpl _getAssocDef(String assocDefUri) {
626        return assocDefs.get(assocDefUri);
627    }
628
629    // ---
630
631    /**
632     * Returns the ID of the last association definition of this type or
633     * <code>-1</code> if there are no association definitions.
634     */
635    private long lastAssocDefId() {
636        long lastAssocDefId = -1;
637        for (AssociationDefinitionModel assocDef : getAssocDefs()) {
638            lastAssocDefId = assocDef.getId();
639        }
640        return lastAssocDefId;
641    }
642
643    private long firstAssocDefId() {
644        return getAssocDefs().iterator().next().getId();
645    }
646
647    private String getFirstAssocDefUri() {
648        Iterator<String> i = iterator();
649        return i.hasNext() ? i.next() : null;
650    }
651
652    // ---
653
654    private long checkAssocDefId(AssociationDefinitionModel assocDef) {
655        long assocDefId = assocDef.getId();
656        if (assocDefId == -1) {
657            throw new RuntimeException("The assoc def ID is uninitialized (-1): " + assocDef);
658        }
659        return assocDefId;
660    }
661
662    // ---
663
664    private SequencedHashMap<String, AssociationDefinitionModelImpl> toMap(
665                                                           Collection<? extends AssociationDefinitionModel> assocDefs) {
666        SequencedHashMap<String, AssociationDefinitionModelImpl> _assocDefs = new SequencedHashMap();
667        for (AssociationDefinitionModel assocDef : assocDefs) {
668            _assocDefs.put(assocDef.getAssocDefUri(), (AssociationDefinitionModelImpl) assocDef);
669        }
670        return _assocDefs;
671    }
672
673
674
675    // === Type Cache (memory access) ===
676
677    private void putInTypeCache() {
678        pl.typeStorage.putInTypeCache(this);
679    }
680
681    /**
682     * Removes this type from type cache and adds a DELETE TYPE directive to the given set of directives.
683     */
684    private void removeFromTypeCache() {
685        pl.typeStorage.removeFromTypeCache(uri);
686        //
687        Directive dir = getDeleteTypeDirective();   // abstract
688        Directives.get().add(dir, new JSONWrapper("uri", uri));
689    }
690
691
692
693    // === Serialization ===
694
695    private JSONArray toJSONArray(List<IndexMode> indexModes) {
696        JSONArray indexModeUris = new JSONArray();
697        for (IndexMode indexMode : indexModes) {
698            indexModeUris.put(indexMode.toUri());
699        }
700        return indexModeUris;
701    }
702
703    private JSONArray toJSONArray(Collection<? extends AssociationDefinitionModel> assocDefs) {
704        JSONArray _assocDefs = new JSONArray();
705        for (AssociationDefinitionModel assocDef : assocDefs) {
706            _assocDefs.put(assocDef.toJSON());
707        }
708        return _assocDefs;
709    }
710
711
712
713    // ------------------------------------------------------------------------------------------------- Private Classes
714
715    private static final class JSONWrapper implements JSONEnabled {
716
717        private JSONObject wrapped;
718
719        private JSONWrapper(String key, Object value) {
720            try {
721                wrapped = new JSONObject();
722                wrapped.put(key, value);
723            } catch (Exception e) {
724                throw new RuntimeException("Constructing a JSONWrapper failed", e);
725            }
726        }
727
728        @Override
729        public JSONObject toJSON() {
730            return wrapped;
731        }
732    }
733}