001    package de.deepamehta.core.impl;
002    
003    import de.deepamehta.core.AssociationDefinition;
004    import de.deepamehta.core.JSONEnabled;
005    import de.deepamehta.core.Type;
006    import de.deepamehta.core.ViewConfiguration;
007    import de.deepamehta.core.model.AssociationDefinitionModel;
008    import de.deepamehta.core.model.IndexMode;
009    import de.deepamehta.core.model.RoleModel;
010    import de.deepamehta.core.model.TypeModel;
011    import de.deepamehta.core.service.ClientState;
012    import de.deepamehta.core.service.Directives;
013    
014    import org.codehaus.jettison.json.JSONObject;
015    
016    import java.util.Collection;
017    import java.util.Iterator;
018    import java.util.LinkedHashMap;
019    import java.util.List;
020    import java.util.Map;
021    import java.util.logging.Logger;
022    
023    
024    
025    abstract class AttachedType extends AttachedTopic implements Type {
026    
027        // ---------------------------------------------------------------------------------------------- Instance Variables
028    
029        private Map<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        // *** Type Implementation ***
049        // ***************************
050    
051    
052    
053        // === Model ===
054    
055        // --- Data Type ---
056    
057        @Override
058        public String getDataTypeUri() {
059            return getModel().getDataTypeUri();
060        }
061    
062        @Override
063        public void setDataTypeUri(String dataTypeUri, Directives directives) {
064            // update memory
065            getModel().setDataTypeUri(dataTypeUri);
066            // update DB
067            _updateDataTypeUri(dataTypeUri, directives);
068        }
069    
070        // --- Index Modes ---
071    
072        @Override
073        public List<IndexMode> getIndexModes() {
074            return getModel().getIndexModes();
075        }
076    
077        @Override
078        public void setIndexModes(List<IndexMode> indexModes) {
079            // update memory
080            getModel().setIndexModes(indexModes);
081            // update DB
082            dms.typeStorage.storeIndexModes(getUri(), indexModes);
083        }
084    
085        // --- Association Definitions ---
086    
087        @Override
088        public Collection<AssociationDefinition> getAssocDefs() {
089            return assocDefs.values();
090        }
091    
092        @Override
093        public AssociationDefinition getAssocDef(String childTypeUri) {
094            AssociationDefinition assocDef = assocDefs.get(childTypeUri);
095            if (assocDef == null) {
096                throw new RuntimeException("Schema violation: association definition \"" +
097                    childTypeUri + "\" not found in " + this);
098            }
099            return assocDef;
100        }
101    
102        @Override
103        public boolean hasAssocDef(String childTypeUri) {
104            return assocDefs.get(childTypeUri) != null;
105        }
106    
107        @Override
108        public void addAssocDef(AssociationDefinitionModel model) {
109            // Note: the predecessor must be determined *before* the memory is updated
110            AssociationDefinitionModel predecessor = lastAssocDef();
111            // update memory
112            getModel().addAssocDef(model);                                          // update model
113            _addAssocDef(model);                                                    // update attached object cache
114            // update DB
115            dms.typeStorage.storeAssociationDefinition(model);
116            dms.typeStorage.appendToSequence(getUri(), model, predecessor);
117        }
118    
119        @Override
120        public void updateAssocDef(AssociationDefinitionModel model) {
121            // update memory
122            getModel().updateAssocDef(model);                                       // update model
123            _addAssocDef(model);                                                    // update attached object cache
124            // update DB
125            // ### Note: the DB is not updated here! In case of interactive assoc type change the association is
126            // already updated in DB. => See interface comment.
127        }
128    
129        @Override
130        public void removeAssocDef(String childTypeUri) {
131            // update memory
132            getModel().removeAssocDef(childTypeUri);                                // update model
133            AttachedAssociationDefinition assocDef = _removeAssocDef(childTypeUri); // update attached object cache
134            // update DB
135            dms.typeStorage.rebuildSequence(this);
136        }
137    
138        // --- Label Configuration ---
139    
140        @Override
141        public List<String> getLabelConfig() {
142            return getModel().getLabelConfig();
143        }
144    
145        @Override
146        public void setLabelConfig(List<String> labelConfig, Directives directives) {
147            // update memory
148            getModel().setLabelConfig(labelConfig);
149            // update DB
150            dms.typeStorage.storeLabelConfig(labelConfig, getModel().getAssocDefs(), directives);
151        }
152    
153        // --- View Configuration ---
154    
155        @Override
156        public ViewConfiguration getViewConfig() {
157            return viewConfig;
158        }
159    
160        // FIXME: to be dropped
161        @Override
162        public Object getViewConfig(String typeUri, String settingUri) {
163            return getModel().getViewConfig(typeUri, settingUri);
164        }
165    
166        // ---
167    
168        @Override
169        public TypeModel getModel() {
170            return (TypeModel) super.getModel();
171        }
172    
173    
174    
175        // === Updating ===
176    
177        @Override
178        public void update(TypeModel model, ClientState clientState, Directives directives) {
179            boolean uriChanged = hasUriChanged(model.getUri());
180            if (uriChanged) {
181                removeFromTypeCache();                                                  // abstract
182                addDeleteTypeDirective(directives, new JSONWrapper("uri", getUri()));   // abstract
183            }
184            //
185            super.update(model, clientState, directives);
186            //
187            if (uriChanged) {
188                putInTypeCache();   // abstract
189            }
190            //
191            updateDataTypeUri(model.getDataTypeUri(), directives);
192            updateAssocDefs(model.getAssocDefs(), clientState, directives);
193            updateSequence(model.getAssocDefs());
194            updateLabelConfig(model.getLabelConfig(), directives);
195        }
196    
197        // ----------------------------------------------------------------------------------------- Package Private Methods
198    
199        abstract void putInTypeCache();
200    
201        abstract void removeFromTypeCache();
202    
203        // ---
204    
205        abstract void addDeleteTypeDirective(Directives directives, JSONEnabled arg);
206    
207        // ------------------------------------------------------------------------------------------------- Private Methods
208    
209    
210    
211        // === Update ===
212    
213        private boolean hasUriChanged(String newUri) {
214            return newUri != null && !getUri().equals(newUri);
215        }
216    
217        // ---
218    
219        private void updateDataTypeUri(String newDataTypeUri, Directives directives) {
220            if (newDataTypeUri != null) {
221                String dataTypeUri = getDataTypeUri();
222                if (!dataTypeUri.equals(newDataTypeUri)) {
223                    logger.info("### Changing data type URI from \"" + dataTypeUri + "\" -> \"" + newDataTypeUri + "\"");
224                    setDataTypeUri(newDataTypeUri, directives);
225                }
226            }
227        }
228    
229        private void _updateDataTypeUri(String dataTypeUri, Directives directives) {
230            // remove current assignment
231            getRelatedTopic("dm4.core.aggregation", "dm4.core.type", "dm4.core.default", "dm4.core.data_type",
232                false, false).getRelatingAssociation().delete(directives);
233            // create new assignment
234            dms.typeStorage.storeDataType(getUri(), dataTypeUri);
235        }
236    
237        // ---
238    
239        private void updateAssocDefs(Collection<AssociationDefinitionModel> newAssocDefs, ClientState clientState,
240                                                                                          Directives directives) {
241            for (AssociationDefinitionModel assocDef : newAssocDefs) {
242                getAssocDef(assocDef.getChildTypeUri()).update(assocDef, clientState, directives);
243            }
244        }
245    
246        // ---
247    
248        private void updateSequence(Collection<AssociationDefinitionModel> newAssocDefs) {
249            if (!hasSequenceChanged(newAssocDefs)) {
250                return;
251            }
252            logger.info("### Changing assoc def sequence");
253            // update memory
254            getModel().removeAllAssocDefs();
255            for (AssociationDefinitionModel assocDef : newAssocDefs) {
256                getModel().addAssocDef(assocDef);
257            }
258            initAssocDefs();    // attached object cache
259            // update DB
260            dms.typeStorage.rebuildSequence(this);
261        }
262    
263        private boolean hasSequenceChanged(Collection<AssociationDefinitionModel> newAssocDefs) {
264            Collection<AssociationDefinition> assocDefs = getAssocDefs();
265            if (assocDefs.size() != newAssocDefs.size()) {
266                throw new RuntimeException("adding/removing of assoc defs not yet supported via updateTopicType() call");
267            }
268            //
269            Iterator<AssociationDefinitionModel> i = newAssocDefs.iterator();
270            for (AssociationDefinition assocDef : assocDefs) {
271                AssociationDefinitionModel newAssocDef = i.next();
272                if (!assocDef.getChildTypeUri().equals(newAssocDef.getChildTypeUri())) {
273                    return true;
274                }
275            }
276            //
277            return false;
278        }
279    
280        // ---
281    
282        private void updateLabelConfig(List<String> newLabelConfig, Directives directives) {
283            if (!getLabelConfig().equals(newLabelConfig)) {
284                logger.info("### Changing label configuration");
285                setLabelConfig(newLabelConfig, directives);
286            }
287        }
288    
289    
290    
291        // === Helper ===
292    
293        /**
294         * Returns the last association definition of this type or
295         * <code>null</code> if there are no association definitions.
296         *
297         * ### TODO: move to class TypeModel?
298         */
299        private AssociationDefinitionModel lastAssocDef() {
300            AssociationDefinitionModel lastAssocDef = null;
301            for (AssociationDefinitionModel assocDef : getModel().getAssocDefs()) {
302                lastAssocDef = assocDef;
303            }
304            return lastAssocDef;
305        }
306    
307        // --- Attached Object Cache ---
308    
309        // ### FIXME: make it private
310        protected void initAssocDefs() {
311            this.assocDefs = new LinkedHashMap();
312            for (AssociationDefinitionModel model : getModel().getAssocDefs()) {
313                _addAssocDef(model);
314            }
315        }
316    
317        /**
318         * @param   model   the new association definition.
319         *                  Note: all fields must be initialized.
320         */
321        private void _addAssocDef(AssociationDefinitionModel model) {
322            AttachedAssociationDefinition assocDef = new AttachedAssociationDefinition(model, dms);
323            assocDefs.put(assocDef.getChildTypeUri(), assocDef);
324        }
325    
326        private AttachedAssociationDefinition _removeAssocDef(String childTypeUri) {
327            // error check
328            getAssocDef(childTypeUri);
329            //
330            return (AttachedAssociationDefinition) assocDefs.remove(childTypeUri);
331        }
332    
333        // ---
334    
335        private void initViewConfig() {
336            RoleModel configurable = dms.typeStorage.createConfigurableType(getId());   // ### type ID is uninitialized
337            this.viewConfig = new AttachedViewConfiguration(configurable, getModel().getViewConfigModel(), dms);
338        }
339    
340    
341    
342        // ------------------------------------------------------------------------------------------------- Private Classes
343    
344        private class JSONWrapper implements JSONEnabled {
345    
346            private JSONObject wrapped;
347    
348            private JSONWrapper(String key, Object value) {
349                try {
350                    wrapped = new JSONObject();
351                    wrapped.put(key, value);
352                } catch (Exception e) {
353                    throw new RuntimeException("Constructing a JSONWrapper failed", e);
354                }
355            }
356    
357            @Override
358            public JSONObject toJSON() {
359                return wrapped;
360            }
361        }
362    }