001    package de.deepamehta.core.impl;
002    
003    import de.deepamehta.core.Association;
004    import de.deepamehta.core.AssociationType;
005    import de.deepamehta.core.RelatedAssociation;
006    import de.deepamehta.core.RelatedTopic;
007    import de.deepamehta.core.Role;
008    import de.deepamehta.core.Topic;
009    import de.deepamehta.core.TopicRole;
010    import de.deepamehta.core.model.AssociationModel;
011    import de.deepamehta.core.model.AssociationRoleModel;
012    import de.deepamehta.core.model.RelatedAssociationModel;
013    import de.deepamehta.core.model.RelatedTopicModel;
014    import de.deepamehta.core.model.RoleModel;
015    import de.deepamehta.core.model.TopicRoleModel;
016    import de.deepamehta.core.service.ClientState;
017    import de.deepamehta.core.service.Directive;
018    import de.deepamehta.core.service.Directives;
019    import de.deepamehta.core.service.ResultList;
020    import de.deepamehta.core.storage.spi.DeepaMehtaTransaction;
021    
022    import java.util.ArrayList;
023    import java.util.List;
024    
025    import java.util.logging.Logger;
026    
027    
028    
029    /**
030     * An association model that is attached to the DB.
031     */
032    class AttachedAssociation extends AttachedDeepaMehtaObject implements Association {
033    
034        // ---------------------------------------------------------------------------------------------- Instance Variables
035    
036        private Role role1;     // attached object cache
037        private Role role2;     // attached object cache
038    
039        private Logger logger = Logger.getLogger(getClass().getName());
040    
041        // ---------------------------------------------------------------------------------------------------- Constructors
042    
043        AttachedAssociation(AssociationModel model, EmbeddedService dms) {
044            super(model, dms);
045            // init attached object cache
046            this.role1 = createAttachedRole(model.getRoleModel1());
047            this.role2 = createAttachedRole(model.getRoleModel2());
048        }
049    
050        // -------------------------------------------------------------------------------------------------- Public Methods
051    
052    
053    
054        // ******************************************
055        // *** AttachedDeepaMehtaObject Overrides ***
056        // ******************************************
057    
058    
059    
060        // === Deletion ===
061    
062        @Override
063        public void delete(Directives directives) {
064            DeepaMehtaTransaction tx = dms.beginTx();
065            try {
066                // delete sub-topics and associations
067                super.delete(directives);
068                // delete association itself
069                logger.info("Deleting " + this);
070                directives.add(Directive.DELETE_ASSOCIATION, this);
071                dms.storageDecorator.deleteAssociation(getId());
072                //
073                tx.success();
074            } catch (IllegalStateException e) {
075                // Note: getAssociations() might throw IllegalStateException and is no problem.
076                // This can happen when this object is an association which is already deleted.
077                //
078                // Consider this particular situation: let A1 and A2 be associations of this object and let A2 point to A1.
079                // If A1 gets deleted first (the association set order is non-deterministic), A2 is implicitely deleted
080                // with it (because it is a direct association of A1 as well). Then when the loop comes to A2
081                // "IllegalStateException: Node[1327] has been deleted in this tx" is thrown because A2 has been deleted
082                // already. (The Node appearing in the exception is the middle node of A2.) If, on the other hand, A2
083                // gets deleted first no error would occur.
084                //
085                // This particular situation exists when e.g. a topicmap is deleted while one of its mapcontext
086                // associations is also a part of the topicmap itself. This originates e.g. when the user reveals
087                // a topicmap's mapcontext association and then deletes the topicmap.
088                //
089                if (e.getMessage().matches("Node\\[\\d+\\] has been deleted in this tx")) {
090                    logger.info("### Association has already been deleted in this transaction. This can happen while " +
091                        "deleting a topic with direct associations A1 and A2 while A2 points to A1 (" + this + ")");
092                    tx.success();
093                } else {
094                    throw e;
095                }
096            } catch (Exception e) {
097                logger.warning("ROLLBACK!");
098                throw new RuntimeException("Deleting association failed (" + this + ")", e);
099            } finally {
100                tx.finish();
101            }
102        }
103    
104    
105    
106        // **********************************
107        // *** Association Implementation ***
108        // **********************************
109    
110    
111    
112        @Override
113        public Role getRole1() {
114            return role1;
115        }
116    
117        @Override
118        public Role getRole2() {
119            return role2;
120        }
121    
122        // ---
123    
124        @Override
125        public Topic getTopic(String roleTypeUri) {
126            List<Topic> topics = getTopics(roleTypeUri);
127            switch (topics.size()) {
128            case 0:
129                return null;
130            case 1:
131                return topics.get(0);
132            default:
133                throw new RuntimeException("Ambiguity in association: " + topics.size() + " topics have role type \"" +
134                    roleTypeUri + "\" (" + this + ")");
135            }
136        }
137    
138        @Override
139        public List<Topic> getTopics(String roleTypeUri) {
140            List<Topic> topics = new ArrayList();
141            filterTopic(getRole1(), roleTypeUri, topics);
142            filterTopic(getRole2(), roleTypeUri, topics);
143            return topics;
144        }
145    
146        // ---
147    
148        @Override
149        public Role getRole(RoleModel roleModel) {
150            if (getRole1().getModel().refsSameObject(roleModel)) {
151                return getRole1();
152            } else if (getRole2().getModel().refsSameObject(roleModel)) {
153                return getRole2();
154            }
155            throw new RuntimeException("Role is not part of association (role=" + roleModel + ", association=" + this);
156        }
157    
158        @Override
159        public boolean isPlayer(TopicRoleModel roleModel) {
160            List<Topic> topics = getTopics(roleModel.getRoleTypeUri());
161            return topics.size() > 0 && topics.get(0).getId() == roleModel.getPlayerId();
162        }
163    
164        // ---
165    
166        @Override
167        public AssociationModel getModel() {
168            return (AssociationModel) super.getModel();
169        }
170    
171    
172    
173        // === Updating ===
174    
175        /**
176         * @param   model   The data to update.
177         *                  If the type URI is <code>null</code> it is not updated.
178         *                  If role 1 is <code>null</code> it is not updated.
179         *                  If role 2 is <code>null</code> it is not updated.
180         */
181        @Override
182        public void update(AssociationModel model, ClientState clientState, Directives directives) {
183            logger.info("Updating association " + getId() + " (new " + model + ")");
184            //
185            dms.fireEvent(CoreEvent.PRE_UPDATE_ASSOCIATION, this, model, directives);
186            //
187            AssociationModel oldModel = getModel().clone();
188            super.update(model, clientState, directives);
189            updateRole(model.getRoleModel1(), 1);
190            updateRole(model.getRoleModel2(), 2);
191            //
192            addUpdateDirective(directives);
193            //
194            dms.fireEvent(CoreEvent.POST_UPDATE_ASSOCIATION, this, oldModel, clientState, directives);
195        }
196    
197    
198    
199        // === Traversal ===
200    
201        @Override
202        public RelatedAssociation getRelatedAssociation(String assocTypeUri, String myRoleTypeUri,
203                                                                             String othersRoleTypeUri) {
204            RelatedAssociationModel assoc = dms.storageDecorator.fetchAssociationRelatedAssociation(getId(),
205                assocTypeUri, myRoleTypeUri, othersRoleTypeUri, null); // othersAssocTypeUri=null
206            return assoc != null ? dms.instantiateRelatedAssociation(assoc, false, false) : null; 
207                                                                       // fetchComposite=false, fetchRelatingComposite=false
208        }
209    
210    
211    
212        // ***************************************
213        // *** DeepaMehtaObject Implementation ***
214        // ***************************************
215    
216    
217    
218        // === Traversal ===
219    
220        // --- Topic Retrieval ---
221    
222        @Override
223        public ResultList<RelatedTopic> getRelatedTopics(List assocTypeUris, String myRoleTypeUri, String othersRoleTypeUri,
224                                        String othersTopicTypeUri, boolean fetchComposite, boolean fetchRelatingComposite,
225                                        int maxResultSize) {
226            ResultList<RelatedTopicModel> topics = dms.storageDecorator.fetchAssociationRelatedTopics(getId(),
227                assocTypeUris, myRoleTypeUri, othersRoleTypeUri, othersTopicTypeUri, maxResultSize);
228            return dms.instantiateRelatedTopics(topics, fetchComposite, fetchRelatingComposite);
229        }
230    
231        // --- Association Retrieval ---
232    
233        @Override
234        public Association getAssociation(String assocTypeUri, String myRoleTypeUri, String othersRoleTypeUri,
235                                                                                       long othersTopicId) {
236            AssociationModel assoc = dms.storageDecorator.fetchAssociationBetweenTopicAndAssociation(assocTypeUri,
237                othersTopicId, getId(), othersRoleTypeUri, myRoleTypeUri);
238            return assoc != null ? dms.instantiateAssociation(assoc, false) : null;     // fetchComposite=false
239        }
240    
241        @Override
242        public List<Association> getAssociations() {
243            return dms.instantiateAssociations(dms.storageDecorator.fetchAssociationAssociations(getId()), false);
244                                                                                        // fetchComposite=false
245        }
246    
247    
248    
249        // === Properties ===
250    
251        @Override
252        public Object getProperty(String propUri) {
253            return dms.storageDecorator.fetchAssociationProperty(getId(), propUri);
254        }
255    
256        @Override
257        public void setProperty(String propUri, Object propValue, boolean addToIndex) {
258            dms.storageDecorator.storeAssociationProperty(getId(), propUri, propValue, addToIndex);
259        }
260    
261        @Override
262        public boolean hasProperty(String propUri) {
263            return dms.storageDecorator.hasAssociationProperty(getId(), propUri);
264        }
265    
266        @Override
267        public void removeProperty(String propUri) {
268            dms.storageDecorator.removeAssociationProperty(getId(), propUri);
269        }
270    
271    
272    
273        // ----------------------------------------------------------------------------------------- Package Private Methods
274    
275        /**
276         * Convenience method.
277         */
278        AssociationType getAssociationType() {
279            return (AssociationType) getType();
280        }
281    
282    
283    
284        // === Implementation of the abstract methods ===
285    
286        @Override
287        final String className() {
288            return "association";
289        }
290    
291        @Override
292        void addUpdateDirective(Directives directives) {
293            directives.add(Directive.UPDATE_ASSOCIATION, this);
294        }
295    
296        @Override
297        final void storeUri() {
298            dms.storageDecorator.storeAssociationUri(getId(), getUri());
299        }
300    
301        @Override
302        final void storeTypeUri() {
303            reassignInstantiation();
304            dms.storageDecorator.storeAssociationTypeUri(getId(), getTypeUri());
305        }
306    
307        // ---
308    
309        @Override
310        final RelatedTopicModel fetchRelatedTopic(String assocTypeUri, String myRoleTypeUri,
311                                                  String othersRoleTypeUri, String othersTopicTypeUri) {
312            return dms.storageDecorator.fetchAssociationRelatedTopic(getId(), assocTypeUri, myRoleTypeUri,
313                othersRoleTypeUri, othersTopicTypeUri);
314        }
315    
316        @Override
317        final ResultList<RelatedTopicModel> fetchRelatedTopics(String assocTypeUri, String myRoleTypeUri,
318                                                              String othersRoleTypeUri, String othersTopicTypeUri,
319                                                              int maxResultSize) {
320            return dms.storageDecorator.fetchAssociationRelatedTopics(getId(), assocTypeUri, myRoleTypeUri,
321                othersRoleTypeUri, othersTopicTypeUri, maxResultSize);
322        }
323    
324    
325    
326        // ------------------------------------------------------------------------------------------------- Private Methods
327    
328        // --- Update ---
329    
330        /**
331         * @param   nr      used only for logging
332         */
333        private void updateRole(RoleModel newModel, int nr) {
334            if (newModel != null) {
335                // Note: We must lookup the roles individually.
336                // The role order (getRole1(), getRole2()) is undeterministic and not fix.
337                Role role = getRole(newModel);
338                String newRoleTypeUri = newModel.getRoleTypeUri();  // new value
339                String roleTypeUri = role.getRoleTypeUri();         // current value
340                if (!roleTypeUri.equals(newRoleTypeUri)) {          // has changed?
341                    logger.info("### Changing role type " + nr + " from \"" + roleTypeUri + "\" -> \"" + newRoleTypeUri +
342                        "\"");
343                    role.setRoleTypeUri(newRoleTypeUri);
344                }
345            }
346        }
347    
348        // --- Helper ---
349    
350        private Role createAttachedRole(RoleModel model) {
351            if (model instanceof TopicRoleModel) {
352                return new AttachedTopicRole((TopicRoleModel) model, this, dms);
353            } else if (model instanceof AssociationRoleModel) {
354                return new AttachedAssociationRole((AssociationRoleModel) model, this, dms);
355            } else {
356                throw new RuntimeException("Unexpected RoleModel object (" + model + ")");
357            }
358        }
359    
360        private void filterTopic(Role role, String roleTypeUri, List<Topic> topics) {
361            if (role instanceof TopicRole && role.getRoleTypeUri().equals(roleTypeUri)) {
362                topics.add(((TopicRole) role).getTopic());
363            }
364        }
365    
366        // ---
367    
368        private void reassignInstantiation() {
369            // remove current assignment
370            fetchInstantiation().delete(new Directives());      // ### FIXME: receive directives as argument
371            // create new assignment
372            dms.createAssociationInstantiation(getId(), getTypeUri());
373        }
374    
375        private Association fetchInstantiation() {
376            RelatedTopic assocType = getRelatedTopic("dm4.core.instantiation", "dm4.core.instance", "dm4.core.type",
377                "dm4.core.assoc_type", false, false);
378            //
379            if (assocType == null) {
380                throw new RuntimeException("Association " + getId() + " is not associated to an association type");
381            }
382            //
383            return assocType.getRelatingAssociation();
384        }
385    }