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            List<Topic> topics = getTopics(roleTypeUri);
166            switch (topics.size()) {
167            case 0:
168                return null;
169            case 1:
170                return topics.get(0);
171            default:
172                throw new RuntimeException("Ambiguity in association: " + topics.size() + " topics have role type \"" +
173                    roleTypeUri + "\" (" + this + ")");
174            }
175        }
176    
177        @Override
178        public List<Topic> getTopics(String roleTypeUri) {
179            List<Topic> topics = new ArrayList();
180            filterTopic(getRole1(), roleTypeUri, topics);
181            filterTopic(getRole2(), roleTypeUri, topics);
182            return topics;
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            List<Topic> topics = getTopics(roleModel.getRoleTypeUri());
200            return topics.size() > 0 && topics.get(0).getId() == roleModel.getPlayerId();
201        }
202    
203        // ---
204    
205        @Override
206        public Association loadChildTopics() {
207            return (Association) super.loadChildTopics();
208        }
209    
210        @Override
211        public Association loadChildTopics(String childTypeUri) {
212            return (Association) super.loadChildTopics(childTypeUri);
213        }
214    
215        // ---
216    
217        @Override
218        public AssociationModel getModel() {
219            return (AssociationModel) super.getModel();
220        }
221    
222    
223    
224        // ***************************************
225        // *** DeepaMehtaObject Implementation ***
226        // ***************************************
227    
228    
229    
230        // === Traversal ===
231    
232        // --- Topic Retrieval ---
233    
234        @Override
235        public ResultList<RelatedTopic> getRelatedTopics(List assocTypeUris, String myRoleTypeUri, String othersRoleTypeUri,
236                                                         String othersTopicTypeUri, int maxResultSize) {
237            ResultList<RelatedTopicModel> topics = dms.storageDecorator.fetchAssociationRelatedTopics(getId(),
238                assocTypeUris, myRoleTypeUri, othersRoleTypeUri, othersTopicTypeUri, maxResultSize);
239            return dms.instantiateRelatedTopics(topics);
240        }
241    
242        // --- Association Retrieval ---
243    
244        @Override
245        public RelatedAssociation getRelatedAssociation(String assocTypeUri, String myRoleTypeUri,
246                                                        String othersRoleTypeUri, String othersAssocTypeUri) {
247            RelatedAssociationModel assoc = dms.storageDecorator.fetchAssociationRelatedAssociation(getId(),
248                assocTypeUri, myRoleTypeUri, othersRoleTypeUri, othersAssocTypeUri);
249            return assoc != null ? dms.instantiateRelatedAssociation(assoc) : null; 
250        }
251    
252        @Override
253        public ResultList<RelatedAssociation> getRelatedAssociations(String assocTypeUri, String myRoleTypeUri,
254                                                                     String othersRoleTypeUri, String othersAssocTypeUri) {
255            ResultList<RelatedAssociationModel> assocs = dms.storageDecorator.fetchAssociationRelatedAssociations(getId(),
256                assocTypeUri, myRoleTypeUri, othersRoleTypeUri, othersAssocTypeUri);
257            return dms.instantiateRelatedAssociations(assocs); 
258        }
259    
260        // ---
261    
262        @Override
263        public Association getAssociation(String assocTypeUri, String myRoleTypeUri, String othersRoleTypeUri,
264                                                                                       long othersTopicId) {
265            AssociationModel assoc = dms.storageDecorator.fetchAssociationBetweenTopicAndAssociation(assocTypeUri,
266                othersTopicId, getId(), othersRoleTypeUri, myRoleTypeUri);
267            return assoc != null ? dms.instantiateAssociation(assoc) : null;
268        }
269    
270        @Override
271        public List<Association> getAssociations() {
272            return dms.instantiateAssociations(dms.storageDecorator.fetchAssociationAssociations(getId()));
273        }
274    
275    
276    
277        // === Properties ===
278    
279        @Override
280        public Object getProperty(String propUri) {
281            return dms.storageDecorator.fetchAssociationProperty(getId(), propUri);
282        }
283    
284        @Override
285        public void setProperty(String propUri, Object propValue, boolean addToIndex) {
286            dms.storageDecorator.storeAssociationProperty(getId(), propUri, propValue, addToIndex);
287        }
288    
289        @Override
290        public boolean hasProperty(String propUri) {
291            return dms.storageDecorator.hasAssociationProperty(getId(), propUri);
292        }
293    
294        @Override
295        public void removeProperty(String propUri) {
296            dms.storageDecorator.removeAssociationProperty(getId(), propUri);
297        }
298    
299    
300    
301        // ----------------------------------------------------------------------------------------- Package Private Methods
302    
303        /**
304         * Convenience method.
305         */
306        AssociationType getAssociationType() {
307            return (AssociationType) getType();
308        }
309    
310    
311    
312        // === Implementation of the abstract methods ===
313    
314        @Override
315        final String className() {
316            return "association";
317        }
318    
319        @Override
320        Directive getUpdateDirective() {
321            return Directive.UPDATE_ASSOCIATION;
322        }
323    
324        @Override
325        final void storeUri() {
326            dms.storageDecorator.storeAssociationUri(getId(), getUri());
327        }
328    
329        @Override
330        final void storeTypeUri() {
331            reassignInstantiation();
332            dms.storageDecorator.storeAssociationTypeUri(getId(), getTypeUri());
333        }
334    
335        // ---
336    
337        @Override
338        final RelatedTopicModel fetchRelatedTopic(String assocTypeUri, String myRoleTypeUri,
339                                                  String othersRoleTypeUri, String othersTopicTypeUri) {
340            return dms.storageDecorator.fetchAssociationRelatedTopic(getId(), assocTypeUri, myRoleTypeUri,
341                othersRoleTypeUri, othersTopicTypeUri);
342        }
343    
344        @Override
345        final ResultList<RelatedTopicModel> fetchRelatedTopics(String assocTypeUri, String myRoleTypeUri,
346                                                              String othersRoleTypeUri, String othersTopicTypeUri,
347                                                              int maxResultSize) {
348            return dms.storageDecorator.fetchAssociationRelatedTopics(getId(), assocTypeUri, myRoleTypeUri,
349                othersRoleTypeUri, othersTopicTypeUri, maxResultSize);
350        }
351    
352    
353    
354        // ------------------------------------------------------------------------------------------------- Private Methods
355    
356        // --- Update ---
357    
358        /**
359         * @param   nr      used only for logging
360         */
361        private void updateRole(RoleModel newModel, int nr) {
362            if (newModel != null) {
363                // Note: We must lookup the roles individually.
364                // The role order (getRole1(), getRole2()) is undeterministic and not fix.
365                Role role = getRole(newModel);
366                String newRoleTypeUri = newModel.getRoleTypeUri();  // new value
367                String roleTypeUri = role.getRoleTypeUri();         // current value
368                if (!roleTypeUri.equals(newRoleTypeUri)) {          // has changed?
369                    logger.info("### Changing role type " + nr + " from \"" + roleTypeUri + "\" -> \"" + newRoleTypeUri +
370                        "\"");
371                    role.setRoleTypeUri(newRoleTypeUri);
372                }
373            }
374        }
375    
376        // --- Helper ---
377    
378        private Role createAttachedRole(RoleModel model) {
379            if (model instanceof TopicRoleModel) {
380                return new AttachedTopicRole((TopicRoleModel) model, this, dms);
381            } else if (model instanceof AssociationRoleModel) {
382                return new AttachedAssociationRole((AssociationRoleModel) model, this, dms);
383            } else {
384                throw new RuntimeException("Unexpected RoleModel object (" + model + ")");
385            }
386        }
387    
388        private void filterTopic(Role role, String roleTypeUri, List<Topic> topics) {
389            if (role instanceof TopicRole && role.getRoleTypeUri().equals(roleTypeUri)) {
390                topics.add(((TopicRole) role).getTopic());
391            }
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    }