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