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 }