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