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