001package de.deepamehta.core.impl; 002 003import de.deepamehta.core.Association; 004import de.deepamehta.core.model.AssociationModel; 005import de.deepamehta.core.model.AssociationTypeModel; 006import de.deepamehta.core.model.ChildTopicsModel; 007import de.deepamehta.core.model.DeepaMehtaObjectModel; 008import de.deepamehta.core.model.IndexMode; 009import de.deepamehta.core.model.RelatedTopicModel; 010import de.deepamehta.core.model.RoleModel; 011import de.deepamehta.core.model.TopicModel; 012import de.deepamehta.core.model.TopicRoleModel; 013import de.deepamehta.core.model.TypeModel; 014import de.deepamehta.core.service.DeepaMehtaEvent; 015import de.deepamehta.core.service.Directive; 016import de.deepamehta.core.service.Directives; 017 018import org.codehaus.jettison.json.JSONObject; 019 020import java.util.List; 021 022 023 024/** 025 * Collection of the data that makes up an {@link Association}. 026 * 027 * @author <a href="mailto:jri@deepamehta.de">Jörg Richter</a> 028 */ 029class AssociationModelImpl extends DeepaMehtaObjectModelImpl implements AssociationModel { 030 031 // ---------------------------------------------------------------------------------------------- Instance Variables 032 033 private RoleModelImpl roleModel1; // may be null in models used for an update operation 034 private RoleModelImpl roleModel2; // may be null in models used for an update operation 035 036 // ---------------------------------------------------------------------------------------------------- Constructors 037 038 AssociationModelImpl(DeepaMehtaObjectModelImpl object, RoleModelImpl roleModel1, RoleModelImpl roleModel2) { 039 super(object); 040 this.roleModel1 = roleModel1; 041 this.roleModel2 = roleModel2; 042 } 043 044 AssociationModelImpl(AssociationModelImpl assoc) { 045 super(assoc); 046 this.roleModel1 = assoc.roleModel1; 047 this.roleModel2 = assoc.roleModel2; 048 } 049 050 // -------------------------------------------------------------------------------------------------- Public Methods 051 052 @Override 053 public RoleModelImpl getRoleModel1() { 054 return roleModel1; 055 } 056 057 @Override 058 public RoleModelImpl getRoleModel2() { 059 return roleModel2; 060 } 061 062 // --- 063 064 @Override 065 public void setRoleModel1(RoleModel roleModel1) { 066 this.roleModel1 = (RoleModelImpl) roleModel1; 067 } 068 069 @Override 070 public void setRoleModel2(RoleModel roleModel2) { 071 this.roleModel2 = (RoleModelImpl) roleModel2; 072 } 073 074 // --- Convenience Methods --- 075 076 @Override 077 public RoleModel getRoleModel(String roleTypeUri) { 078 boolean rm1 = roleModel1.getRoleTypeUri().equals(roleTypeUri); 079 boolean rm2 = roleModel2.getRoleTypeUri().equals(roleTypeUri); 080 if (rm1 && rm2) { 081 throw new RuntimeException("Ambiguous getRoleModel() call: both players occupy role \"" + roleTypeUri + 082 "\" (" + this + ")"); 083 } 084 return rm1 ? roleModel1 : rm2 ? roleModel2 : null; 085 } 086 087 @Override 088 public long getOtherPlayerId(long id) { 089 long id1 = roleModel1.getPlayerId(); 090 long id2 = roleModel2.getPlayerId(); 091 if (id1 == id) { 092 return id2; 093 } else if (id2 == id) { 094 return id1; 095 } else { 096 throw new IllegalArgumentException("ID " + id + " doesn't refer to a player in " + this); 097 } 098 } 099 100 @Override 101 public boolean hasSameRoleTypeUris() { 102 return roleModel1.getRoleTypeUri().equals(roleModel2.getRoleTypeUri()); 103 } 104 105 106 107 // === Implementation of the abstract methods === 108 109 @Override 110 public RoleModel createRoleModel(String roleTypeUri) { 111 return mf.newAssociationRoleModel(id, roleTypeUri); 112 } 113 114 115 116 // === Serialization === 117 118 @Override 119 public JSONObject toJSON() { 120 try { 121 return super.toJSON() 122 .put("role_1", roleModel1.toJSON()) 123 .put("role_2", roleModel2.toJSON()); 124 } catch (Exception e) { 125 throw new RuntimeException("Serialization failed (" + this + ")", e); 126 } 127 } 128 129 130 131 // === Java API === 132 133 @Override 134 public AssociationModel clone() { 135 try { 136 AssociationModel model = (AssociationModel) super.clone(); 137 model.setRoleModel1(roleModel1.clone()); 138 model.setRoleModel2(roleModel2.clone()); 139 return model; 140 } catch (Exception e) { 141 throw new RuntimeException("Cloning an AssociationModel failed", e); 142 } 143 } 144 145 @Override 146 public String toString() { 147 return "association (" + super.toString() + ", " + roleModel1 + ", " + roleModel2 + ")"; 148 } 149 150 151 152 // ----------------------------------------------------------------------------------------- Package Private Methods 153 154 @Override 155 String className() { 156 return "association"; 157 } 158 159 @Override 160 AssociationImpl instantiate() { 161 return new AssociationImpl(this, pl); 162 } 163 164 // --- 165 166 @Override 167 AssociationTypeModel getType() { 168 return pl.typeStorage.getAssociationType(typeUri); 169 } 170 171 @Override 172 List<AssociationModelImpl> getAssociations() { 173 return pl.fetchAssociationAssociations(id); 174 } 175 176 // --- 177 178 @Override 179 RelatedTopicModelImpl getRelatedTopic(String assocTypeUri, String myRoleTypeUri, String othersRoleTypeUri, 180 String othersTopicTypeUri) { 181 return pl.fetchAssociationRelatedTopic(id, assocTypeUri, myRoleTypeUri, othersRoleTypeUri, 182 othersTopicTypeUri); 183 } 184 185 @Override 186 List<RelatedTopicModelImpl> getRelatedTopics(String assocTypeUri, String myRoleTypeUri, String othersRoleTypeUri, 187 String othersTopicTypeUri) { 188 return pl.fetchAssociationRelatedTopics(id, assocTypeUri, myRoleTypeUri, othersRoleTypeUri, 189 othersTopicTypeUri); 190 } 191 192 @Override 193 List<RelatedTopicModelImpl> getRelatedTopics(List assocTypeUris, String myRoleTypeUri, String othersRoleTypeUri, 194 String othersTopicTypeUri) { 195 return pl.fetchAssociationRelatedTopics(id, assocTypeUris, myRoleTypeUri, othersRoleTypeUri, 196 othersTopicTypeUri); 197 } 198 199 // --- 200 201 @Override 202 void storeUri() { 203 pl.storeAssociationUri(id, uri); 204 } 205 206 @Override 207 void storeTypeUri() { 208 reassignInstantiation(); 209 pl.storeAssociationTypeUri(id, typeUri); 210 } 211 212 @Override 213 void storeSimpleValue() { 214 TypeModel type = getType(); 215 pl.storeAssociationValue(id, value, type.getIndexModes(), type.getUri(), getIndexValue()); 216 } 217 218 @Override 219 void indexSimpleValue(IndexMode indexMode) { 220 pl.indexAssociationValue(id, indexMode, typeUri, getIndexValue()); 221 } 222 223 // --- 224 225 @Override 226 void updateChildTopics(ChildTopicsModel childTopics) { 227 update(mf.newAssociationModel(childTopics)); 228 } 229 230 @Override 231 void _delete() { 232 pl._deleteAssociation(id); 233 } 234 235 // --- 236 237 @Override 238 DeepaMehtaEvent getReadAccessEvent() { 239 return CoreEvent.CHECK_ASSOCIATION_READ_ACCESS; 240 } 241 242 @Override 243 DeepaMehtaEvent getPreUpdateEvent() { 244 return CoreEvent.PRE_UPDATE_ASSOCIATION; 245 } 246 247 @Override 248 DeepaMehtaEvent getPostUpdateEvent() { 249 return CoreEvent.POST_UPDATE_ASSOCIATION; 250 } 251 252 @Override 253 DeepaMehtaEvent getPreDeleteEvent() { 254 return CoreEvent.PRE_DELETE_ASSOCIATION; 255 } 256 257 @Override 258 DeepaMehtaEvent getPostDeleteEvent() { 259 return CoreEvent.POST_DELETE_ASSOCIATION; 260 } 261 262 // --- 263 264 @Override 265 Directive getUpdateDirective() { 266 return Directive.UPDATE_ASSOCIATION; 267 } 268 269 @Override 270 Directive getDeleteDirective() { 271 return Directive.DELETE_ASSOCIATION; 272 } 273 274 275 276 // === Core Internal Hooks === 277 278 @Override 279 void preCreate() { 280 duplicateCheck(); 281 } 282 283 @Override 284 void postUpdate(DeepaMehtaObjectModel newModel, DeepaMehtaObjectModel oldModel) { 285 // update association specific parts: the 2 roles 286 updateRoles((AssociationModel) newModel); 287 // 288 duplicateCheck(); 289 // 290 // Type Editor Support 291 if (isAssocDef(this)) { 292 if (isAssocDef((AssociationModel) oldModel)) { 293 updateAssocDef(); 294 } else { 295 createAssocDef(); 296 } 297 } else if (isAssocDef((AssociationModel) oldModel)) { 298 removeAssocDef(); 299 } 300 } 301 302 @Override 303 void preDelete() { 304 // Type Editor Support 305 if (isAssocDef(this)) { 306 // Note: we listen to the PRE event here, not the POST event. At POST time the assocdef sequence might be 307 // interrupted, which would result in a corrupted sequence once rebuild. (Due to the interruption, while 308 // rebuilding not all segments would be catched for deletion and recreated redundantly -> ambiguity.) 309 // ### FIXDOC 310 removeAssocDef(); 311 } 312 } 313 314 315 316 // === 317 318 /** 319 * @teturn this association's topic which plays the given role. 320 * If there is no such topic, null is returned. 321 * <p> 322 * If there are 2 such topics an exception is thrown. 323 */ 324 TopicModelImpl getTopic(String roleTypeUri) { 325 RoleModel role = getRoleModel(roleTypeUri); 326 return role instanceof TopicRoleModel ? ((TopicRoleModelImpl) role).getPlayer() : null; 327 } 328 329 TopicModelImpl getTopicByType(String topicTypeUri) { 330 TopicModelImpl topic1 = filterTopic(roleModel1, topicTypeUri); 331 TopicModelImpl topic2 = filterTopic(roleModel2, topicTypeUri); 332 if (topic1 != null && topic2 != null) { 333 throw new RuntimeException("Ambiguous getTopicByType() call: both topics are of type \"" + topicTypeUri + 334 "\" (" + this + ")"); 335 } 336 return topic1 != null ? topic1 : topic2 != null ? topic2 : null; 337 } 338 339 // --- 340 341 void updateRoleTypeUri(RoleModelImpl role, String roleTypeUri) { 342 role.setRoleTypeUri(roleTypeUri); // update memory 343 pl.storeRoleTypeUri(id, role.playerId, role.roleTypeUri); // update DB 344 } 345 346 // ------------------------------------------------------------------------------------------------- Private Methods 347 348 349 350 private void duplicateCheck() { 351 // ### FIXME: the duplicate check is supported only for topic players, and if they are identified by-ID. 352 // Note: we can't call roleModel.getPlayer() as this would build an entire object model, but its "value" 353 // is not yet available in case this association is part of the player's composite structure. 354 // Compare to DeepaMehtaUtils.associationAutoTyping() 355 if (!(roleModel1 instanceof TopicRoleModel) || ((TopicRoleModel) roleModel1).topicIdentifiedByUri() || 356 !(roleModel2 instanceof TopicRoleModel) || ((TopicRoleModel) roleModel2).topicIdentifiedByUri()) { 357 return; 358 } 359 // Note: only readable assocs (access control) are considered 360 for (AssociationModelImpl assoc : pl._getAssociations(typeUri, roleModel1.playerId, roleModel2.playerId, 361 roleModel1.roleTypeUri, roleModel2.roleTypeUri)) { 362 if (assoc.id != id) { 363 throw new RuntimeException("Duplicate: such an association exists already (ID=" + assoc.id + 364 ", typeUri=\"" + typeUri + "\")"); 365 } 366 } 367 } 368 369 370 371 // === Update (memory + DB) === 372 373 /** 374 * @param newModel The data to update. 375 * If role 1 is <code>null</code> it is not updated. 376 * If role 2 is <code>null</code> it is not updated. 377 */ 378 private void updateRoles(AssociationModel newModel) { 379 updateRole(newModel.getRoleModel1(), 1); 380 updateRole(newModel.getRoleModel2(), 2); 381 } 382 383 /** 384 * @param nr used only for logging 385 */ 386 private void updateRole(RoleModel newModel, int nr) { 387 if (newModel != null) { // abort if no update is requested 388 // Note: We must lookup the roles individually. 389 // The role order (getRole1(), getRole2()) is undeterministic and not fix. 390 RoleModelImpl role = getRole(newModel); 391 String newRoleTypeUri = newModel.getRoleTypeUri(); // new value 392 String roleTypeUri = role.getRoleTypeUri(); // current value 393 if (!roleTypeUri.equals(newRoleTypeUri)) { // has changed? 394 logger.info("### Changing role type " + nr + " from \"" + roleTypeUri + "\" -> \"" + newRoleTypeUri + 395 "\""); 396 updateRoleTypeUri(role, newRoleTypeUri); 397 } 398 } 399 } 400 401 402 403 // === Roles (memory access) === 404 405 /** 406 * Returns this association's role which refers to the same object as the given role model. 407 * The role returned is found by comparing topic IDs, topic URIs, or association IDs. 408 * The role types are <i>not</i> compared. 409 * <p> 410 * If the object refered by the given role model is not a player in this association an exception is thrown. 411 */ 412 private RoleModelImpl getRole(RoleModel roleModel) { 413 if (roleModel1.refsSameObject(roleModel)) { 414 return roleModel1; 415 } else if (roleModel2.refsSameObject(roleModel)) { 416 return roleModel2; 417 } 418 throw new RuntimeException("Role is not part of association (role=" + roleModel + ", association=" + this); 419 } 420 421 // --- 422 423 private TopicModelImpl filterTopic(RoleModelImpl role, String topicTypeUri) { 424 if (role instanceof TopicRoleModel) { 425 TopicModelImpl topic = ((TopicRoleModelImpl) role).getPlayer(); 426 if (topic.getTypeUri().equals(topicTypeUri)) { 427 return topic; 428 } 429 } 430 return null; 431 } 432 433 434 435 // === Store (DB only) === 436 437 private void reassignInstantiation() { 438 // remove current assignment 439 fetchInstantiation().delete(); 440 // create new assignment 441 pl.createAssociationInstantiation(id, typeUri); 442 } 443 444 private AssociationModelImpl fetchInstantiation() { 445 RelatedTopicModelImpl assocType = getRelatedTopic("dm4.core.instantiation", "dm4.core.instance", 446 "dm4.core.type", "dm4.core.assoc_type"); 447 // 448 if (assocType == null) { 449 throw new RuntimeException("Association " + id + " is not associated to an association type"); 450 } 451 // 452 return assocType.getRelatingAssociation(); 453 } 454 455 456 457 // === Type Editor Support === 458 459 // ### TODO: explain 460 461 private void createAssocDef() { 462 TypeModelImpl parentType = fetchParentType(); 463 logger.info("##### Adding association definition " + id + " to type \"" + parentType.getUri() + "\""); 464 // 465 parentType._addAssocDef(this); 466 } 467 468 private void updateAssocDef() { 469 TypeModelImpl parentType = fetchParentType(); 470 logger.info("##### Updating association definition " + id + " of type \"" + parentType.getUri() + "\""); 471 // 472 parentType._updateAssocDef(this); 473 } 474 475 private void removeAssocDef() { 476 TypeModelImpl parentType = fetchParentType(); 477 logger.info("##### Removing association definition " + id + " from type \"" + parentType.getUri() + "\""); 478 // 479 parentType._removeAssocDefFromMemoryAndRebuildSequence(this); 480 } 481 482 // --- 483 484 private boolean isAssocDef(AssociationModel assoc) { 485 String typeUri = assoc.getTypeUri(); 486 if (!typeUri.equals("dm4.core.aggregation_def") && 487 !typeUri.equals("dm4.core.composition_def")) { 488 return false; 489 } 490 // 491 if (assoc.hasSameRoleTypeUris()) { 492 return false; 493 } 494 if (assoc.getRoleModel("dm4.core.parent_type") == null || 495 assoc.getRoleModel("dm4.core.child_type") == null) { 496 return false; 497 } 498 // 499 return true; 500 } 501 502 private TypeModelImpl fetchParentType() { 503 return pl.typeStorage.fetchParentType(this); 504 } 505}