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