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