001package de.deepamehta.core.impl;
002
003import de.deepamehta.core.Association;
004import de.deepamehta.core.AssociationType;
005import de.deepamehta.core.DeepaMehtaObject;
006import de.deepamehta.core.RelatedAssociation;
007import de.deepamehta.core.RelatedTopic;
008import de.deepamehta.core.Role;
009import de.deepamehta.core.Topic;
010import de.deepamehta.core.TopicRole;
011import de.deepamehta.core.model.AssociationModel;
012import de.deepamehta.core.model.AssociationRoleModel;
013import de.deepamehta.core.model.ChildTopicsModel;
014import de.deepamehta.core.model.RelatedAssociationModel;
015import de.deepamehta.core.model.RelatedTopicModel;
016import de.deepamehta.core.model.RoleModel;
017import de.deepamehta.core.model.TopicRoleModel;
018import de.deepamehta.core.service.Directive;
019import de.deepamehta.core.service.Directives;
020import de.deepamehta.core.service.ResultList;
021
022import java.util.List;
023
024import java.util.logging.Logger;
025
026
027
028/**
029 * An association model that is attached to the DB.
030 */
031class 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}