001 package de.deepamehta.core.impl; 002 003 import de.deepamehta.core.Association; 004 import de.deepamehta.core.AssociationType; 005 import de.deepamehta.core.AssociationDefinition; 006 import de.deepamehta.core.RelatedAssociation; 007 import de.deepamehta.core.RelatedTopic; 008 import de.deepamehta.core.Topic; 009 import de.deepamehta.core.TopicType; 010 import de.deepamehta.core.Type; 011 import de.deepamehta.core.model.AssociationDefinitionModel; 012 import de.deepamehta.core.model.AssociationModel; 013 import de.deepamehta.core.model.AssociationRoleModel; 014 import de.deepamehta.core.model.AssociationTypeModel; 015 import de.deepamehta.core.model.IndexMode; 016 import de.deepamehta.core.model.RelatedAssociationModel; 017 import de.deepamehta.core.model.RelatedTopicModel; 018 import de.deepamehta.core.model.RoleModel; 019 import de.deepamehta.core.model.SimpleValue; 020 import de.deepamehta.core.model.TopicModel; 021 import de.deepamehta.core.model.TopicRoleModel; 022 import de.deepamehta.core.model.TopicTypeModel; 023 import de.deepamehta.core.model.TypeModel; 024 import de.deepamehta.core.model.ViewConfigurationModel; 025 import de.deepamehta.core.service.ResultList; 026 import de.deepamehta.core.service.TypeStorage; 027 import de.deepamehta.core.util.DeepaMehtaUtils; 028 029 import static java.util.Arrays.asList; 030 import java.util.ArrayList; 031 import java.util.Collection; 032 import java.util.HashMap; 033 import java.util.List; 034 import java.util.Map; 035 import java.util.logging.Logger; 036 037 038 039 /** 040 * Storage-impl agnostic support for fetching/storing type models. 041 */ 042 class TypeStorageImpl implements TypeStorage { 043 044 // ---------------------------------------------------------------------------------------------- Instance Variables 045 046 private Map<String, TypeModel> typeCache = new HashMap(); 047 048 private EmbeddedService dms; 049 050 private Logger logger = Logger.getLogger(getClass().getName()); 051 052 // ---------------------------------------------------------------------------------------------------- Constructors 053 054 TypeStorageImpl(EmbeddedService dms) { 055 this.dms = dms; 056 } 057 058 // --------------------------------------------------------------------------------------------------------- Methods 059 060 061 062 // === Type Model Cache === 063 064 private TypeModel getType(String typeUri) { 065 return typeCache.get(typeUri); 066 } 067 068 private void putInTypeCache(TypeModel type) { 069 typeCache.put(type.getUri(), type); 070 } 071 072 // --- 073 074 TopicTypeModel getTopicType(String topicTypeUri) { 075 TopicTypeModel topicType = (TopicTypeModel) getType(topicTypeUri); 076 return topicType != null ? topicType : fetchTopicType(topicTypeUri); 077 } 078 079 AssociationTypeModel getAssociationType(String assocTypeUri) { 080 AssociationTypeModel assocType = (AssociationTypeModel) getType(assocTypeUri); 081 return assocType != null ? assocType : fetchAssociationType(assocTypeUri); 082 } 083 084 085 086 // === Types === 087 088 // --- Fetch --- 089 090 // ### TODO: unify with next method 091 private TopicTypeModel fetchTopicType(String topicTypeUri) { 092 Topic typeTopic = dms.getTopic("uri", new SimpleValue(topicTypeUri)); 093 checkTopicType(topicTypeUri, typeTopic); 094 // 095 // 1) fetch type components 096 String dataTypeUri = fetchDataTypeTopic(typeTopic.getId(), topicTypeUri, "topic type").getUri(); 097 List<IndexMode> indexModes = fetchIndexModes(typeTopic.getId()); 098 List<AssociationDefinitionModel> assocDefs = fetchAssociationDefinitions(typeTopic); 099 List<String> labelConfig = fetchLabelConfig(assocDefs); 100 ViewConfigurationModel viewConfig = fetchTypeViewConfig(typeTopic); 101 // 102 // 2) build type model 103 TopicTypeModel topicType = new TopicTypeModel(typeTopic.getModel(), dataTypeUri, indexModes, 104 assocDefs, labelConfig, viewConfig); 105 // 106 // 3) put in type cache 107 putInTypeCache(topicType); 108 // 109 return topicType; 110 } 111 112 // ### TODO: unify with previous method 113 private AssociationTypeModel fetchAssociationType(String assocTypeUri) { 114 Topic typeTopic = dms.getTopic("uri", new SimpleValue(assocTypeUri)); 115 checkAssociationType(assocTypeUri, typeTopic); 116 // 117 // 1) fetch type components 118 String dataTypeUri = fetchDataTypeTopic(typeTopic.getId(), assocTypeUri, "association type").getUri(); 119 List<IndexMode> indexModes = fetchIndexModes(typeTopic.getId()); 120 List<AssociationDefinitionModel> assocDefs = fetchAssociationDefinitions(typeTopic); 121 List<String> labelConfig = fetchLabelConfig(assocDefs); 122 ViewConfigurationModel viewConfig = fetchTypeViewConfig(typeTopic); 123 // 124 // 2) build type model 125 AssociationTypeModel assocType = new AssociationTypeModel(typeTopic.getModel(), dataTypeUri, indexModes, 126 assocDefs, labelConfig, viewConfig); 127 // 128 // 3) put in type cache 129 putInTypeCache(assocType); 130 // 131 return assocType; 132 } 133 134 // --- 135 136 private void checkTopicType(String topicTypeUri, Topic typeTopic) { 137 if (typeTopic == null) { 138 throw new RuntimeException("Topic type \"" + topicTypeUri + "\" not found in DB"); 139 } else if (!typeTopic.getTypeUri().equals("dm4.core.topic_type") && 140 !typeTopic.getTypeUri().equals("dm4.core.meta_type") && 141 !typeTopic.getTypeUri().equals("dm4.core.meta_meta_type")) { 142 throw new RuntimeException("URI \"" + topicTypeUri + "\" refers to a \"" + typeTopic.getTypeUri() + 143 "\" when the caller expects a \"dm4.core.topic_type\""); 144 } 145 } 146 147 private void checkAssociationType(String assocTypeUri, Topic typeTopic) { 148 if (typeTopic == null) { 149 throw new RuntimeException("Association type \"" + assocTypeUri + "\" not found in DB"); 150 } else if (!typeTopic.getTypeUri().equals("dm4.core.assoc_type")) { 151 throw new RuntimeException("URI \"" + assocTypeUri + "\" refers to a \"" + typeTopic.getTypeUri() + 152 "\" when the caller expects a \"dm4.core.assoc_type\""); 153 } 154 } 155 156 // --- Store --- 157 158 /** 159 * Stores the type-specific parts of the given type model. 160 * Prerequisite: the generic topic parts are stored already. 161 * <p> 162 * Called to store a newly created topic type or association type. 163 */ 164 void storeType(TypeModel type) { 165 // 1) put in type cache 166 // Note: an association type must be put in type cache *before* storing its association definitions. 167 // Consider creation of association type "Composition Definition": it has a composition definition itself. 168 putInTypeCache(type); 169 // 170 // 2) store type-specific parts 171 storeDataType(type.getUri(), type.getDataTypeUri()); 172 storeIndexModes(type.getUri(), type.getIndexModes()); 173 storeAssocDefs(type.getUri(), type.getAssocDefs()); 174 storeLabelConfig(type.getLabelConfig(), type.getAssocDefs()); 175 storeViewConfig(createConfigurableType(type.getId()), type.getViewConfigModel()); 176 } 177 178 179 180 // === Data Type === 181 182 // --- Fetch --- 183 184 private RelatedTopicModel fetchDataTypeTopic(long typeId, String typeUri, String className) { 185 try { 186 RelatedTopicModel dataType = dms.storageDecorator.fetchTopicRelatedTopic(typeId, "dm4.core.aggregation", 187 "dm4.core.type", "dm4.core.default", "dm4.core.data_type"); 188 if (dataType == null) { 189 throw new RuntimeException("No data type topic is associated to " + className + " \"" + typeUri + "\""); 190 } 191 return dataType; 192 } catch (Exception e) { 193 throw new RuntimeException("Fetching the data type topic of " + className + " \"" + typeUri + "\" failed", 194 e); 195 } 196 } 197 198 // --- Store --- 199 200 // ### TODO: compare to low-level method EmbeddedService._associateDataType(). Remove structural similarity. 201 void storeDataType(String typeUri, String dataTypeUri) { 202 try { 203 dms.createAssociation("dm4.core.aggregation", 204 new TopicRoleModel(typeUri, "dm4.core.type"), 205 new TopicRoleModel(dataTypeUri, "dm4.core.default")); 206 } catch (Exception e) { 207 throw new RuntimeException("Associating type \"" + typeUri + "\" with data type \"" + 208 dataTypeUri + "\" failed", e); 209 } 210 } 211 212 213 214 // === Index Modes === 215 216 // --- Fetch --- 217 218 private List<IndexMode> fetchIndexModes(long typeId) { 219 ResultList<RelatedTopicModel> indexModes = dms.storageDecorator.fetchTopicRelatedTopics(typeId, 220 "dm4.core.aggregation", "dm4.core.type", "dm4.core.default", "dm4.core.index_mode", 0); 221 return IndexMode.fromTopics(indexModes.getItems()); 222 } 223 224 // --- Store --- 225 226 private void storeIndexModes(String typeUri, List<IndexMode> indexModes) { 227 for (IndexMode indexMode : indexModes) { 228 storeIndexMode(typeUri, indexMode); 229 } 230 } 231 232 void storeIndexMode(String typeUri, IndexMode indexMode) { 233 dms.createAssociation("dm4.core.aggregation", 234 new TopicRoleModel(typeUri, "dm4.core.type"), 235 new TopicRoleModel(indexMode.toUri(), "dm4.core.default")); 236 } 237 238 239 240 // === Association Definitions === 241 242 @Override 243 public void removeAssociationDefinitionFromMemoryAndRebuildSequence(Type type, String childTypeUri) { 244 ((AttachedType) type).removeAssocDefFromMemoryAndRebuildSequence(childTypeUri); 245 } 246 247 // --- Fetch --- 248 249 private List<AssociationDefinitionModel> fetchAssociationDefinitions(Topic typeTopic) { 250 Map<Long, AssociationDefinitionModel> assocDefs = fetchAssociationDefinitionsUnsorted(typeTopic); 251 List<RelatedAssociationModel> sequence = fetchSequence(typeTopic); 252 // error check 253 if (assocDefs.size() != sequence.size()) { 254 throw new RuntimeException("DB inconsistency: type \"" + typeTopic.getUri() + "\" has " + 255 assocDefs.size() + " association definitions but in sequence are " + sequence.size()); 256 } 257 // 258 return sortAssocDefs(assocDefs, DeepaMehtaUtils.idList(sequence)); 259 } 260 261 private Map<Long, AssociationDefinitionModel> fetchAssociationDefinitionsUnsorted(Topic typeTopic) { 262 Map<Long, AssociationDefinitionModel> assocDefs = new HashMap(); 263 // 264 // 1) fetch child topic types 265 // Note: we must set fetchRelatingComposite to false here. Fetching the composite of association type 266 // Composition Definition would cause an endless recursion. Composition Definition is defined through 267 // Composition Definition itself (child types "Include in Label", "Ordered"). ### FIXDOC: this is obsolete 268 // Note: "othersTopicTypeUri" is set to null. We want consider "dm4.core.topic_type" and "dm4.core.meta_type" 269 // as well (the latter required e.g. by dm4-mail) ### TODO: add a getRelatedTopics() method that takes a list 270 // of topic types. 271 ResultList<RelatedTopic> childTypes = typeTopic.getRelatedTopics(asList("dm4.core.aggregation_def", 272 "dm4.core.composition_def"), "dm4.core.parent_type", "dm4.core.child_type", null, 0); 273 // othersTopicTypeUri=null, maxResultSize=0 274 // 275 // 2) create association definitions 276 // Note: the returned map is an intermediate, hashed by ID. The actual type model is 277 // subsequently build from it by sorting the assoc def's according to the sequence IDs. 278 for (RelatedTopic childType : childTypes) { 279 AssociationDefinitionModel assocDef = fetchAssociationDefinition(childType.getRelatingAssociation(), 280 typeTopic.getUri(), childType.getUri()); 281 assocDefs.put(assocDef.getId(), assocDef); 282 } 283 return assocDefs; 284 } 285 286 // --- 287 288 @Override 289 public AssociationDefinitionModel fetchAssociationDefinition(Association assoc) { 290 return fetchAssociationDefinition(assoc, fetchParentType(assoc).getUri(), fetchChildType(assoc).getUri()); 291 } 292 293 private AssociationDefinitionModel fetchAssociationDefinition(Association assoc, String parentTypeUri, 294 String childTypeUri) { 295 try { 296 long assocId = assoc.getId(); 297 return new AssociationDefinitionModel( 298 assocId, assoc.getUri(), assoc.getTypeUri(), 299 parentTypeUri, childTypeUri, 300 fetchParentCardinality(assocId).getUri(), fetchChildCardinality(assocId).getUri(), 301 fetchAssocDefViewConfig(assoc) 302 ); 303 } catch (Exception e) { 304 throw new RuntimeException("Fetching association definition failed (parentTypeUri=\"" + parentTypeUri + 305 "\", childTypeUri=" + childTypeUri + ", " + assoc + ")", e); 306 } 307 } 308 309 // --- 310 311 private List<AssociationDefinitionModel> sortAssocDefs(Map<Long, AssociationDefinitionModel> assocDefs, 312 List<Long> sequence) { 313 List<AssociationDefinitionModel> sortedAssocDefs = new ArrayList(); 314 for (long assocDefId : sequence) { 315 AssociationDefinitionModel assocDef = assocDefs.get(assocDefId); 316 // error check 317 if (assocDef == null) { 318 throw new RuntimeException("DB inconsistency: ID " + assocDefId + 319 " is in sequence but not in the type's association definitions"); 320 } 321 sortedAssocDefs.add(assocDef); 322 } 323 return sortedAssocDefs; 324 } 325 326 // --- Store --- 327 328 private void storeAssocDefs(String typeUri, Collection<AssociationDefinitionModel> assocDefs) { 329 for (AssociationDefinitionModel assocDef : assocDefs) { 330 storeAssociationDefinition(assocDef); 331 } 332 storeSequence(typeUri, assocDefs); 333 } 334 335 void storeAssociationDefinition(AssociationDefinitionModel assocDef) { 336 try { 337 // Note: creating the underlying association is conditional. It exists already for 338 // an interactively created association definition. Its ID is already set. 339 if (assocDef.getId() == -1) { 340 dms.createAssociation(assocDef); 341 } 342 // Note: the assoc def ID is known only after creating the association 343 long assocDefId = assocDef.getId(); 344 // cardinality 345 associateParentCardinality(assocDefId, assocDef.getParentCardinalityUri()); 346 associateChildCardinality(assocDefId, assocDef.getChildCardinalityUri()); 347 // 348 storeViewConfig(createConfigurableAssocDef(assocDefId), assocDef.getViewConfigModel()); 349 } catch (Exception e) { 350 throw new RuntimeException("Storing association definition \"" + assocDef.getChildTypeUri() + 351 "\" of type \"" + assocDef.getParentTypeUri() + "\" failed", e); 352 } 353 } 354 355 356 357 // === Parent Type / Child Type === 358 359 // --- Fetch --- 360 361 @Override 362 public Topic fetchParentType(Association assoc) { 363 Topic parentTypeTopic = assoc.getTopic("dm4.core.parent_type"); 364 // error check 365 if (parentTypeTopic == null) { 366 throw new RuntimeException("Invalid association definition: topic role dm4.core.parent_type " + 367 "is missing in " + assoc); 368 } 369 // 370 return parentTypeTopic; 371 } 372 373 @Override 374 public Topic fetchChildType(Association assoc) { 375 Topic childTypeTopic = assoc.getTopic("dm4.core.child_type"); 376 // error check 377 if (childTypeTopic == null) { 378 throw new RuntimeException("Invalid association definition: topic role dm4.core.child_type " + 379 "is missing in " + assoc); 380 } 381 // 382 return childTypeTopic; 383 } 384 385 386 387 // === Cardinality === 388 389 // --- Fetch --- 390 391 // ### TODO: pass Association instead ID? 392 private RelatedTopicModel fetchParentCardinality(long assocDefId) { 393 RelatedTopicModel parentCard = dms.storageDecorator.fetchAssociationRelatedTopic(assocDefId, 394 "dm4.core.aggregation", "dm4.core.assoc_def", "dm4.core.parent_cardinality", "dm4.core.cardinality"); 395 // error check 396 if (parentCard == null) { 397 throw new RuntimeException("Invalid association definition: parent cardinality is missing (assocDefId=" + 398 assocDefId + ")"); 399 } 400 // 401 return parentCard; 402 } 403 404 // ### TODO: pass Association instead ID? 405 private RelatedTopicModel fetchChildCardinality(long assocDefId) { 406 RelatedTopicModel childCard = dms.storageDecorator.fetchAssociationRelatedTopic(assocDefId, 407 "dm4.core.aggregation", "dm4.core.assoc_def", "dm4.core.child_cardinality", "dm4.core.cardinality"); 408 // error check 409 if (childCard == null) { 410 throw new RuntimeException("Invalid association definition: child cardinality is missing (assocDefId=" + 411 assocDefId + ")"); 412 } 413 // 414 return childCard; 415 } 416 417 // --- Store --- 418 419 void storeParentCardinalityUri(long assocDefId, String parentCardinalityUri) { 420 // remove current assignment 421 long assocId = fetchParentCardinality(assocDefId).getRelatingAssociation().getId(); 422 dms.deleteAssociation(assocId); 423 // create new assignment 424 associateParentCardinality(assocDefId, parentCardinalityUri); 425 } 426 427 void storeChildCardinalityUri(long assocDefId, String childCardinalityUri) { 428 // remove current assignment 429 long assocId = fetchChildCardinality(assocDefId).getRelatingAssociation().getId(); 430 dms.deleteAssociation(assocId); 431 // create new assignment 432 associateChildCardinality(assocDefId, childCardinalityUri); 433 } 434 435 // --- 436 437 private void associateParentCardinality(long assocDefId, String parentCardinalityUri) { 438 dms.createAssociation("dm4.core.aggregation", 439 new TopicRoleModel(parentCardinalityUri, "dm4.core.parent_cardinality"), 440 new AssociationRoleModel(assocDefId, "dm4.core.assoc_def")); 441 } 442 443 private void associateChildCardinality(long assocDefId, String childCardinalityUri) { 444 dms.createAssociation("dm4.core.aggregation", 445 new TopicRoleModel(childCardinalityUri, "dm4.core.child_cardinality"), 446 new AssociationRoleModel(assocDefId, "dm4.core.assoc_def")); 447 } 448 449 450 451 // === Sequence === 452 453 // --- Fetch --- 454 455 // Note: the sequence is fetched in 2 situations: 456 // 1) When fetching a type's association definitions. 457 // In this situation we don't have a Type object at hand but a sole type topic. 458 // 2) When deleting a sequence in order to rebuild it. 459 private List<RelatedAssociationModel> fetchSequence(Topic typeTopic) { 460 try { 461 List<RelatedAssociationModel> sequence = new ArrayList(); 462 // find sequence start 463 RelatedAssociation assocDef = typeTopic.getRelatedAssociation("dm4.core.aggregation", "dm4.core.type", 464 "dm4.core.sequence_start", null); // othersAssocTypeUri=null 465 // fetch sequence segments 466 if (assocDef != null) { 467 sequence.add(assocDef.getModel()); 468 while ((assocDef = assocDef.getRelatedAssociation("dm4.core.sequence", "dm4.core.predecessor", 469 "dm4.core.successor", null)) != null) { 470 // 471 sequence.add(assocDef.getModel()); 472 } 473 } 474 // 475 return sequence; 476 } catch (Exception e) { 477 throw new RuntimeException("Fetching sequence for type \"" + typeTopic.getUri() + "\" failed", e); 478 } 479 } 480 481 // --- Store --- 482 483 private void storeSequence(String typeUri, Collection<AssociationDefinitionModel> assocDefs) { 484 logger.fine("### Storing " + assocDefs.size() + " sequence segments for type \"" + typeUri + "\""); 485 AssociationDefinitionModel predecessor = null; 486 for (AssociationDefinitionModel assocDef : assocDefs) { 487 appendToSequence(typeUri, assocDef, predecessor); 488 predecessor = assocDef; 489 } 490 } 491 492 void appendToSequence(String typeUri, AssociationDefinitionModel assocDef, AssociationDefinitionModel predecessor) { 493 if (predecessor == null) { 494 storeSequenceStart(typeUri, assocDef.getId()); 495 } else { 496 storeSequenceSegment(predecessor.getId(), assocDef.getId()); 497 } 498 } 499 500 private void storeSequenceStart(String typeUri, long assocDefId) { 501 dms.createAssociation("dm4.core.aggregation", 502 new TopicRoleModel(typeUri, "dm4.core.type"), 503 new AssociationRoleModel(assocDefId, "dm4.core.sequence_start")); 504 } 505 506 private void storeSequenceSegment(long predAssocDefId, long succAssocDefId) { 507 dms.createAssociation("dm4.core.sequence", 508 new AssociationRoleModel(predAssocDefId, "dm4.core.predecessor"), 509 new AssociationRoleModel(succAssocDefId, "dm4.core.successor")); 510 } 511 512 // --- 513 514 void rebuildSequence(Type type) { 515 deleteSequence(type); 516 storeSequence(type.getUri(), type.getModel().getAssocDefs()); 517 } 518 519 private void deleteSequence(Topic typeTopic) { 520 List<RelatedAssociationModel> sequence = fetchSequence(typeTopic); 521 logger.info("### Deleting " + sequence.size() + " sequence segments of type \"" + typeTopic.getUri() + "\""); 522 for (RelatedAssociationModel assoc : sequence) { 523 long assocId = assoc.getRelatingAssociation().getId(); 524 dms.deleteAssociation(assocId); 525 } 526 } 527 528 529 530 // === Label Configuration === 531 532 // --- Fetch --- 533 534 private List<String> fetchLabelConfig(List<AssociationDefinitionModel> assocDefs) { 535 List<String> labelConfig = new ArrayList(); 536 for (AssociationDefinitionModel assocDef : assocDefs) { 537 RelatedTopicModel includeInLabel = fetchLabelConfigTopic(assocDef.getId()); 538 if (includeInLabel != null && includeInLabel.getSimpleValue().booleanValue()) { 539 labelConfig.add(assocDef.getChildTypeUri()); 540 } 541 } 542 return labelConfig; 543 } 544 545 private RelatedTopicModel fetchLabelConfigTopic(long assocDefId) { 546 return dms.storageDecorator.fetchAssociationRelatedTopic(assocDefId, "dm4.core.composition", 547 "dm4.core.parent", "dm4.core.child", "dm4.core.include_in_label"); 548 } 549 550 // --- Store --- 551 552 void storeLabelConfig(List<String> labelConfig, Collection<AssociationDefinitionModel> assocDefs) { 553 for (AssociationDefinitionModel assocDef : assocDefs) { 554 boolean includeInLabel = labelConfig.contains(assocDef.getChildTypeUri()); 555 new AttachedAssociationDefinition(assocDef, dms).getChildTopics() 556 .set("dm4.core.include_in_label", includeInLabel); 557 } 558 } 559 560 561 562 // === View Configurations === 563 564 // --- Fetch --- 565 566 private ViewConfigurationModel fetchTypeViewConfig(Topic typeTopic) { 567 try { 568 // Note: othersTopicTypeUri=null, the view config's topic type is unknown (it is client-specific) 569 ResultList<RelatedTopic> configTopics = typeTopic.getRelatedTopics("dm4.core.aggregation", 570 "dm4.core.type", "dm4.core.view_config", null, 0).loadChildTopics(); 571 return new ViewConfigurationModel(DeepaMehtaUtils.toTopicModels(configTopics)); 572 } catch (Exception e) { 573 throw new RuntimeException("Fetching view configuration for type \"" + typeTopic.getUri() + 574 "\" failed", e); 575 } 576 } 577 578 private ViewConfigurationModel fetchAssocDefViewConfig(Association assocDef) { 579 try { 580 // Note: othersTopicTypeUri=null, the view config's topic type is unknown (it is client-specific) 581 ResultList<RelatedTopic> configTopics = assocDef.getRelatedTopics("dm4.core.aggregation", 582 "dm4.core.assoc_def", "dm4.core.view_config", null, 0).loadChildTopics(); 583 return new ViewConfigurationModel(DeepaMehtaUtils.toTopicModels(configTopics)); 584 } catch (Exception e) { 585 throw new RuntimeException("Fetching view configuration for association definition " + assocDef.getId() + 586 " failed", e); 587 } 588 } 589 590 // --- 591 592 private RelatedTopicModel fetchTypeViewConfigTopic(long typeId, String configTypeUri) { 593 // Note: the child topics are not fetched as they are not needed 594 return dms.storageDecorator.fetchTopicRelatedTopic(typeId, "dm4.core.aggregation", 595 "dm4.core.type", "dm4.core.view_config", configTypeUri); 596 } 597 598 private RelatedTopicModel fetchAssocDefViewConfigTopic(long assocDefId, String configTypeUri) { 599 // Note: the child topics are not fetched as they are not needed 600 return dms.storageDecorator.fetchAssociationRelatedTopic(assocDefId, "dm4.core.aggregation", 601 "dm4.core.assoc_def", "dm4.core.view_config", configTypeUri); 602 } 603 604 // --- 605 606 private TopicModel fetchViewConfigTopic(RoleModel configurable, String configTypeUri) { 607 if (configurable instanceof TopicRoleModel) { 608 long typeId = configurable.getPlayerId(); 609 return fetchTypeViewConfigTopic(typeId, configTypeUri); 610 } else if (configurable instanceof AssociationRoleModel) { 611 long assocDefId = configurable.getPlayerId(); 612 return fetchAssocDefViewConfigTopic(assocDefId, configTypeUri); 613 } else { 614 throw new RuntimeException("Unexpected configurable: " + configurable); 615 } 616 } 617 618 // --- Store --- 619 620 private void storeViewConfig(RoleModel configurable, ViewConfigurationModel viewConfig) { 621 try { 622 for (TopicModel configTopic : viewConfig.getConfigTopics()) { 623 storeViewConfigTopic(configurable, configTopic); 624 } 625 } catch (Exception e) { 626 throw new RuntimeException("Storing view configuration failed (configurable=" + configurable + ")", e); 627 } 628 } 629 630 void storeViewConfigTopic(RoleModel configurable, TopicModel configTopic) { 631 Topic topic = dms.createTopic(configTopic); 632 dms.createAssociation("dm4.core.aggregation", configurable, 633 new TopicRoleModel(topic.getId(), "dm4.core.view_config")); 634 } 635 636 // --- 637 638 /** 639 * Prerequisite: for the configurable a config topic of type configTypeUri exists in the DB. 640 */ 641 void storeViewConfigSetting(RoleModel configurable, String configTypeUri, String settingUri, Object value) { 642 try { 643 TopicModel configTopic = fetchViewConfigTopic(configurable, configTypeUri); 644 // ### TODO: do not create an attached topic here. Can we use the value storage? 645 new AttachedTopic(configTopic, dms).getChildTopics().set(settingUri, value); 646 } catch (Exception e) { 647 throw new RuntimeException("Storing view configuration setting failed (configurable=" + configurable + 648 ", configTypeUri=\"" + configTypeUri + "\", settingUri=\"" + settingUri + "\", value=\"" + value + 649 "\")", e); 650 } 651 } 652 653 // --- Helper --- 654 655 RoleModel createConfigurableType(long typeId) { 656 return new TopicRoleModel(typeId, "dm4.core.type"); 657 } 658 659 RoleModel createConfigurableAssocDef(long assocDefId) { 660 return new AssociationRoleModel(assocDefId, "dm4.core.assoc_def"); 661 } 662 }