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