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