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}