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