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