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 // ### TODO: 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 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 addUpdateDirective(); 082 // 083 dms.fireEvent(CoreEvent.POST_UPDATE_ASSOCIATION, this, oldModel); 084 } 085 086 087 088 // === Deletion === 089 090 @Override 091 public void delete() { 092 try { 093 dms.fireEvent(CoreEvent.PRE_DELETE_ASSOCIATION, this); 094 // 095 // delete sub-topics and associations 096 super.delete(); 097 // delete association itself 098 logger.info("Deleting " + this); 099 Directives.get().add(Directive.DELETE_ASSOCIATION, this); 100 dms.storageDecorator.deleteAssociation(getId()); 101 // 102 dms.fireEvent(CoreEvent.POST_DELETE_ASSOCIATION, this); 103 } catch (IllegalStateException e) { 104 // Note: getAssociations() might throw IllegalStateException and is no problem. 105 // This can happen when this object is an association which is already deleted. 106 // 107 // Consider this particular situation: let A1 and A2 be associations of this object and let A2 point to A1. 108 // If A1 gets deleted first (the association set order is non-deterministic), A2 is implicitely deleted 109 // with it (because it is a direct association of A1 as well). Then when the loop comes to A2 110 // "IllegalStateException: Node[1327] has been deleted in this tx" is thrown because A2 has been deleted 111 // already. (The Node appearing in the exception is the middle node of A2.) If, on the other hand, A2 112 // gets deleted first no error would occur. 113 // 114 // This particular situation exists when e.g. a topicmap is deleted while one of its mapcontext 115 // associations is also a part of the topicmap itself. This originates e.g. when the user reveals 116 // a topicmap's mapcontext association and then deletes the topicmap. 117 // 118 if (e.getMessage().equals("Node[" + getId() + "] has been deleted in this tx")) { 119 logger.info("### Association " + getId() + " has already been deleted in this transaction. This can " + 120 "happen while deleting a topic with associations A1 and A2 while A2 points to A1 (" + this + ")"); 121 } else { 122 throw e; 123 } 124 } catch (Exception e) { 125 throw new RuntimeException("Deleting association failed (" + this + ")", e); 126 } 127 } 128 129 130 131 // ********************************** 132 // *** Association Implementation *** 133 // ********************************** 134 135 136 137 @Override 138 public Role getRole1() { 139 return role1; 140 } 141 142 @Override 143 public Role getRole2() { 144 return role2; 145 } 146 147 // --- 148 149 @Override 150 public DeepaMehtaObject getPlayer1() { 151 return getRole1().getPlayer(); 152 } 153 154 @Override 155 public DeepaMehtaObject getPlayer2() { 156 return getRole2().getPlayer(); 157 } 158 159 // --- 160 161 @Override 162 public Topic getTopic(String roleTypeUri) { 163 List<Topic> topics = getTopics(roleTypeUri); 164 switch (topics.size()) { 165 case 0: 166 return null; 167 case 1: 168 return topics.get(0); 169 default: 170 throw new RuntimeException("Ambiguity in association: " + topics.size() + " topics have role type \"" + 171 roleTypeUri + "\" (" + this + ")"); 172 } 173 } 174 175 @Override 176 public List<Topic> getTopics(String roleTypeUri) { 177 List<Topic> topics = new ArrayList(); 178 filterTopic(getRole1(), roleTypeUri, topics); 179 filterTopic(getRole2(), roleTypeUri, topics); 180 return topics; 181 } 182 183 // --- 184 185 @Override 186 public Role getRole(RoleModel roleModel) { 187 if (getRole1().getModel().refsSameObject(roleModel)) { 188 return getRole1(); 189 } else if (getRole2().getModel().refsSameObject(roleModel)) { 190 return getRole2(); 191 } 192 throw new RuntimeException("Role is not part of association (role=" + roleModel + ", association=" + this); 193 } 194 195 @Override 196 public boolean isPlayer(TopicRoleModel roleModel) { 197 List<Topic> topics = getTopics(roleModel.getRoleTypeUri()); 198 return topics.size() > 0 && topics.get(0).getId() == roleModel.getPlayerId(); 199 } 200 201 // --- 202 203 @Override 204 public Association loadChildTopics() { 205 return (Association) super.loadChildTopics(); 206 } 207 208 @Override 209 public Association loadChildTopics(String childTypeUri) { 210 return (Association) super.loadChildTopics(childTypeUri); 211 } 212 213 // --- 214 215 @Override 216 public AssociationModel getModel() { 217 return (AssociationModel) super.getModel(); 218 } 219 220 221 222 // *************************************** 223 // *** DeepaMehtaObject Implementation *** 224 // *************************************** 225 226 227 228 // === Traversal === 229 230 // --- Topic Retrieval --- 231 232 @Override 233 public ResultList<RelatedTopic> getRelatedTopics(List assocTypeUris, String myRoleTypeUri, String othersRoleTypeUri, 234 String othersTopicTypeUri, int maxResultSize) { 235 ResultList<RelatedTopicModel> topics = dms.storageDecorator.fetchAssociationRelatedTopics(getId(), 236 assocTypeUris, myRoleTypeUri, othersRoleTypeUri, othersTopicTypeUri, maxResultSize); 237 return dms.instantiateRelatedTopics(topics); 238 } 239 240 // --- Association Retrieval --- 241 242 @Override 243 public RelatedAssociation getRelatedAssociation(String assocTypeUri, String myRoleTypeUri, 244 String othersRoleTypeUri, String othersAssocTypeUri) { 245 RelatedAssociationModel assoc = dms.storageDecorator.fetchAssociationRelatedAssociation(getId(), 246 assocTypeUri, myRoleTypeUri, othersRoleTypeUri, othersAssocTypeUri); 247 return assoc != null ? dms.instantiateRelatedAssociation(assoc) : null; 248 } 249 250 @Override 251 public ResultList<RelatedAssociation> getRelatedAssociations(String assocTypeUri, String myRoleTypeUri, 252 String othersRoleTypeUri, String othersAssocTypeUri) { 253 ResultList<RelatedAssociationModel> assocs = dms.storageDecorator.fetchAssociationRelatedAssociations(getId(), 254 assocTypeUri, myRoleTypeUri, othersRoleTypeUri, othersAssocTypeUri); 255 return dms.instantiateRelatedAssociations(assocs); 256 } 257 258 // --- 259 260 @Override 261 public Association getAssociation(String assocTypeUri, String myRoleTypeUri, String othersRoleTypeUri, 262 long othersTopicId) { 263 AssociationModel assoc = dms.storageDecorator.fetchAssociationBetweenTopicAndAssociation(assocTypeUri, 264 othersTopicId, getId(), othersRoleTypeUri, myRoleTypeUri); 265 return assoc != null ? dms.instantiateAssociation(assoc) : null; 266 } 267 268 @Override 269 public List<Association> getAssociations() { 270 return dms.instantiateAssociations(dms.storageDecorator.fetchAssociationAssociations(getId())); 271 } 272 273 274 275 // === Properties === 276 277 @Override 278 public Object getProperty(String propUri) { 279 return dms.storageDecorator.fetchAssociationProperty(getId(), propUri); 280 } 281 282 @Override 283 public void setProperty(String propUri, Object propValue, boolean addToIndex) { 284 dms.storageDecorator.storeAssociationProperty(getId(), propUri, propValue, addToIndex); 285 } 286 287 @Override 288 public boolean hasProperty(String propUri) { 289 return dms.storageDecorator.hasAssociationProperty(getId(), propUri); 290 } 291 292 @Override 293 public void removeProperty(String propUri) { 294 dms.storageDecorator.removeAssociationProperty(getId(), propUri); 295 } 296 297 298 299 // ----------------------------------------------------------------------------------------- Package Private Methods 300 301 /** 302 * Convenience method. 303 */ 304 AssociationType getAssociationType() { 305 return (AssociationType) getType(); 306 } 307 308 309 310 // === Implementation of the abstract methods === 311 312 @Override 313 final String className() { 314 return "association"; 315 } 316 317 @Override 318 void addUpdateDirective() { 319 Directives.get().add(Directive.UPDATE_ASSOCIATION, this); 320 } 321 322 @Override 323 final void storeUri() { 324 dms.storageDecorator.storeAssociationUri(getId(), getUri()); 325 } 326 327 @Override 328 final void storeTypeUri() { 329 reassignInstantiation(); 330 dms.storageDecorator.storeAssociationTypeUri(getId(), getTypeUri()); 331 } 332 333 // --- 334 335 @Override 336 final RelatedTopicModel fetchRelatedTopic(String assocTypeUri, String myRoleTypeUri, 337 String othersRoleTypeUri, String othersTopicTypeUri) { 338 return dms.storageDecorator.fetchAssociationRelatedTopic(getId(), assocTypeUri, myRoleTypeUri, 339 othersRoleTypeUri, othersTopicTypeUri); 340 } 341 342 @Override 343 final ResultList<RelatedTopicModel> fetchRelatedTopics(String assocTypeUri, String myRoleTypeUri, 344 String othersRoleTypeUri, String othersTopicTypeUri, 345 int maxResultSize) { 346 return dms.storageDecorator.fetchAssociationRelatedTopics(getId(), assocTypeUri, myRoleTypeUri, 347 othersRoleTypeUri, othersTopicTypeUri, maxResultSize); 348 } 349 350 351 352 // ------------------------------------------------------------------------------------------------- Private Methods 353 354 // --- Update --- 355 356 /** 357 * @param nr used only for logging 358 */ 359 private void updateRole(RoleModel newModel, int nr) { 360 if (newModel != null) { 361 // Note: We must lookup the roles individually. 362 // The role order (getRole1(), getRole2()) is undeterministic and not fix. 363 Role role = getRole(newModel); 364 String newRoleTypeUri = newModel.getRoleTypeUri(); // new value 365 String roleTypeUri = role.getRoleTypeUri(); // current value 366 if (!roleTypeUri.equals(newRoleTypeUri)) { // has changed? 367 logger.info("### Changing role type " + nr + " from \"" + roleTypeUri + "\" -> \"" + newRoleTypeUri + 368 "\""); 369 role.setRoleTypeUri(newRoleTypeUri); 370 } 371 } 372 } 373 374 // --- Helper --- 375 376 private Role createAttachedRole(RoleModel model) { 377 if (model instanceof TopicRoleModel) { 378 return new AttachedTopicRole((TopicRoleModel) model, this, dms); 379 } else if (model instanceof AssociationRoleModel) { 380 return new AttachedAssociationRole((AssociationRoleModel) model, this, dms); 381 } else { 382 throw new RuntimeException("Unexpected RoleModel object (" + model + ")"); 383 } 384 } 385 386 private void filterTopic(Role role, String roleTypeUri, List<Topic> topics) { 387 if (role instanceof TopicRole && role.getRoleTypeUri().equals(roleTypeUri)) { 388 topics.add(((TopicRole) role).getTopic()); 389 } 390 } 391 392 // --- 393 394 private void reassignInstantiation() { 395 // remove current assignment 396 fetchInstantiation().delete(); 397 // create new assignment 398 dms.createAssociationInstantiation(getId(), getTypeUri()); 399 } 400 401 private Association fetchInstantiation() { 402 RelatedTopic assocType = getRelatedTopic("dm4.core.instantiation", "dm4.core.instance", "dm4.core.type", 403 "dm4.core.assoc_type"); 404 // 405 if (assocType == null) { 406 throw new RuntimeException("Association " + getId() + " is not associated to an association type"); 407 } 408 // 409 return assocType.getRelatingAssociation(); 410 } 411 }