001package systems.dmx.core.impl; 002 003import systems.dmx.core.Topic; 004import systems.dmx.core.model.AssociationDefinitionModel; 005import systems.dmx.core.model.AssociationModel; 006import systems.dmx.core.model.ChildTopicsModel; 007import systems.dmx.core.model.DMXObjectModel; 008import systems.dmx.core.model.IndexMode; 009import systems.dmx.core.model.RelatedTopicModel; 010import systems.dmx.core.model.RoleModel; 011import systems.dmx.core.model.SimpleValue; 012import systems.dmx.core.model.TopicModel; 013import systems.dmx.core.model.TypeModel; 014import systems.dmx.core.model.ViewConfigurationModel; 015import systems.dmx.core.util.DMXUtils; 016 017import static java.util.Arrays.asList; 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.logging.Logger; 024 025 026 027/** 028 * Storage vendor agnostic support for fetching/storing type models. 029 */ 030class TypeStorage { 031 032 // ------------------------------------------------------------------------------------------------------- Constants 033 034 // role types 035 private static final String PARENT_CARDINALITY = "dmx.core.parent_cardinality"; 036 private static final String CHILD_CARDINALITY = "dmx.core.child_cardinality"; 037 038 // ---------------------------------------------------------------------------------------------- Instance Variables 039 040 private Map<String, TypeModelImpl> typeCache = new HashMap(); 041 042 private EndlessRecursionDetection endlessRecursionDetection = new EndlessRecursionDetection(); 043 044 private PersistenceLayer pl; 045 private ModelFactoryImpl mf; 046 047 private Logger logger = Logger.getLogger(getClass().getName()); 048 049 // ---------------------------------------------------------------------------------------------------- Constructors 050 051 TypeStorage(PersistenceLayer pl) { 052 this.pl = pl; 053 this.mf = pl.mf; 054 } 055 056 // --------------------------------------------------------------------------------------------------------- Methods 057 058 059 060 // === Type Model Cache === 061 062 TopicTypeModelImpl getTopicType(String topicTypeUri) { 063 TopicTypeModelImpl topicType = (TopicTypeModelImpl) getType(topicTypeUri); 064 return topicType != null ? topicType : fetchTopicType(topicTypeUri); 065 } 066 067 AssociationTypeModelImpl getAssociationType(String assocTypeUri) { 068 AssociationTypeModelImpl assocType = (AssociationTypeModelImpl) getType(assocTypeUri); 069 return assocType != null ? assocType : fetchAssociationType(assocTypeUri); 070 } 071 072 // --- 073 074 void putInTypeCache(TypeModelImpl type) { 075 typeCache.put(type.uri, type); 076 } 077 078 void removeFromTypeCache(String typeUri) { 079 logger.info("### Removing type \"" + typeUri + "\" from type cache"); 080 if (typeCache.remove(typeUri) == null) { 081 throw new RuntimeException("Type \"" + typeUri + "\" not found in type cache"); 082 } 083 } 084 085 // --- 086 087 // ### FIXME: make private 088 TypeModelImpl getType(String typeUri) { 089 return typeCache.get(typeUri); 090 } 091 092 093 094 // === Types === 095 096 // --- Fetch --- 097 098 private TopicTypeModelImpl fetchTopicType(String topicTypeUri) { 099 try { 100 logger.info("Fetching topic type \"" + topicTypeUri + "\""); 101 endlessRecursionDetection.check(topicTypeUri); 102 // 103 // fetch generic topic 104 TopicModelImpl typeTopic = pl.fetchTopic("uri", new SimpleValue(topicTypeUri)); 105 checkTopicType(topicTypeUri, typeTopic); 106 long typeId = typeTopic.getId(); 107 // 108 // fetch type-specific parts 109 String dataTypeUri = fetchDataTypeTopic(typeId, topicTypeUri, "topic type").getUri(); 110 List<IndexMode> indexModes = fetchIndexModes(typeId); 111 List<AssociationDefinitionModel> assocDefs = fetchAssociationDefinitions(typeTopic); 112 // 113 // create and cache type model 114 TopicTypeModelImpl topicType = mf.newTopicTypeModel(typeTopic, dataTypeUri, indexModes, 115 assocDefs, null); // viewConfig=null 116 putInTypeCache(topicType); 117 // 118 // Note: the topic type "View Config" can have view configs itself. In order to avoid endless recursions 119 // the topic type "View Config" must be available in type cache *before* the view configs are fetched. 120 topicType.setViewConfig(fetchTypeViewConfig(typeTopic)); 121 fetchAssocDefsViewConfig(assocDefs); 122 // 123 return topicType; 124 } catch (Exception e) { 125 throw new RuntimeException("Fetching topic type \"" + topicTypeUri + "\" failed", e); 126 } finally { 127 endlessRecursionDetection.reset(topicTypeUri); 128 } 129 } 130 131 private AssociationTypeModelImpl fetchAssociationType(String assocTypeUri) { 132 try { 133 logger.info("Fetching association type \"" + assocTypeUri + "\""); 134 endlessRecursionDetection.check(assocTypeUri); 135 // 136 // fetch generic topic 137 TopicModelImpl typeTopic = pl.fetchTopic("uri", new SimpleValue(assocTypeUri)); 138 checkAssociationType(assocTypeUri, typeTopic); 139 long typeId = typeTopic.getId(); 140 // 141 // fetch type-specific parts 142 String dataTypeUri = fetchDataTypeTopic(typeId, assocTypeUri, "association type").getUri(); 143 List<IndexMode> indexModes = fetchIndexModes(typeId); 144 List<AssociationDefinitionModel> assocDefs = fetchAssociationDefinitions(typeTopic); 145 // 146 // create and cache type model 147 AssociationTypeModelImpl assocType = mf.newAssociationTypeModel(typeTopic, dataTypeUri, indexModes, 148 assocDefs, null); // viewConfig=null 149 putInTypeCache(assocType); 150 // 151 // Note: the topic type "View Config" can have view configs itself. In order to avoid endless recursions 152 // the topic type "View Config" must be available in type cache *before* the view configs are fetched. 153 assocType.setViewConfig(fetchTypeViewConfig(typeTopic)); 154 fetchAssocDefsViewConfig(assocDefs); 155 // 156 return assocType; 157 } catch (Exception e) { 158 throw new RuntimeException("Fetching association type \"" + assocTypeUri + "\" failed", e); 159 } finally { 160 endlessRecursionDetection.reset(assocTypeUri); 161 } 162 } 163 164 // --- 165 166 private void checkTopicType(String topicTypeUri, TopicModel typeTopic) { 167 if (typeTopic == null) { 168 throw new RuntimeException("Topic type \"" + topicTypeUri + "\" not found in DB"); 169 } else if (!typeTopic.getTypeUri().equals("dmx.core.topic_type") && 170 !typeTopic.getTypeUri().equals("dmx.core.meta_type") && 171 !typeTopic.getTypeUri().equals("dmx.core.meta_meta_type")) { 172 throw new RuntimeException("URI \"" + topicTypeUri + "\" refers to a \"" + typeTopic.getTypeUri() + 173 "\" when the caller expects a \"dmx.core.topic_type\""); 174 } 175 } 176 177 private void checkAssociationType(String assocTypeUri, TopicModel typeTopic) { 178 if (typeTopic == null) { 179 throw new RuntimeException("Association type \"" + assocTypeUri + "\" not found in DB"); 180 } else if (!typeTopic.getTypeUri().equals("dmx.core.assoc_type")) { 181 throw new RuntimeException("URI \"" + assocTypeUri + "\" refers to a \"" + typeTopic.getTypeUri() + 182 "\" when the caller expects a \"dmx.core.assoc_type\""); 183 } 184 } 185 186 // --- Store --- 187 188 /** 189 * Stores the type-specific parts of the given type model. 190 * Prerequisite: the generic topic parts are stored already. 191 * <p> 192 * Called to store a newly created topic type or association type. 193 */ 194 void storeType(TypeModelImpl type) { 195 // 1) put in type model cache 196 // Note: an association type must be put in type model cache *before* storing its association definitions. 197 // Consider creation of association type "Composition Definition": it has a composition definition itself. 198 putInTypeCache(type); 199 // 200 // 2) store type-specific parts 201 storeDataType(type.getUri(), type.getDataTypeUri()); 202 storeIndexModes(type.getUri(), type.getIndexModes()); 203 storeAssocDefs(type.getId(), type.getAssocDefs()); 204 storeViewConfig(newTypeRole(type.getId()), type.getViewConfig()); 205 } 206 207 208 209 // === Data Type === 210 211 // --- Fetch --- 212 213 private RelatedTopicModel fetchDataTypeTopic(long typeId, String typeUri, String className) { 214 try { 215 RelatedTopicModel dataType = pl.fetchTopicRelatedTopic(typeId, "dmx.core.aggregation", "dmx.core.type", 216 "dmx.core.default", "dmx.core.data_type"); 217 if (dataType == null) { 218 throw new RuntimeException("No data type topic is associated to " + className + " \"" + typeUri + "\""); 219 } 220 return dataType; 221 } catch (Exception e) { 222 throw new RuntimeException("Fetching the data type topic of " + className + " \"" + typeUri + "\" failed", 223 e); 224 } 225 } 226 227 // --- Store --- 228 229 // ### TODO: compare to low-level method CoreServiceImpl._associateDataType(). Remove structural similarity. 230 void storeDataType(String typeUri, String dataTypeUri) { 231 try { 232 pl.createAssociation("dmx.core.aggregation", 233 mf.newTopicRoleModel(typeUri, "dmx.core.type"), 234 mf.newTopicRoleModel(dataTypeUri, "dmx.core.default") 235 ); 236 } catch (Exception e) { 237 throw new RuntimeException("Associating type \"" + typeUri + "\" with data type \"" + dataTypeUri + 238 "\" failed", e); 239 } 240 } 241 242 243 244 // === Index Modes === 245 246 // --- Fetch --- 247 248 private List<IndexMode> fetchIndexModes(long typeId) { 249 List<RelatedTopicModelImpl> indexModes = pl.fetchTopicRelatedTopics(typeId, "dmx.core.aggregation", 250 "dmx.core.type", "dmx.core.default", "dmx.core.index_mode"); 251 return IndexMode.fromTopics(indexModes); 252 } 253 254 // --- Store --- 255 256 private void storeIndexModes(String typeUri, List<IndexMode> indexModes) { 257 for (IndexMode indexMode : indexModes) { 258 storeIndexMode(typeUri, indexMode); 259 } 260 } 261 262 void storeIndexMode(String typeUri, IndexMode indexMode) { 263 pl.createAssociation("dmx.core.aggregation", 264 mf.newTopicRoleModel(typeUri, "dmx.core.type"), 265 mf.newTopicRoleModel(indexMode.toUri(), "dmx.core.default") 266 ); 267 } 268 269 270 271 // === Association Definitions === 272 273 // Note: if the underlying association was an association definition before it has cardinality 274 // assignments already. These assignments are restored. Otherwise "One" is used as default. 275 private String defaultCardinalityUri(AssociationModel assoc, String cardinalityRoleTypeUri) { 276 RelatedTopicModel cardinality = fetchCardinality(assoc.getId(), cardinalityRoleTypeUri); 277 if (cardinality != null) { 278 return cardinality.getUri(); 279 } else { 280 return "dmx.core.one"; 281 } 282 } 283 284 // --- Fetch --- 285 286 private List<AssociationDefinitionModel> fetchAssociationDefinitions(TopicModelImpl typeTopic) { 287 Map<Long, AssociationDefinitionModel> assocDefs = fetchAssociationDefinitionsUnsorted(typeTopic); 288 List<RelatedAssociationModelImpl> sequence = fetchSequence(typeTopic); 289 // error check 290 if (assocDefs.size() != sequence.size()) { 291 throw new RuntimeException("DB inconsistency: type \"" + typeTopic.getUri() + "\" has " + 292 assocDefs.size() + " association definitions but in sequence are " + sequence.size()); 293 } 294 // 295 return sortAssocDefs(assocDefs, DMXUtils.idList(sequence)); 296 } 297 298 private Map<Long, AssociationDefinitionModel> fetchAssociationDefinitionsUnsorted(TopicModelImpl typeTopic) { 299 Map<Long, AssociationDefinitionModel> assocDefs = new HashMap(); 300 // 301 // 1) fetch child topic types 302 // Note: we must set fetchRelatingComposite to false here. Fetching the composite of association type 303 // Composition Definition would cause an endless recursion. Composition Definition is defined through 304 // Composition Definition itself (child types "Include in Label", "Ordered"). ### FIXDOC: this is obsolete 305 // Note: the "othersTopicTypeUri" filter is not set here (null). We want match both "dmx.core.topic_type" 306 // and "dmx.core.meta_type" (the latter is required e.g. by dmx-mail). ### TODO: add a getRelatedTopics() 307 // method that takes a list of topic types. 308 List<RelatedTopicModelImpl> childTypes = typeTopic.getRelatedTopics(asList("dmx.core.aggregation_def", 309 "dmx.core.composition_def"), "dmx.core.parent_type", "dmx.core.child_type", null); 310 // othersTopicTypeUri=null 311 // 312 // 2) create association definitions 313 // Note: the returned map is an intermediate, hashed by ID. The actual type model is 314 // subsequently build from it by sorting the assoc def's according to the sequence IDs. 315 for (RelatedTopicModel childType : childTypes) { 316 AssociationDefinitionModel assocDef = fetchAssociationDefinition(childType.getRelatingAssociation(), 317 typeTopic.getUri(), childType.getUri()); 318 assocDefs.put(assocDef.getId(), assocDef); 319 } 320 return assocDefs; 321 } 322 323 // --- 324 325 /** 326 * Creates an assoc def from an association which may or may not have been an assoc def before. 327 * This is needed when an association becomes an assoc def through retyping. 328 * <p> 329 * Note: the assoc is **not** required to identify its players by URI (by ID is OK) 330 */ 331 AssociationDefinitionModelImpl newAssociationDefinition(AssociationModelImpl assoc) { 332 // Note: we must not manipulate the assoc model in-place. The Webclient expects by-ID roles. 333 AssociationModel model = mf.newAssociationModel(assoc); 334 String parentTypeUri = fetchParentTypeTopic(assoc).getUri(); 335 String childTypeUri = fetchChildTypeTopic(assoc).getUri(); 336 prepareAssocModel(model, parentTypeUri, childTypeUri); 337 return mf.newAssociationDefinitionModel(model, 338 defaultCardinalityUri(assoc, PARENT_CARDINALITY), 339 defaultCardinalityUri(assoc, CHILD_CARDINALITY), 340 null // viewConfig=null 341 ); 342 } 343 344 // Note: the assoc is **not** required to identify its players by URI (by ID is OK) 345 private AssociationDefinitionModel fetchAssociationDefinition(AssociationModel assoc, String parentTypeUri, 346 String childTypeUri) { 347 try { 348 prepareAssocModel(assoc, parentTypeUri, childTypeUri); 349 return mf.newAssociationDefinitionModel(assoc, 350 fetchCardinalityOrThrow(assoc.getId(), PARENT_CARDINALITY).getUri(), 351 fetchCardinalityOrThrow(assoc.getId(), CHILD_CARDINALITY).getUri(), 352 null // viewConfig=null 353 ); 354 } catch (Exception e) { 355 throw new RuntimeException("Fetching assoc def failed (parentTypeUri=\"" + parentTypeUri + 356 "\", childTypeUri=\"" + childTypeUri + "\", " + assoc + ")", e); 357 } 358 } 359 360 /** 361 * Prepares an assoc model for being used as the base for an assoc def model. 362 */ 363 private void prepareAssocModel(AssociationModel assoc, String parentTypeUri, String childTypeUri) { 364 long assocDefId = assoc.getId(); 365 assoc.setRoleModel1(mf.newTopicRoleModel(parentTypeUri, "dmx.core.parent_type")); 366 assoc.setRoleModel2(mf.newTopicRoleModel(childTypeUri, "dmx.core.child_type")); 367 ChildTopicsModel childTopics = assoc.getChildTopicsModel(); 368 RelatedTopicModel customAssocType = fetchCustomAssocType(assocDefId); 369 if (customAssocType != null) { 370 childTopics.put("dmx.core.assoc_type#dmx.core.custom_assoc_type", customAssocType); 371 } 372 RelatedTopicModel isIdentityAttr = fetchIsIdentityAttr(assocDefId); 373 if (isIdentityAttr != null) { // ### TODO: should a isIdentityAttr topic always exist? 374 childTopics.put("dmx.core.identity_attr", isIdentityAttr); 375 } 376 RelatedTopicModel includeInLabel = fetchIncludeInLabel(assocDefId); 377 if (includeInLabel != null) { // ### TODO: should a includeInLabel topic always exist? 378 childTopics.put("dmx.core.include_in_label", includeInLabel); 379 } 380 } 381 382 private RelatedTopicModel fetchCustomAssocType(long assocDefId) { 383 // ### TODO: can we use model-driven retrieval? => NO! 384 // Fetching assoc type "Aggregation Definition" would run into an endless recursion while fetching 385 // its "Custom Association Type" assoc def. 386 return pl.fetchAssociationRelatedTopic(assocDefId, "dmx.core.custom_assoc_type", "dmx.core.parent", 387 "dmx.core.child", "dmx.core.assoc_type"); 388 } 389 390 private RelatedTopicModel fetchIsIdentityAttr(long assocDefId) { 391 // ### TODO: can we use model-driven retrieval? => NO! See above. 392 return pl.fetchAssociationRelatedTopic(assocDefId, "dmx.core.composition", "dmx.core.parent", 393 "dmx.core.child", "dmx.core.identity_attr"); 394 } 395 396 private RelatedTopicModel fetchIncludeInLabel(long assocDefId) { 397 // ### TODO: can we use model-driven retrieval? => NO! See above. 398 return pl.fetchAssociationRelatedTopic(assocDefId, "dmx.core.composition", "dmx.core.parent", 399 "dmx.core.child", "dmx.core.include_in_label"); 400 } 401 402 // --- 403 404 private List<AssociationDefinitionModel> sortAssocDefs(Map<Long, AssociationDefinitionModel> assocDefs, 405 List<Long> sequence) { 406 List<AssociationDefinitionModel> sortedAssocDefs = new ArrayList(); 407 for (long assocDefId : sequence) { 408 AssociationDefinitionModel assocDef = assocDefs.get(assocDefId); 409 // error check 410 if (assocDef == null) { 411 throw new RuntimeException("DB inconsistency: ID " + assocDefId + 412 " is in sequence but not in the type's association definitions"); 413 } 414 sortedAssocDefs.add(assocDef); 415 } 416 return sortedAssocDefs; 417 } 418 419 // --- Store --- 420 421 private void storeAssocDefs(long typeId, Collection<AssociationDefinitionModelImpl> assocDefs) { 422 for (AssociationDefinitionModelImpl assocDef : assocDefs) { 423 storeAssociationDefinition(assocDef); 424 } 425 storeSequence(typeId, assocDefs); 426 } 427 428 void storeAssociationDefinition(AssociationDefinitionModelImpl assocDef) { 429 try { 430 long assocDefId = assocDef.getId(); 431 // 432 // 1) create association 433 // Note: if the association definition has been created interactively the underlying association 434 // exists already. We must not create it again. We detect this case by inspecting the ID. 435 if (assocDefId == -1) { 436 assocDefId = pl.createAssociation(assocDef).getId(); 437 } 438 // 439 // 2) cardinality 440 // Note: if the underlying association was an association definition before it has cardinality 441 // assignments already. These must be removed before assigning new cardinality. 442 removeCardinalityAssignmentIfExists(assocDefId, PARENT_CARDINALITY); 443 removeCardinalityAssignmentIfExists(assocDefId, CHILD_CARDINALITY); 444 associateCardinality(assocDefId, PARENT_CARDINALITY, assocDef.getParentCardinalityUri()); 445 associateCardinality(assocDefId, CHILD_CARDINALITY, assocDef.getChildCardinalityUri()); 446 // 447 // 3) view config 448 storeViewConfig(newAssocDefRole(assocDefId), assocDef.getViewConfig()); 449 } catch (Exception e) { 450 throw new RuntimeException("Storing assoc def \"" + assocDef.getAssocDefUri() + 451 "\" failed (parent type \"" + assocDef.getParentTypeUri() + "\")", e); 452 } 453 } 454 455 456 457 // === Parent Type / Child Type === 458 459 // --- Fetch --- 460 461 /** 462 * @param assoc an association representing an association definition 463 * 464 * @return the parent type topic. 465 * A topic representing either a topic type or an association type. 466 */ 467 private TopicModel fetchParentTypeTopic(AssociationModelImpl assoc) { 468 TopicModel parentType = (TopicModel) assoc.getPlayer("dmx.core.parent_type"); 469 // error check 470 if (parentType == null) { 471 throw new RuntimeException("DB inconsistency: topic role \"dmx.core.parent_type\" is missing in " + assoc); 472 } 473 // 474 return parentType; 475 } 476 477 /** 478 * @param assoc an association representing an association definition 479 * 480 * @return the child type topic. 481 * A topic representing a topic type. 482 */ 483 private TopicModel fetchChildTypeTopic(AssociationModelImpl assoc) { 484 TopicModel childType = (TopicModel) assoc.getPlayer("dmx.core.child_type"); 485 // error check 486 if (childType == null) { 487 throw new RuntimeException("DB inconsistency: topic role \"dmx.core.child_type\" is missing in " + assoc); 488 } 489 // 490 return childType; 491 } 492 493 // --- 494 495 TypeModelImpl fetchParentType(AssociationModelImpl assoc) { 496 TopicModel type = fetchParentTypeTopic(assoc); 497 String typeUri = type.getTypeUri(); 498 if (typeUri.equals("dmx.core.topic_type")) { 499 return getTopicType(type.getUri()); 500 } else if (typeUri.equals("dmx.core.assoc_type")) { 501 return getAssociationType(type.getUri()); 502 } else { 503 throw new RuntimeException("DB inconsistency: the \"dmx.core.parent_type\" player is not a type " + 504 "but of type \"" + typeUri + "\" in " + assoc); 505 } 506 } 507 508 509 510 // === Cardinality === 511 512 // --- Fetch --- 513 514 private RelatedTopicModelImpl fetchCardinality(long assocDefId, String cardinalityRoleTypeUri) { 515 return pl.fetchAssociationRelatedTopic(assocDefId, "dmx.core.aggregation", "dmx.core.assoc_def", 516 cardinalityRoleTypeUri, "dmx.core.cardinality"); 517 } 518 519 private RelatedTopicModelImpl fetchCardinalityOrThrow(long assocDefId, String cardinalityRoleTypeUri) { 520 RelatedTopicModelImpl cardinality = fetchCardinality(assocDefId, cardinalityRoleTypeUri); 521 // error check 522 if (cardinality == null) { 523 throw new RuntimeException("DB inconsistency: assoc def " + assocDefId + " is missing a cardinality (\"" + 524 cardinalityRoleTypeUri + "\")"); 525 } 526 // 527 return cardinality; 528 } 529 530 // --- Store --- 531 532 void storeParentCardinalityUri(long assocDefId, String parentCardinalityUri) { 533 storeCardinalityUri(assocDefId, PARENT_CARDINALITY, parentCardinalityUri); 534 } 535 536 void storeChildCardinalityUri(long assocDefId, String childCardinalityUri) { 537 storeCardinalityUri(assocDefId, CHILD_CARDINALITY, childCardinalityUri); 538 } 539 540 // --- 541 542 private void storeCardinalityUri(long assocDefId, String cardinalityRoleTypeUri, String cardinalityUri) { 543 // remove current assignment 544 RelatedTopicModelImpl cardinality = fetchCardinalityOrThrow(assocDefId, cardinalityRoleTypeUri); 545 removeCardinalityAssignment(cardinality); 546 // create new assignment 547 associateCardinality(assocDefId, cardinalityRoleTypeUri, cardinalityUri); 548 } 549 550 private void removeCardinalityAssignmentIfExists(long assocDefId, String cardinalityRoleTypeUri) { 551 RelatedTopicModelImpl cardinality = fetchCardinality(assocDefId, cardinalityRoleTypeUri); 552 if (cardinality != null) { 553 removeCardinalityAssignment(cardinality); 554 } 555 } 556 557 private void removeCardinalityAssignment(RelatedTopicModelImpl cardinalityAssignment) { 558 cardinalityAssignment.getRelatingAssociation().delete(); 559 } 560 561 private void associateCardinality(long assocDefId, String cardinalityRoleTypeUri, String cardinalityUri) { 562 pl.createAssociation("dmx.core.aggregation", 563 mf.newTopicRoleModel(cardinalityUri, cardinalityRoleTypeUri), 564 mf.newAssociationRoleModel(assocDefId, "dmx.core.assoc_def") 565 ); 566 } 567 568 569 570 // === Sequence === 571 572 // --- Fetch --- 573 574 // Note: the sequence is fetched in 2 situations: 575 // 1) When fetching a type's association definitions. 576 // In this situation we don't have a DMXType object at hand but a sole type topic. 577 // 2) When deleting a sequence in order to rebuild it. 578 private List<RelatedAssociationModelImpl> fetchSequence(TopicModel typeTopic) { 579 try { 580 List<RelatedAssociationModelImpl> sequence = new ArrayList(); 581 // 582 RelatedAssociationModelImpl assocDef = fetchSequenceStart(typeTopic.getId()); 583 if (assocDef != null) { 584 sequence.add(assocDef); 585 while ((assocDef = fetchSuccessor(assocDef.getId())) != null) { 586 sequence.add(assocDef); 587 } 588 } 589 // 590 return sequence; 591 } catch (Exception e) { 592 throw new RuntimeException("Fetching sequence for type \"" + typeTopic.getUri() + "\" failed", e); 593 } 594 } 595 596 // --- 597 598 private RelatedAssociationModelImpl fetchSequenceStart(long typeId) { 599 return pl.fetchTopicRelatedAssociation(typeId, "dmx.core.aggregation", "dmx.core.type", 600 "dmx.core.sequence_start", null); // othersAssocTypeUri=null 601 } 602 603 private RelatedAssociationModelImpl fetchSuccessor(long assocDefId) { 604 return pl.fetchAssociationRelatedAssociation(assocDefId, "dmx.core.sequence", "dmx.core.predecessor", 605 "dmx.core.successor", null); // othersAssocTypeUri=null 606 } 607 608 private RelatedAssociationModelImpl fetchPredecessor(long assocDefId) { 609 return pl.fetchAssociationRelatedAssociation(assocDefId, "dmx.core.sequence", "dmx.core.successor", 610 "dmx.core.predecessor", null); // othersAssocTypeUri=null 611 } 612 613 // --- Store --- 614 615 private void storeSequence(long typeId, Collection<AssociationDefinitionModelImpl> assocDefs) { 616 logger.fine("### Storing " + assocDefs.size() + " sequence segments for type " + typeId); 617 long predAssocDefId = -1; 618 for (AssociationDefinitionModel assocDef : assocDefs) { 619 addAssocDefToSequence(typeId, assocDef.getId(), -1, -1, predAssocDefId); 620 predAssocDefId = assocDef.getId(); 621 } 622 } 623 624 /** 625 * Adds an assoc def to the sequence. Depending on the last 3 arguments either appends it at end, inserts it at 626 * start, or inserts it in the middle. 627 * 628 * @param beforeAssocDefId the ID of the assoc def <i>before</i> the assoc def is added 629 * If <code>-1</code> the assoc def is <b>appended at end</b>. 630 * In this case <code>lastAssocDefId</code> must identify the end. 631 * (<code>firstAssocDefId</code> is not relevant in this case.) 632 * @param firstAssocDefId Identifies the first assoc def. If this equals the ID of the assoc def to add 633 * the assoc def is <b>inserted at start</b>. 634 */ 635 void addAssocDefToSequence(long typeId, long assocDefId, long beforeAssocDefId, long firstAssocDefId, 636 long lastAssocDefId) { 637 if (beforeAssocDefId == -1) { 638 // append at end 639 appendToSequence(typeId, assocDefId, lastAssocDefId); 640 } else if (firstAssocDefId == assocDefId) { 641 // insert at start 642 insertAtSequenceStart(typeId, assocDefId); 643 } else { 644 // insert in the middle 645 insertIntoSequence(assocDefId, beforeAssocDefId); 646 } 647 } 648 649 private void appendToSequence(long typeId, long assocDefId, long predAssocDefId) { 650 if (predAssocDefId == -1) { 651 storeSequenceStart(typeId, assocDefId); 652 } else { 653 storeSequenceSegment(predAssocDefId, assocDefId); 654 } 655 } 656 657 private void insertAtSequenceStart(long typeId, long assocDefId) { 658 // delete sequence start 659 RelatedAssociationModelImpl assocDef = fetchSequenceStart(typeId); 660 assocDef.getRelatingAssociation().delete(); 661 // reconnect 662 storeSequenceStart(typeId, assocDefId); 663 storeSequenceSegment(assocDefId, assocDef.getId()); 664 } 665 666 private void insertIntoSequence(long assocDefId, long beforeAssocDefId) { 667 // delete sequence segment 668 RelatedAssociationModelImpl assocDef = fetchPredecessor(beforeAssocDefId); 669 assocDef.getRelatingAssociation().delete(); 670 // reconnect 671 storeSequenceSegment(assocDef.getId(), assocDefId); 672 storeSequenceSegment(assocDefId, beforeAssocDefId); 673 } 674 675 // --- 676 677 private void storeSequenceStart(long typeId, long assocDefId) { 678 pl.createAssociation("dmx.core.aggregation", 679 mf.newTopicRoleModel(typeId, "dmx.core.type"), 680 mf.newAssociationRoleModel(assocDefId, "dmx.core.sequence_start") 681 ); 682 } 683 684 private void storeSequenceSegment(long predAssocDefId, long succAssocDefId) { 685 pl.createAssociation("dmx.core.sequence", 686 mf.newAssociationRoleModel(predAssocDefId, "dmx.core.predecessor"), 687 mf.newAssociationRoleModel(succAssocDefId, "dmx.core.successor") 688 ); 689 } 690 691 // --- 692 693 void rebuildSequence(TypeModelImpl type) { 694 deleteSequence(type); 695 storeSequence(type.getId(), type.getAssocDefs()); 696 } 697 698 private void deleteSequence(TopicModel typeTopic) { 699 List<RelatedAssociationModelImpl> sequence = fetchSequence(typeTopic); 700 logger.info("### Deleting " + sequence.size() + " sequence segments of type \"" + typeTopic.getUri() + "\""); 701 for (RelatedAssociationModelImpl assoc : sequence) { 702 assoc.getRelatingAssociation().delete(); 703 } 704 } 705 706 707 708 // === View Configurations === 709 710 // --- Fetch --- 711 712 private void fetchAssocDefsViewConfig(List<AssociationDefinitionModel> assocDefs) { 713 for (AssociationDefinitionModel assocDef : assocDefs) { 714 assocDef.setViewConfig(fetchAssocDefViewConfig(assocDef)); 715 } 716 } 717 718 // --- 719 720 private ViewConfigurationModel fetchTypeViewConfig(TopicModel typeTopic) { 721 try { 722 // Note: othersTopicTypeUri=null, the view config's topic type is unknown (it is client-specific) 723 return viewConfigModel(pl.fetchTopicRelatedTopics(typeTopic.getId(), "dmx.core.aggregation", 724 "dmx.core.type", "dmx.core.view_config", null)); 725 } catch (Exception e) { 726 throw new RuntimeException("Fetching view configuration for type \"" + typeTopic.getUri() + 727 "\" failed", e); 728 } 729 } 730 731 private ViewConfigurationModel fetchAssocDefViewConfig(AssociationModel assocDef) { 732 try { 733 // Note: othersTopicTypeUri=null, the view config's topic type is unknown (it is client-specific) 734 return viewConfigModel(pl.fetchAssociationRelatedTopics(assocDef.getId(), "dmx.core.aggregation", 735 "dmx.core.assoc_def", "dmx.core.view_config", null)); 736 } catch (Exception e) { 737 throw new RuntimeException("Fetching view configuration for assoc def " + assocDef.getId() + " failed", e); 738 } 739 } 740 741 // --- 742 743 /** 744 * Creates a view config model from a bunch of config topics. 745 * Loads the child topics of the given topics and updates them in-place. 746 */ 747 private ViewConfigurationModel viewConfigModel(Iterable<? extends TopicModelImpl> configTopics) { 748 loadChildTopics(configTopics); 749 return mf.newViewConfigurationModel(configTopics); 750 } 751 752 // --- Store --- 753 754 private void storeViewConfig(RoleModel configurable, ViewConfigurationModelImpl viewConfig) { 755 try { 756 for (TopicModelImpl configTopic : viewConfig.getConfigTopics()) { 757 storeViewConfigTopic(configurable, configTopic); 758 } 759 } catch (Exception e) { 760 throw new RuntimeException("Storing view configuration failed (configurable=" + configurable + 761 ", viewConfig=" + viewConfig + ")", e); 762 } 763 } 764 765 void storeViewConfigTopic(RoleModel configurable, TopicModelImpl configTopic) { 766 pl.createTopic(configTopic); 767 pl.createAssociation("dmx.core.aggregation", configurable, 768 mf.newTopicRoleModel(configTopic.getId(), "dmx.core.view_config")); 769 } 770 771 // --- Helper --- 772 773 private void loadChildTopics(Iterable<? extends DMXObjectModelImpl> objects) { 774 for (DMXObjectModelImpl object : objects) { 775 object.loadChildTopics(true); // deep=true 776 } 777 } 778 779 // --- 780 781 RoleModel newTypeRole(long typeId) { 782 return mf.newTopicRoleModel(typeId, "dmx.core.type"); 783 } 784 785 RoleModel newAssocDefRole(long assocDefId) { 786 return mf.newAssociationRoleModel(assocDefId, "dmx.core.assoc_def"); 787 } 788 789 790 791 // ------------------------------------------------------------------------------------------------- Private Classes 792 793 private static final class EndlessRecursionDetection { 794 795 private Map<String, Boolean> loadInProgress = new HashMap(); 796 797 private void check(String typeUri) { 798 if (loadInProgress.get(typeUri) != null) { 799 throw new RuntimeException("Endless recursion detected while loading type \"" + typeUri + "\""); 800 } 801 loadInProgress.put(typeUri, true); 802 } 803 804 private void reset(String typeUri) { 805 loadInProgress.remove(typeUri); 806 } 807 } 808}