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