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