001 package de.deepamehta.core.impl; 002 003 import de.deepamehta.core.Association; 004 import de.deepamehta.core.AssociationType; 005 import de.deepamehta.core.RelatedAssociation; 006 import de.deepamehta.core.RelatedTopic; 007 import de.deepamehta.core.Role; 008 import de.deepamehta.core.Topic; 009 import de.deepamehta.core.TopicRole; 010 import de.deepamehta.core.model.AssociationModel; 011 import de.deepamehta.core.model.AssociationRoleModel; 012 import de.deepamehta.core.model.RelatedAssociationModel; 013 import de.deepamehta.core.model.RelatedTopicModel; 014 import de.deepamehta.core.model.RoleModel; 015 import de.deepamehta.core.model.TopicRoleModel; 016 import de.deepamehta.core.service.ClientState; 017 import de.deepamehta.core.service.Directive; 018 import de.deepamehta.core.service.Directives; 019 import de.deepamehta.core.service.ResultList; 020 import de.deepamehta.core.storage.spi.DeepaMehtaTransaction; 021 022 import java.util.ArrayList; 023 import java.util.List; 024 025 import java.util.logging.Logger; 026 027 028 029 /** 030 * An association model that is attached to the DB. 031 */ 032 class AttachedAssociation extends AttachedDeepaMehtaObject implements Association { 033 034 // ---------------------------------------------------------------------------------------------- Instance Variables 035 036 private Role role1; // attached object cache 037 private Role role2; // attached object cache 038 039 private Logger logger = Logger.getLogger(getClass().getName()); 040 041 // ---------------------------------------------------------------------------------------------------- Constructors 042 043 AttachedAssociation(AssociationModel model, EmbeddedService dms) { 044 super(model, dms); 045 // init attached object cache 046 this.role1 = createAttachedRole(model.getRoleModel1()); 047 this.role2 = createAttachedRole(model.getRoleModel2()); 048 } 049 050 // -------------------------------------------------------------------------------------------------- Public Methods 051 052 053 054 // ****************************************** 055 // *** AttachedDeepaMehtaObject Overrides *** 056 // ****************************************** 057 058 059 060 // === Deletion === 061 062 @Override 063 public void delete(Directives directives) { 064 DeepaMehtaTransaction tx = dms.beginTx(); 065 try { 066 // delete sub-topics and associations 067 super.delete(directives); 068 // delete association itself 069 logger.info("Deleting " + this); 070 directives.add(Directive.DELETE_ASSOCIATION, this); 071 dms.storageDecorator.deleteAssociation(getId()); 072 // 073 tx.success(); 074 } catch (IllegalStateException e) { 075 // Note: getAssociations() might throw IllegalStateException and is no problem. 076 // This can happen when this object is an association which is already deleted. 077 // 078 // Consider this particular situation: let A1 and A2 be associations of this object and let A2 point to A1. 079 // If A1 gets deleted first (the association set order is non-deterministic), A2 is implicitely deleted 080 // with it (because it is a direct association of A1 as well). Then when the loop comes to A2 081 // "IllegalStateException: Node[1327] has been deleted in this tx" is thrown because A2 has been deleted 082 // already. (The Node appearing in the exception is the middle node of A2.) If, on the other hand, A2 083 // gets deleted first no error would occur. 084 // 085 // This particular situation exists when e.g. a topicmap is deleted while one of its mapcontext 086 // associations is also a part of the topicmap itself. This originates e.g. when the user reveals 087 // a topicmap's mapcontext association and then deletes the topicmap. 088 // 089 if (e.getMessage().matches("Node\\[\\d+\\] has been deleted in this tx")) { 090 logger.info("### Association has already been deleted in this transaction. This can happen while " + 091 "deleting a topic with direct associations A1 and A2 while A2 points to A1 (" + this + ")"); 092 tx.success(); 093 } else { 094 throw e; 095 } 096 } catch (Exception e) { 097 logger.warning("ROLLBACK!"); 098 throw new RuntimeException("Deleting association failed (" + this + ")", e); 099 } finally { 100 tx.finish(); 101 } 102 } 103 104 105 106 // ********************************** 107 // *** Association Implementation *** 108 // ********************************** 109 110 111 112 @Override 113 public Role getRole1() { 114 return role1; 115 } 116 117 @Override 118 public Role getRole2() { 119 return role2; 120 } 121 122 // --- 123 124 @Override 125 public Topic getTopic(String roleTypeUri) { 126 List<Topic> topics = getTopics(roleTypeUri); 127 switch (topics.size()) { 128 case 0: 129 return null; 130 case 1: 131 return topics.get(0); 132 default: 133 throw new RuntimeException("Ambiguity in association: " + topics.size() + " topics have role type \"" + 134 roleTypeUri + "\" (" + this + ")"); 135 } 136 } 137 138 @Override 139 public List<Topic> getTopics(String roleTypeUri) { 140 List<Topic> topics = new ArrayList(); 141 filterTopic(getRole1(), roleTypeUri, topics); 142 filterTopic(getRole2(), roleTypeUri, topics); 143 return topics; 144 } 145 146 // --- 147 148 @Override 149 public Role getRole(RoleModel roleModel) { 150 if (getRole1().getModel().refsSameObject(roleModel)) { 151 return getRole1(); 152 } else if (getRole2().getModel().refsSameObject(roleModel)) { 153 return getRole2(); 154 } 155 throw new RuntimeException("Role is not part of association (role=" + roleModel + ", association=" + this); 156 } 157 158 @Override 159 public boolean isPlayer(TopicRoleModel roleModel) { 160 List<Topic> topics = getTopics(roleModel.getRoleTypeUri()); 161 return topics.size() > 0 && topics.get(0).getId() == roleModel.getPlayerId(); 162 } 163 164 // --- 165 166 @Override 167 public AssociationModel getModel() { 168 return (AssociationModel) super.getModel(); 169 } 170 171 172 173 // === Updating === 174 175 /** 176 * @param model The data to update. 177 * If the type URI is <code>null</code> it is not updated. 178 * If role 1 is <code>null</code> it is not updated. 179 * If role 2 is <code>null</code> it is not updated. 180 */ 181 @Override 182 public void update(AssociationModel model, ClientState clientState, Directives directives) { 183 logger.info("Updating association " + getId() + " (new " + model + ")"); 184 // 185 dms.fireEvent(CoreEvent.PRE_UPDATE_ASSOCIATION, this, model, directives); 186 // 187 AssociationModel oldModel = getModel().clone(); 188 super.update(model, clientState, directives); 189 updateRole(model.getRoleModel1(), 1); 190 updateRole(model.getRoleModel2(), 2); 191 // 192 addUpdateDirective(directives); 193 // 194 dms.fireEvent(CoreEvent.POST_UPDATE_ASSOCIATION, this, oldModel, clientState, directives); 195 } 196 197 198 199 // === Traversal === 200 201 @Override 202 public RelatedAssociation getRelatedAssociation(String assocTypeUri, String myRoleTypeUri, 203 String othersRoleTypeUri) { 204 RelatedAssociationModel assoc = dms.storageDecorator.fetchAssociationRelatedAssociation(getId(), 205 assocTypeUri, myRoleTypeUri, othersRoleTypeUri, null); // othersAssocTypeUri=null 206 return assoc != null ? dms.instantiateRelatedAssociation(assoc, false, false) : null; 207 // fetchComposite=false, fetchRelatingComposite=false 208 } 209 210 211 212 // *************************************** 213 // *** DeepaMehtaObject Implementation *** 214 // *************************************** 215 216 217 218 // === Traversal === 219 220 // --- Topic Retrieval --- 221 222 @Override 223 public ResultList<RelatedTopic> getRelatedTopics(List assocTypeUris, String myRoleTypeUri, String othersRoleTypeUri, 224 String othersTopicTypeUri, boolean fetchComposite, boolean fetchRelatingComposite, 225 int maxResultSize) { 226 ResultList<RelatedTopicModel> topics = dms.storageDecorator.fetchAssociationRelatedTopics(getId(), 227 assocTypeUris, myRoleTypeUri, othersRoleTypeUri, othersTopicTypeUri, maxResultSize); 228 return dms.instantiateRelatedTopics(topics, fetchComposite, fetchRelatingComposite); 229 } 230 231 // --- Association Retrieval --- 232 233 @Override 234 public Association getAssociation(String assocTypeUri, String myRoleTypeUri, String othersRoleTypeUri, 235 long othersTopicId) { 236 AssociationModel assoc = dms.storageDecorator.fetchAssociationBetweenTopicAndAssociation(assocTypeUri, 237 othersTopicId, getId(), othersRoleTypeUri, myRoleTypeUri); 238 return assoc != null ? dms.instantiateAssociation(assoc, false) : null; // fetchComposite=false 239 } 240 241 @Override 242 public List<Association> getAssociations() { 243 return dms.instantiateAssociations(dms.storageDecorator.fetchAssociationAssociations(getId()), false); 244 // fetchComposite=false 245 } 246 247 248 249 // === Properties === 250 251 @Override 252 public Object getProperty(String propUri) { 253 return dms.storageDecorator.fetchAssociationProperty(getId(), propUri); 254 } 255 256 @Override 257 public void setProperty(String propUri, Object propValue, boolean addToIndex) { 258 dms.storageDecorator.storeAssociationProperty(getId(), propUri, propValue, addToIndex); 259 } 260 261 @Override 262 public boolean hasProperty(String propUri) { 263 return dms.storageDecorator.hasAssociationProperty(getId(), propUri); 264 } 265 266 @Override 267 public void removeProperty(String propUri) { 268 dms.storageDecorator.removeAssociationProperty(getId(), propUri); 269 } 270 271 272 273 // ----------------------------------------------------------------------------------------- Package Private Methods 274 275 /** 276 * Convenience method. 277 */ 278 AssociationType getAssociationType() { 279 return (AssociationType) getType(); 280 } 281 282 283 284 // === Implementation of the abstract methods === 285 286 @Override 287 final String className() { 288 return "association"; 289 } 290 291 @Override 292 void addUpdateDirective(Directives directives) { 293 directives.add(Directive.UPDATE_ASSOCIATION, this); 294 } 295 296 @Override 297 final void storeUri() { 298 dms.storageDecorator.storeAssociationUri(getId(), getUri()); 299 } 300 301 @Override 302 final void storeTypeUri() { 303 reassignInstantiation(); 304 dms.storageDecorator.storeAssociationTypeUri(getId(), getTypeUri()); 305 } 306 307 // --- 308 309 @Override 310 final RelatedTopicModel fetchRelatedTopic(String assocTypeUri, String myRoleTypeUri, 311 String othersRoleTypeUri, String othersTopicTypeUri) { 312 return dms.storageDecorator.fetchAssociationRelatedTopic(getId(), assocTypeUri, myRoleTypeUri, 313 othersRoleTypeUri, othersTopicTypeUri); 314 } 315 316 @Override 317 final ResultList<RelatedTopicModel> fetchRelatedTopics(String assocTypeUri, String myRoleTypeUri, 318 String othersRoleTypeUri, String othersTopicTypeUri, 319 int maxResultSize) { 320 return dms.storageDecorator.fetchAssociationRelatedTopics(getId(), assocTypeUri, myRoleTypeUri, 321 othersRoleTypeUri, othersTopicTypeUri, maxResultSize); 322 } 323 324 325 326 // ------------------------------------------------------------------------------------------------- Private Methods 327 328 // --- Update --- 329 330 /** 331 * @param nr used only for logging 332 */ 333 private void updateRole(RoleModel newModel, int nr) { 334 if (newModel != null) { 335 // Note: We must lookup the roles individually. 336 // The role order (getRole1(), getRole2()) is undeterministic and not fix. 337 Role role = getRole(newModel); 338 String newRoleTypeUri = newModel.getRoleTypeUri(); // new value 339 String roleTypeUri = role.getRoleTypeUri(); // current value 340 if (!roleTypeUri.equals(newRoleTypeUri)) { // has changed? 341 logger.info("### Changing role type " + nr + " from \"" + roleTypeUri + "\" -> \"" + newRoleTypeUri + 342 "\""); 343 role.setRoleTypeUri(newRoleTypeUri); 344 } 345 } 346 } 347 348 // --- Helper --- 349 350 private Role createAttachedRole(RoleModel model) { 351 if (model instanceof TopicRoleModel) { 352 return new AttachedTopicRole((TopicRoleModel) model, this, dms); 353 } else if (model instanceof AssociationRoleModel) { 354 return new AttachedAssociationRole((AssociationRoleModel) model, this, dms); 355 } else { 356 throw new RuntimeException("Unexpected RoleModel object (" + model + ")"); 357 } 358 } 359 360 private void filterTopic(Role role, String roleTypeUri, List<Topic> topics) { 361 if (role instanceof TopicRole && role.getRoleTypeUri().equals(roleTypeUri)) { 362 topics.add(((TopicRole) role).getTopic()); 363 } 364 } 365 366 // --- 367 368 private void reassignInstantiation() { 369 // remove current assignment 370 fetchInstantiation().delete(new Directives()); // ### FIXME: receive directives as argument 371 // create new assignment 372 dms.createAssociationInstantiation(getId(), getTypeUri()); 373 } 374 375 private Association fetchInstantiation() { 376 RelatedTopic assocType = getRelatedTopic("dm4.core.instantiation", "dm4.core.instance", "dm4.core.type", 377 "dm4.core.assoc_type", false, false); 378 // 379 if (assocType == null) { 380 throw new RuntimeException("Association " + getId() + " is not associated to an association type"); 381 } 382 // 383 return assocType.getRelatingAssociation(); 384 } 385 }