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