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 * Helper for storing and fetching 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 void storeIndexModes(String typeUri, List<IndexMode> indexModes) { 228 for (IndexMode indexMode : indexModes) { 229 dms.createAssociation("dm4.core.aggregation", 230 new TopicRoleModel(typeUri, "dm4.core.type"), 231 new TopicRoleModel(indexMode.toUri(), "dm4.core.default")); 232 } 233 } 234 235 236 237 // === Association Definitions === 238 239 // --- Fetch --- 240 241 private List<AssociationDefinitionModel> fetchAssociationDefinitions(Topic typeTopic) { 242 Map<Long, AssociationDefinitionModel> assocDefs = fetchAssociationDefinitionsUnsorted(typeTopic); 243 List<RelatedAssociationModel> sequence = fetchSequence(typeTopic); 244 // error check 245 if (assocDefs.size() != sequence.size()) { 246 throw new RuntimeException("DB inconsistency: type \"" + typeTopic.getUri() + "\" has " + 247 assocDefs.size() + " association definitions but in sequence are " + sequence.size()); 248 } 249 // 250 return sortAssocDefs(assocDefs, DeepaMehtaUtils.idList(sequence)); 251 } 252 253 private Map<Long, AssociationDefinitionModel> fetchAssociationDefinitionsUnsorted(Topic typeTopic) { 254 Map<Long, AssociationDefinitionModel> assocDefs = new HashMap(); 255 // 256 // 1) fetch child topic types 257 // Note: we must set fetchRelatingComposite to false here. Fetching the composite of association type 258 // Composition Definition would cause an endless recursion. Composition Definition is defined through 259 // Composition Definition itself (child types "Include in Label", "Ordered"). 260 // Note: "othersTopicTypeUri" is set to null. We want consider "dm4.core.topic_type" and "dm4.core.meta_type" 261 // as well (the latter required e.g. by dm4-mail) ### TODO: add a getRelatedTopics() method that takes a list 262 // of topic types. 263 ResultList<RelatedTopic> childTypes = typeTopic.getRelatedTopics(asList("dm4.core.aggregation_def", 264 "dm4.core.composition_def"), "dm4.core.parent_type", "dm4.core.child_type", null, false, false, 0); 265 // othersTopicTypeUri=null, fetchComposite=false, fetchRelatingComposite=false, clientState=null 266 // 267 // 2) create association definitions 268 // Note: the returned map is an intermediate, hashed by ID. The actual type model is 269 // subsequently build from it by sorting the assoc def's according to the sequence IDs. 270 for (RelatedTopic childType : childTypes) { 271 AssociationDefinitionModel assocDef = fetchAssociationDefinition(childType.getRelatingAssociation(), 272 typeTopic.getUri(), childType.getUri()); 273 assocDefs.put(assocDef.getId(), assocDef); 274 } 275 return assocDefs; 276 } 277 278 // --- 279 280 @Override 281 public AssociationDefinitionModel fetchAssociationDefinition(Association assoc) { 282 return fetchAssociationDefinition(assoc, fetchParentType(assoc).getUri(), fetchChildType(assoc).getUri()); 283 } 284 285 private AssociationDefinitionModel fetchAssociationDefinition(Association assoc, String parentTypeUri, 286 String childTypeUri) { 287 try { 288 long assocId = assoc.getId(); 289 return new AssociationDefinitionModel( 290 assocId, assoc.getUri(), assoc.getTypeUri(), 291 parentTypeUri, childTypeUri, 292 fetchParentCardinality(assocId).getUri(), fetchChildCardinality(assocId).getUri(), 293 fetchAssocDefViewConfig(assoc) 294 ); 295 } catch (Exception e) { 296 throw new RuntimeException("Fetching association definition failed (parentTypeUri=\"" + parentTypeUri + 297 "\", childTypeUri=" + childTypeUri + ", " + assoc + ")", e); 298 } 299 } 300 301 // --- 302 303 private List<AssociationDefinitionModel> sortAssocDefs(Map<Long, AssociationDefinitionModel> assocDefs, 304 List<Long> sequence) { 305 List<AssociationDefinitionModel> sortedAssocDefs = new ArrayList(); 306 for (long assocDefId : sequence) { 307 AssociationDefinitionModel assocDef = assocDefs.get(assocDefId); 308 // error check 309 if (assocDef == null) { 310 throw new RuntimeException("DB inconsistency: ID " + assocDefId + 311 " is in sequence but not in the type's association definitions"); 312 } 313 sortedAssocDefs.add(assocDef); 314 } 315 return sortedAssocDefs; 316 } 317 318 // --- Store --- 319 320 private void storeAssocDefs(String typeUri, Collection<AssociationDefinitionModel> assocDefs) { 321 for (AssociationDefinitionModel assocDef : assocDefs) { 322 storeAssociationDefinition(assocDef); 323 } 324 storeSequence(typeUri, assocDefs); 325 } 326 327 void storeAssociationDefinition(AssociationDefinitionModel assocDef) { 328 try { 329 // Note: creating the underlying association is conditional. It exists already for 330 // an interactively created association definition. Its ID is already set. 331 if (assocDef.getId() == -1) { 332 dms.createAssociation(assocDef, null); // clientState=null 333 } 334 // Note: the assoc def ID is known only after creating the association 335 long assocDefId = assocDef.getId(); 336 // cardinality 337 associateParentCardinality(assocDefId, assocDef.getParentCardinalityUri()); 338 associateChildCardinality(assocDefId, assocDef.getChildCardinalityUri()); 339 // 340 storeViewConfig(createConfigurableAssocDef(assocDefId), assocDef.getViewConfigModel()); 341 } catch (Exception e) { 342 throw new RuntimeException("Storing association definition \"" + assocDef.getChildTypeUri() + 343 "\" of type \"" + assocDef.getParentTypeUri() + "\" failed", e); 344 } 345 } 346 347 348 349 // === Parent Type / Child Type === 350 351 // --- Fetch --- 352 353 @Override 354 public Topic fetchParentType(Association assoc) { 355 Topic parentTypeTopic = assoc.getTopic("dm4.core.parent_type"); 356 // error check 357 if (parentTypeTopic == null) { 358 throw new RuntimeException("Invalid association definition: topic role dm4.core.parent_type " + 359 "is missing in " + assoc); 360 } 361 // 362 return parentTypeTopic; 363 } 364 365 @Override 366 public Topic fetchChildType(Association assoc) { 367 Topic childTypeTopic = assoc.getTopic("dm4.core.child_type"); 368 // error check 369 if (childTypeTopic == null) { 370 throw new RuntimeException("Invalid association definition: topic role dm4.core.child_type " + 371 "is missing in " + assoc); 372 } 373 // 374 return childTypeTopic; 375 } 376 377 378 379 // === Cardinality === 380 381 // --- Fetch --- 382 383 // ### TODO: pass Association instead ID? 384 private RelatedTopicModel fetchParentCardinality(long assocDefId) { 385 RelatedTopicModel parentCard = dms.storageDecorator.fetchAssociationRelatedTopic(assocDefId, 386 "dm4.core.aggregation", "dm4.core.assoc_def", "dm4.core.parent_cardinality", "dm4.core.cardinality"); 387 // error check 388 if (parentCard == null) { 389 throw new RuntimeException("Invalid association definition: parent cardinality is missing (assocDefId=" + 390 assocDefId + ")"); 391 } 392 // 393 return parentCard; 394 } 395 396 // ### TODO: pass Association instead ID? 397 private RelatedTopicModel fetchChildCardinality(long assocDefId) { 398 RelatedTopicModel childCard = dms.storageDecorator.fetchAssociationRelatedTopic(assocDefId, 399 "dm4.core.aggregation", "dm4.core.assoc_def", "dm4.core.child_cardinality", "dm4.core.cardinality"); 400 // error check 401 if (childCard == null) { 402 throw new RuntimeException("Invalid association definition: child cardinality is missing (assocDefId=" + 403 assocDefId + ")"); 404 } 405 // 406 return childCard; 407 } 408 409 // --- Store --- 410 411 void storeParentCardinalityUri(long assocDefId, String parentCardinalityUri) { 412 // remove current assignment 413 long assocId = fetchParentCardinality(assocDefId).getRelatingAssociation().getId(); 414 dms.deleteAssociation(assocId); 415 // create new assignment 416 associateParentCardinality(assocDefId, parentCardinalityUri); 417 } 418 419 void storeChildCardinalityUri(long assocDefId, String childCardinalityUri) { 420 // remove current assignment 421 long assocId = fetchChildCardinality(assocDefId).getRelatingAssociation().getId(); 422 dms.deleteAssociation(assocId); 423 // create new assignment 424 associateChildCardinality(assocDefId, childCardinalityUri); 425 } 426 427 // --- 428 429 private void associateParentCardinality(long assocDefId, String parentCardinalityUri) { 430 dms.createAssociation("dm4.core.aggregation", 431 new TopicRoleModel(parentCardinalityUri, "dm4.core.parent_cardinality"), 432 new AssociationRoleModel(assocDefId, "dm4.core.assoc_def")); 433 } 434 435 private void associateChildCardinality(long assocDefId, String childCardinalityUri) { 436 dms.createAssociation("dm4.core.aggregation", 437 new TopicRoleModel(childCardinalityUri, "dm4.core.child_cardinality"), 438 new AssociationRoleModel(assocDefId, "dm4.core.assoc_def")); 439 } 440 441 442 443 // === Sequence === 444 445 // --- Fetch --- 446 447 List<RelatedAssociationModel> fetchSequence(Topic typeTopic) { 448 try { 449 List<RelatedAssociationModel> sequence = new ArrayList(); 450 // find sequence start 451 RelatedAssociation assocDef = typeTopic.getRelatedAssociation("dm4.core.aggregation", "dm4.core.type", 452 "dm4.core.sequence_start", null, false, false); // othersAssocTypeUri=null 453 // fetch sequence segments 454 if (assocDef != null) { 455 sequence.add(assocDef.getModel()); 456 while ((assocDef = assocDef.getRelatedAssociation("dm4.core.sequence", "dm4.core.predecessor", 457 "dm4.core.successor")) != null) { 458 // 459 sequence.add(assocDef.getModel()); 460 } 461 } 462 // 463 return sequence; 464 } catch (Exception e) { 465 throw new RuntimeException("Fetching sequence for type \"" + typeTopic.getUri() + "\" failed", e); 466 } 467 } 468 469 // --- Store --- 470 471 private void storeSequence(String typeUri, Collection<AssociationDefinitionModel> assocDefs) { 472 logger.fine("Storing " + assocDefs.size() + " sequence segments for type \"" + typeUri + "\""); 473 AssociationDefinitionModel predecessor = null; 474 for (AssociationDefinitionModel assocDef : assocDefs) { 475 appendToSequence(typeUri, assocDef, predecessor); 476 predecessor = assocDef; 477 } 478 } 479 480 void appendToSequence(String typeUri, AssociationDefinitionModel assocDef, AssociationDefinitionModel predecessor) { 481 if (predecessor == null) { 482 storeSequenceStart(typeUri, assocDef.getId()); 483 } else { 484 storeSequenceSegment(predecessor.getId(), assocDef.getId()); 485 } 486 } 487 488 private void storeSequenceStart(String typeUri, long assocDefId) { 489 dms.createAssociation("dm4.core.aggregation", 490 new TopicRoleModel(typeUri, "dm4.core.type"), 491 new AssociationRoleModel(assocDefId, "dm4.core.sequence_start")); 492 } 493 494 private void storeSequenceSegment(long predAssocDefId, long succAssocDefId) { 495 dms.createAssociation("dm4.core.sequence", 496 new AssociationRoleModel(predAssocDefId, "dm4.core.predecessor"), 497 new AssociationRoleModel(succAssocDefId, "dm4.core.successor")); 498 } 499 500 // --- 501 502 void rebuildSequence(Type type) { 503 deleteSequence(type); 504 storeSequence(type.getUri(), type.getModel().getAssocDefs()); 505 } 506 507 private void deleteSequence(Topic typeTopic) { 508 List<RelatedAssociationModel> sequence = fetchSequence(typeTopic); 509 logger.info("### Deleting " + sequence.size() + " sequence segments of type \"" + typeTopic.getUri() + "\""); 510 for (RelatedAssociationModel assoc : sequence) { 511 long assocId = assoc.getRelatingAssociation().getId(); 512 dms.deleteAssociation(assocId); 513 } 514 } 515 516 517 518 // === Label Configuration === 519 520 // --- Fetch --- 521 522 private List<String> fetchLabelConfig(List<AssociationDefinitionModel> assocDefs) { 523 List<String> labelConfig = new ArrayList(); 524 for (AssociationDefinitionModel assocDef : assocDefs) { 525 RelatedTopicModel includeInLabel = fetchLabelConfigTopic(assocDef.getId()); 526 if (includeInLabel != null && includeInLabel.getSimpleValue().booleanValue()) { 527 labelConfig.add(assocDef.getChildTypeUri()); 528 } 529 } 530 return labelConfig; 531 } 532 533 private RelatedTopicModel fetchLabelConfigTopic(long assocDefId) { 534 return dms.storageDecorator.fetchAssociationRelatedTopic(assocDefId, "dm4.core.composition", 535 "dm4.core.parent", "dm4.core.child", "dm4.core.include_in_label"); 536 } 537 538 // --- Store --- 539 540 void storeLabelConfig(List<String> labelConfig, Collection<AssociationDefinitionModel> assocDefs, 541 Directives directives) { 542 for (AssociationDefinitionModel assocDef : assocDefs) { 543 boolean includeInLabel = labelConfig.contains(assocDef.getChildTypeUri()); 544 new AttachedAssociationDefinition(assocDef, dms).getCompositeValue() 545 .set("dm4.core.include_in_label", includeInLabel, null, directives); // clientState=null 546 } 547 } 548 549 550 551 // === View Configurations === 552 553 // --- Fetch --- 554 555 private ViewConfigurationModel fetchTypeViewConfig(Topic typeTopic) { 556 try { 557 // Note: othersTopicTypeUri=null, the view config's topic type is unknown (it is client-specific) 558 ResultList<RelatedTopic> configTopics = typeTopic.getRelatedTopics("dm4.core.aggregation", 559 "dm4.core.type", "dm4.core.view_config", null, true, false, 0); 560 return new ViewConfigurationModel(DeepaMehtaUtils.toTopicModels(configTopics.getItems())); 561 } catch (Exception e) { 562 throw new RuntimeException("Fetching view configuration for type \"" + typeTopic.getUri() + 563 "\" failed", e); 564 } 565 } 566 567 private ViewConfigurationModel fetchAssocDefViewConfig(Association assocDef) { 568 try { 569 // Note: othersTopicTypeUri=null, the view config's topic type is unknown (it is client-specific) 570 ResultList<RelatedTopic> configTopics = assocDef.getRelatedTopics("dm4.core.aggregation", 571 "dm4.core.assoc_def", "dm4.core.view_config", null, true, false, 0); 572 return new ViewConfigurationModel(DeepaMehtaUtils.toTopicModels(configTopics.getItems())); 573 } catch (Exception e) { 574 throw new RuntimeException("Fetching view configuration for association definition " + assocDef.getId() + 575 " failed", e); 576 } 577 } 578 579 // --- 580 581 private RelatedTopicModel fetchTypeViewConfigTopic(long typeId, String configTypeUri) { 582 // Note: the composite is not fetched as it is not needed 583 return dms.storageDecorator.fetchTopicRelatedTopic(typeId, "dm4.core.aggregation", 584 "dm4.core.type", "dm4.core.view_config", configTypeUri); 585 } 586 587 private RelatedTopicModel fetchAssocDefViewConfigTopic(long assocDefId, String configTypeUri) { 588 // Note: the composite is not fetched as it is not needed 589 return dms.storageDecorator.fetchAssociationRelatedTopic(assocDefId, "dm4.core.aggregation", 590 "dm4.core.assoc_def", "dm4.core.view_config", configTypeUri); 591 } 592 593 // --- 594 595 private TopicModel fetchViewConfigTopic(RoleModel configurable, String configTypeUri) { 596 if (configurable instanceof TopicRoleModel) { 597 long typeId = configurable.getPlayerId(); 598 return fetchTypeViewConfigTopic(typeId, configTypeUri); 599 } else if (configurable instanceof AssociationRoleModel) { 600 long assocDefId = configurable.getPlayerId(); 601 return fetchAssocDefViewConfigTopic(assocDefId, configTypeUri); 602 } else { 603 throw new RuntimeException("Unexpected configurable: " + configurable); 604 } 605 } 606 607 // --- Store --- 608 609 private void storeViewConfig(RoleModel configurable, ViewConfigurationModel viewConfig) { 610 try { 611 for (TopicModel configTopic : viewConfig.getConfigTopics()) { 612 storeViewConfigTopic(configurable, configTopic); 613 } 614 } catch (Exception e) { 615 throw new RuntimeException("Storing view configuration failed (configurable=" + configurable + ")", e); 616 } 617 } 618 619 void storeViewConfigTopic(RoleModel configurable, TopicModel configTopic) { 620 // Note: null is passed as clientState. Called only (indirectly) from a migration ### FIXME: is this true? 621 // and in a migration we have no clientState anyway. 622 Topic topic = dms.createTopic(configTopic, null); // clientState=null 623 dms.createAssociation("dm4.core.aggregation", configurable, 624 new TopicRoleModel(topic.getId(), "dm4.core.view_config")); 625 } 626 627 // --- 628 629 /** 630 * Prerequisite: for the configurable a config topic of type configTypeUri exists in the DB. 631 */ 632 void storeViewConfigSetting(RoleModel configurable, String configTypeUri, String settingUri, Object value) { 633 try { 634 TopicModel configTopic = fetchViewConfigTopic(configurable, configTypeUri); 635 // ### TODO: do not create an attached topic here. Can we use the value storage? 636 new AttachedTopic(configTopic, dms).getCompositeValue().set(settingUri, value, null, new Directives()); 637 } catch (Exception e) { 638 throw new RuntimeException("Storing view configuration setting failed (configurable=" + configurable + 639 ", configTypeUri=\"" + configTypeUri + "\", settingUri=\"" + settingUri + "\", value=\"" + value + 640 "\")", e); 641 } 642 } 643 644 // --- Helper --- 645 646 RoleModel createConfigurableType(long typeId) { 647 return new TopicRoleModel(typeId, "dm4.core.type"); 648 } 649 650 RoleModel createConfigurableAssocDef(long assocDefId) { 651 return new AssociationRoleModel(assocDefId, "dm4.core.assoc_def"); 652 } 653 }