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