001    package de.deepamehta.core.impl;
002    
003    import de.deepamehta.core.Association;
004    import de.deepamehta.core.AssociationDefinition;
005    import de.deepamehta.core.DeepaMehtaObject;
006    import de.deepamehta.core.RelatedTopic;
007    import de.deepamehta.core.Topic;
008    import de.deepamehta.core.Type;
009    import de.deepamehta.core.model.ChildTopicsModel;
010    import de.deepamehta.core.model.DeepaMehtaObjectModel;
011    import de.deepamehta.core.model.RelatedTopicModel;
012    import de.deepamehta.core.model.SimpleValue;
013    import de.deepamehta.core.model.TopicModel;
014    import de.deepamehta.core.service.Directive;
015    import de.deepamehta.core.service.Directives;
016    import de.deepamehta.core.service.ResultList;
017    
018    import org.codehaus.jettison.json.JSONObject;
019    
020    import java.util.List;
021    import java.util.logging.Logger;
022    
023    
024    
025    /**
026     * A DeepaMehta object model that is attached to the DB.
027     *
028     * Method name conventions and semantics:
029     *  - getXX()           Reads from memory (model).
030     *  - setXX(arg)        Writes to memory (model) and DB. Elementary operation.
031     *  - updateXX(arg)     Compares arg with current value (model) and calls setXX() method(s) if required.
032     *                      Can be called with arg=null which indicates no update is requested.
033     *                      Typically returns nothing.
034     *  - fetchXX()         Fetches value from DB.              ### FIXDOC
035     *  - storeXX()         Stores current value (model) to DB. ### FIXDOC
036     */
037    abstract class AttachedDeepaMehtaObject implements DeepaMehtaObject {
038    
039        // ---------------------------------------------------------------------------------------------- Instance Variables
040    
041        private DeepaMehtaObjectModel model;        // underlying model
042    
043        private AttachedChildTopics childTopics;    // attached object cache
044    
045        protected final EmbeddedService dms;
046    
047        private Logger logger = Logger.getLogger(getClass().getName());
048    
049        // ---------------------------------------------------------------------------------------------------- Constructors
050    
051        AttachedDeepaMehtaObject(DeepaMehtaObjectModel model, EmbeddedService dms) {
052            this.model = model;
053            this.dms = dms;
054            this.childTopics = new AttachedChildTopics(model.getChildTopicsModel(), this, dms);
055        }
056    
057        // -------------------------------------------------------------------------------------------------- Public Methods
058    
059    
060    
061        // ***************************************
062        // *** DeepaMehtaObject Implementation ***
063        // ***************************************
064    
065    
066    
067        // === Model ===
068    
069        // --- ID ---
070    
071        @Override
072        public long getId() {
073            return model.getId();
074        }
075    
076        // --- URI ---
077    
078        @Override
079        public String getUri() {
080            return model.getUri();
081        }
082    
083        @Override
084        public void setUri(String uri) {
085            // update memory
086            model.setUri(uri);
087            // update DB
088            storeUri();         // abstract
089        }
090    
091        // --- Type URI ---
092    
093        @Override
094        public String getTypeUri() {
095            return model.getTypeUri();
096        }
097    
098        @Override
099        public void setTypeUri(String typeUri) {
100            // update memory
101            model.setTypeUri(typeUri);
102            // update DB
103            storeTypeUri();     // abstract
104        }
105    
106        // --- Simple Value ---
107    
108        @Override
109        public SimpleValue getSimpleValue() {
110            return model.getSimpleValue();
111        }
112    
113        // ---
114    
115        @Override
116        public void setSimpleValue(String value) {
117            setSimpleValue(new SimpleValue(value));
118        }
119    
120        @Override
121        public void setSimpleValue(int value) {
122            setSimpleValue(new SimpleValue(value));
123        }
124    
125        @Override
126        public void setSimpleValue(long value) {
127            setSimpleValue(new SimpleValue(value));
128        }
129    
130        @Override
131        public void setSimpleValue(boolean value) {
132            setSimpleValue(new SimpleValue(value));
133        }
134    
135        @Override
136        public void setSimpleValue(SimpleValue value) {
137            dms.valueStorage.setSimpleValue(getModel(), value);
138        }
139    
140        // --- Child Topics ---
141    
142        @Override
143        public AttachedChildTopics getChildTopics() {
144            return childTopics;
145        }
146    
147        // ### FIXME: no UPDATE directives are added. No UPDATE events are fired.
148        // Should we call the abstract updateChildTopics() instead?
149        // Should we drop setChildTopics() completely as its semantics is too unclear?
150        @Override
151        public void setChildTopics(ChildTopicsModel childTopics) {
152            try {
153                getChildTopics().update(childTopics);
154            } catch (Exception e) {
155                throw new RuntimeException("Setting the child topics failed (" + childTopics + ")", e);
156            }
157        }
158    
159        // ---
160    
161        @Override
162        public DeepaMehtaObject loadChildTopics() {
163            getChildTopics().loadChildTopics();
164            return this;
165        }
166    
167        @Override
168        public DeepaMehtaObject loadChildTopics(String childTypeUri) {
169            getChildTopics().loadChildTopics(childTypeUri);
170            return this;
171        }
172    
173        // ---
174    
175        @Override
176        public DeepaMehtaObjectModel getModel() {
177            return model;
178        }
179    
180    
181    
182        // === Updating ===
183    
184        @Override
185        public void update(DeepaMehtaObjectModel newModel) {
186            updateUri(newModel.getUri());
187            updateTypeUri(newModel.getTypeUri());
188            if (getType().getDataTypeUri().equals("dm4.core.composite")) {
189                getChildTopics().update(newModel.getChildTopicsModel());
190            } else {
191                updateSimpleValue(newModel.getSimpleValue());
192            }
193            //
194            Directives.get().add(getUpdateDirective(), this);
195        }
196    
197        // ---
198    
199        @Override
200        public void updateChildTopic(RelatedTopicModel newChildTopic, AssociationDefinition assocDef) {
201            getChildTopics().updateChildTopics(newChildTopic, null, assocDef);      // newChildTopics=null
202        }
203    
204        @Override
205        public void updateChildTopics(List<RelatedTopicModel> newChildTopics, AssociationDefinition assocDef) {
206            getChildTopics().updateChildTopics(null, newChildTopics, assocDef);     // newChildTopic=null
207        }
208    
209    
210    
211        // === Deletion ===
212    
213        /**
214         * Deletes 1) this DeepaMehta object's child topics (recursively) which have an underlying association definition of
215         * type "Composition Definition" and 2) deletes all the remaining direct associations of this DeepaMehta object.
216         * <p>
217         * Note: deletion of the object itself is up to the subclasses.
218         */
219        @Override
220        public void delete() {
221            // 1) delete child topics (recursively)
222            for (AssociationDefinition assocDef : getType().getAssocDefs()) {
223                if (assocDef.getTypeUri().equals("dm4.core.composition_def")) {
224                    for (Topic childTopic : getRelatedTopics(assocDef.getInstanceLevelAssocTypeUri(),
225                            "dm4.core.parent", "dm4.core.child", assocDef.getChildTypeUri(), 0)) {
226                        childTopic.delete();
227                    }
228                }
229            }
230            // 2) delete direct associations
231            for (Association assoc : getAssociations()) {       // getAssociations() is abstract
232                assoc.delete();
233            }
234        }
235    
236    
237    
238        // === Traversal ===
239    
240        // --- Topic Retrieval ---
241    
242        @Override
243        public RelatedTopic getRelatedTopic(String assocTypeUri, String myRoleTypeUri, String othersRoleTypeUri,
244                                                                                       String othersTopicTypeUri) {
245            RelatedTopicModel topic = fetchRelatedTopic(assocTypeUri, myRoleTypeUri, othersRoleTypeUri, othersTopicTypeUri);
246            // fetchRelatedTopic() is abstract
247            return topic != null ? dms.instantiateRelatedTopic(topic) : null;
248        }
249    
250        @Override
251        public ResultList<RelatedTopic> getRelatedTopics(String assocTypeUri, int maxResultSize) {
252            return getRelatedTopics(assocTypeUri, null, null, null, maxResultSize);
253        }
254    
255        @Override
256        public ResultList<RelatedTopic> getRelatedTopics(String assocTypeUri, String myRoleTypeUri,
257                                                String othersRoleTypeUri, String othersTopicTypeUri, int maxResultSize) {
258            ResultList<RelatedTopicModel> topics = fetchRelatedTopics(assocTypeUri, myRoleTypeUri, othersRoleTypeUri,
259                othersTopicTypeUri, maxResultSize);     // fetchRelatedTopics() is abstract
260            return dms.instantiateRelatedTopics(topics);
261        }
262    
263        // Note: this method is implemented in the subclasses (this is an abstract class):
264        //     getRelatedTopics(List assocTypeUris, ...)
265    
266        // --- Association Retrieval ---
267    
268        // Note: these methods are implemented in the subclasses (this is an abstract class):
269        //     getAssociation(...)
270        //     getAssociations()
271    
272    
273    
274        // === Properties ===
275    
276        @Override
277        public Object getProperty(String propUri) {
278            return dms.getProperty(getId(), propUri);
279        }
280    
281        @Override
282        public boolean hasProperty(String propUri) {
283            return dms.hasProperty(getId(), propUri);
284        }
285    
286        // Note: these methods are implemented in the subclasses:
287        //     setProperty(...)
288        //     removeProperty(...)
289    
290    
291    
292        // === Misc ===
293    
294        @Override
295        public Object getDatabaseVendorObject() {
296            return dms.storageDecorator.getDatabaseVendorObject(getId());
297        }
298    
299    
300    
301        // **********************************
302        // *** JSONEnabled Implementation ***
303        // **********************************
304    
305    
306    
307        @Override
308        public JSONObject toJSON() {
309            return model.toJSON();
310        }
311    
312    
313    
314        // ****************
315        // *** Java API ***
316        // ****************
317    
318    
319    
320        @Override
321        public boolean equals(Object o) {
322            return ((AttachedDeepaMehtaObject) o).model.equals(model);
323        }
324    
325        @Override
326        public int hashCode() {
327            return model.hashCode();
328        }
329    
330        @Override
331        public String toString() {
332            return model.toString();
333        }
334    
335    
336    
337        // ----------------------------------------------------------------------------------------- Package Private Methods
338    
339        abstract String className();
340    
341        abstract void updateChildTopics(ChildTopicsModel childTopics);
342    
343        abstract Directive getUpdateDirective();
344    
345        abstract void storeUri();
346    
347        abstract void storeTypeUri();
348    
349        // ---
350    
351        abstract RelatedTopicModel fetchRelatedTopic(String assocTypeUri, String myRoleTypeUri,
352                                                     String othersRoleTypeUri, String othersTopicTypeUri);
353    
354        abstract ResultList<RelatedTopicModel> fetchRelatedTopics(String assocTypeUri, String myRoleTypeUri,
355                                                    String othersRoleTypeUri, String othersTopicTypeUri, int maxResultSize);
356    
357        // ---
358    
359        // ### TODO: add to public interface?
360        abstract Type getType();
361    
362        // ------------------------------------------------------------------------------------------------- Private Methods
363    
364    
365    
366        // === Update ===
367    
368        private void updateUri(String newUri) {
369            // abort if no update is requested
370            if (newUri == null) {
371                return;
372            }
373            //
374            String uri = getUri();
375            if (!uri.equals(newUri)) {
376                logger.info("### Changing URI of " + className() + " " + getId() +
377                    " from \"" + uri + "\" -> \"" + newUri + "\"");
378                setUri(newUri);
379            }
380        }
381    
382        private void updateTypeUri(String newTypeUri) {
383            // abort if no update is requested
384            if (newTypeUri == null) {
385                return;
386            }
387            //
388            String typeUri = getTypeUri();
389            if (!typeUri.equals(newTypeUri)) {
390                logger.info("### Changing type URI of " + className() + " " + getId() +
391                    " from \"" + typeUri + "\" -> \"" + newTypeUri + "\"");
392                setTypeUri(newTypeUri);
393            }
394        }
395    
396        private void updateSimpleValue(SimpleValue newValue) {
397            // abort if no update is requested
398            if (newValue == null) {
399                return;
400            }
401            //
402            SimpleValue value = getSimpleValue();
403            if (!value.equals(newValue)) {
404                logger.info("### Changing simple value of " + className() + " " + getId() +
405                    " from \"" + value + "\" -> \"" + newValue + "\"");
406                setSimpleValue(newValue);
407            }
408        }
409    }