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(String childTypeUri) {
166            getCompositeValue().loadChildTopics(childTypeUri);
167        }
168    
169        // ---
170    
171        @Override
172        public DeepaMehtaObjectModel getModel() {
173            return model;
174        }
175    
176    
177    
178        // === Updating ===
179    
180        @Override
181        public void update(DeepaMehtaObjectModel newModel, ClientState clientState, Directives directives) {
182            updateUri(newModel.getUri());
183            updateTypeUri(newModel.getTypeUri());
184            //
185            if (getType().getDataTypeUri().equals("dm4.core.composite")) {
186                getCompositeValue().update(newModel.getCompositeValueModel(), clientState, directives);
187            } else {
188                updateSimpleValue(newModel.getSimpleValue());
189            }
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        // === Traversal ===
209    
210        // --- Topic Retrieval ---
211    
212        @Override
213        public RelatedTopic getRelatedTopic(String assocTypeUri, String myRoleTypeUri, String othersRoleTypeUri,
214                                                    String othersTopicTypeUri, boolean fetchComposite,
215                                                    boolean fetchRelatingComposite) {
216            RelatedTopicModel topic = fetchRelatedTopic(assocTypeUri, myRoleTypeUri, othersRoleTypeUri, othersTopicTypeUri);
217            // fetchRelatedTopic() is abstract
218            return topic != null ? dms.instantiateRelatedTopic(topic, fetchComposite, fetchRelatingComposite) : null;
219        }
220    
221        @Override
222        public ResultList<RelatedTopic> getRelatedTopics(String assocTypeUri, int maxResultSize) {
223            return getRelatedTopics(assocTypeUri, null, null, null, false, false, maxResultSize);
224        }
225    
226        @Override
227        public ResultList<RelatedTopic> getRelatedTopics(String assocTypeUri, String myRoleTypeUri,
228                                                String othersRoleTypeUri, String othersTopicTypeUri,
229                                                boolean fetchComposite, boolean fetchRelatingComposite, int maxResultSize) {
230            ResultList<RelatedTopicModel> topics = fetchRelatedTopics(assocTypeUri, myRoleTypeUri, othersRoleTypeUri,
231                othersTopicTypeUri, maxResultSize);     // fetchRelatedTopics() is abstract
232            return dms.instantiateRelatedTopics(topics, fetchComposite, fetchRelatingComposite);
233        }
234    
235        // Note: this method is implemented in the subclasses (this is an abstract class):
236        //     getRelatedTopics(List assocTypeUris, ...)
237    
238        // --- Association Retrieval ---
239    
240        // Note: these methods are implemented in the subclasses (this is an abstract class):
241        //     getAssociation(...);
242        //     getAssociations();
243    
244    
245    
246        // === Deletion ===
247    
248        /**
249         * Deletes all sub-topics of this DeepaMehta object (associated via "dm4.core.composition", recursively) and
250         * deletes all the remaining direct associations of this DeepaMehta object.
251         * <p>
252         * Note: deletion of the object itself is up to the subclasses.
253         */
254        @Override
255        public void delete(Directives directives) {
256            // Note: directives must be not null.
257            // The subclass's delete() methods add DELETE_TOPIC and DELETE_ASSOCIATION directives to it respectively.
258            if (directives == null) {
259                throw new IllegalArgumentException("directives is null");
260            }
261            // 1) recursively delete sub-topics
262            ResultList<RelatedTopic> childTopics = getRelatedTopics("dm4.core.composition",
263                "dm4.core.parent", "dm4.core.child", null, false, false, 0);
264            for (Topic childTopic : childTopics) {
265                childTopic.delete(directives);
266            }
267            // 2) delete direct associations
268            for (Association assoc : getAssociations()) {       // getAssociations() is abstract
269                assoc.delete(directives);
270            }
271        }
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        abstract void addUpdateDirective(Directives directives);
316    
317        abstract void storeUri();
318    
319        abstract void storeTypeUri();
320    
321        // ---
322    
323        abstract RelatedTopicModel fetchRelatedTopic(String assocTypeUri, String myRoleTypeUri,
324                                                    String othersRoleTypeUri, String othersTopicTypeUri);
325    
326        abstract ResultList<RelatedTopicModel> fetchRelatedTopics(String assocTypeUri, String myRoleTypeUri,
327                                                    String othersRoleTypeUri, String othersTopicTypeUri, int maxResultSize);
328    
329        // ---
330    
331        // ### TODO: add to public interface
332        Type getType() {
333            return dms.valueStorage.getType(getModel());
334        }
335    
336        // ------------------------------------------------------------------------------------------------- Private Methods
337    
338    
339    
340        // === Update ===
341    
342        private void updateUri(String newUri) {
343            // abort if no update is requested
344            if (newUri == null) {
345                return;
346            }
347            //
348            String uri = getUri();
349            if (!uri.equals(newUri)) {
350                logger.info("### Changing URI of " + className() + " " + getId() +
351                    " from \"" + uri + "\" -> \"" + newUri + "\"");
352                setUri(newUri);
353            }
354        }
355    
356        private void updateTypeUri(String newTypeUri) {
357            // abort if no update is requested
358            if (newTypeUri == null) {
359                return;
360            }
361            //
362            String typeUri = getTypeUri();
363            if (!typeUri.equals(newTypeUri)) {
364                logger.info("### Changing type URI of " + className() + " " + getId() +
365                    " from \"" + typeUri + "\" -> \"" + newTypeUri + "\"");
366                setTypeUri(newTypeUri);
367            }
368        }
369    
370        private void updateSimpleValue(SimpleValue newValue) {
371            // abort if no update is requested
372            if (newValue == null) {
373                return;
374            }
375            //
376            SimpleValue value = getSimpleValue();
377            if (!value.equals(newValue)) {
378                logger.info("### Changing simple value of " + className() + " " + getId() +
379                    " from \"" + value + "\" -> \"" + newValue + "\"");
380                setSimpleValue(newValue);
381            }
382        }
383    }