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