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