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 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 // Note: the sequence is fetched in 2 situations: 448 // 1) When fetching a type's association definitions. 449 // In this situation we don't have a Type object at hand but a sole type topic. 450 // 2) When deleting a sequence in order to rebuild it. 451 private List<RelatedAssociationModel> fetchSequence(Topic typeTopic) { 452 try { 453 List<RelatedAssociationModel> sequence = new ArrayList(); 454 // find sequence start 455 RelatedAssociation assocDef = typeTopic.getRelatedAssociation("dm4.core.aggregation", "dm4.core.type", 456 "dm4.core.sequence_start", null, false, false); // othersAssocTypeUri=null 457 // fetch sequence segments 458 if (assocDef != null) { 459 sequence.add(assocDef.getModel()); 460 while ((assocDef = assocDef.getRelatedAssociation("dm4.core.sequence", "dm4.core.predecessor", 461 "dm4.core.successor")) != null) { 462 // 463 sequence.add(assocDef.getModel()); 464 } 465 } 466 // 467 return sequence; 468 } catch (Exception e) { 469 throw new RuntimeException("Fetching sequence for type \"" + typeTopic.getUri() + "\" failed", e); 470 } 471 } 472 473 // --- Store --- 474 475 private void storeSequence(String typeUri, Collection<AssociationDefinitionModel> assocDefs) { 476 logger.info("### Storing " + assocDefs.size() + " sequence segments for type \"" + typeUri + "\""); 477 AssociationDefinitionModel predecessor = null; 478 for (AssociationDefinitionModel assocDef : assocDefs) { 479 appendToSequence(typeUri, assocDef, predecessor); 480 predecessor = assocDef; 481 } 482 } 483 484 void appendToSequence(String typeUri, AssociationDefinitionModel assocDef, AssociationDefinitionModel predecessor) { 485 if (predecessor == null) { 486 storeSequenceStart(typeUri, assocDef.getId()); 487 } else { 488 storeSequenceSegment(predecessor.getId(), assocDef.getId()); 489 } 490 } 491 492 private void storeSequenceStart(String typeUri, long assocDefId) { 493 dms.createAssociation("dm4.core.aggregation", 494 new TopicRoleModel(typeUri, "dm4.core.type"), 495 new AssociationRoleModel(assocDefId, "dm4.core.sequence_start")); 496 } 497 498 private void storeSequenceSegment(long predAssocDefId, long succAssocDefId) { 499 dms.createAssociation("dm4.core.sequence", 500 new AssociationRoleModel(predAssocDefId, "dm4.core.predecessor"), 501 new AssociationRoleModel(succAssocDefId, "dm4.core.successor")); 502 } 503 504 // --- 505 506 void rebuildSequence(Type type) { 507 deleteSequence(type); 508 storeSequence(type.getUri(), type.getModel().getAssocDefs()); 509 } 510 511 private void deleteSequence(Topic typeTopic) { 512 List<RelatedAssociationModel> sequence = fetchSequence(typeTopic); 513 logger.info("### Deleting " + sequence.size() + " sequence segments of type \"" + typeTopic.getUri() + "\""); 514 for (RelatedAssociationModel assoc : sequence) { 515 long assocId = assoc.getRelatingAssociation().getId(); 516 dms.deleteAssociation(assocId); 517 } 518 } 519 520 521 522 // === Label Configuration === 523 524 // --- Fetch --- 525 526 private List<String> fetchLabelConfig(List<AssociationDefinitionModel> assocDefs) { 527 List<String> labelConfig = new ArrayList(); 528 for (AssociationDefinitionModel assocDef : assocDefs) { 529 RelatedTopicModel includeInLabel = fetchLabelConfigTopic(assocDef.getId()); 530 if (includeInLabel != null && includeInLabel.getSimpleValue().booleanValue()) { 531 labelConfig.add(assocDef.getChildTypeUri()); 532 } 533 } 534 return labelConfig; 535 } 536 537 private RelatedTopicModel fetchLabelConfigTopic(long assocDefId) { 538 return dms.storageDecorator.fetchAssociationRelatedTopic(assocDefId, "dm4.core.composition", 539 "dm4.core.parent", "dm4.core.child", "dm4.core.include_in_label"); 540 } 541 542 // --- Store --- 543 544 void storeLabelConfig(List<String> labelConfig, Collection<AssociationDefinitionModel> assocDefs, 545 Directives directives) { 546 for (AssociationDefinitionModel assocDef : assocDefs) { 547 boolean includeInLabel = labelConfig.contains(assocDef.getChildTypeUri()); 548 new AttachedAssociationDefinition(assocDef, dms).getCompositeValue() 549 .set("dm4.core.include_in_label", includeInLabel, null, directives); // clientState=null 550 } 551 } 552 553 554 555 // === View Configurations === 556 557 // --- Fetch --- 558 559 private ViewConfigurationModel fetchTypeViewConfig(Topic typeTopic) { 560 try { 561 // Note: othersTopicTypeUri=null, the view config's topic type is unknown (it is client-specific) 562 ResultList<RelatedTopic> configTopics = typeTopic.getRelatedTopics("dm4.core.aggregation", 563 "dm4.core.type", "dm4.core.view_config", null, true, false, 0); 564 return new ViewConfigurationModel(DeepaMehtaUtils.toTopicModels(configTopics.getItems())); 565 } catch (Exception e) { 566 throw new RuntimeException("Fetching view configuration for type \"" + typeTopic.getUri() + 567 "\" failed", e); 568 } 569 } 570 571 private ViewConfigurationModel fetchAssocDefViewConfig(Association assocDef) { 572 try { 573 // Note: othersTopicTypeUri=null, the view config's topic type is unknown (it is client-specific) 574 ResultList<RelatedTopic> configTopics = assocDef.getRelatedTopics("dm4.core.aggregation", 575 "dm4.core.assoc_def", "dm4.core.view_config", null, true, false, 0); 576 return new ViewConfigurationModel(DeepaMehtaUtils.toTopicModels(configTopics.getItems())); 577 } catch (Exception e) { 578 throw new RuntimeException("Fetching view configuration for association definition " + assocDef.getId() + 579 " failed", e); 580 } 581 } 582 583 // --- 584 585 private RelatedTopicModel fetchTypeViewConfigTopic(long typeId, String configTypeUri) { 586 // Note: the composite is not fetched as it is not needed 587 return dms.storageDecorator.fetchTopicRelatedTopic(typeId, "dm4.core.aggregation", 588 "dm4.core.type", "dm4.core.view_config", configTypeUri); 589 } 590 591 private RelatedTopicModel fetchAssocDefViewConfigTopic(long assocDefId, String configTypeUri) { 592 // Note: the composite is not fetched as it is not needed 593 return dms.storageDecorator.fetchAssociationRelatedTopic(assocDefId, "dm4.core.aggregation", 594 "dm4.core.assoc_def", "dm4.core.view_config", configTypeUri); 595 } 596 597 // --- 598 599 private TopicModel fetchViewConfigTopic(RoleModel configurable, String configTypeUri) { 600 if (configurable instanceof TopicRoleModel) { 601 long typeId = configurable.getPlayerId(); 602 return fetchTypeViewConfigTopic(typeId, configTypeUri); 603 } else if (configurable instanceof AssociationRoleModel) { 604 long assocDefId = configurable.getPlayerId(); 605 return fetchAssocDefViewConfigTopic(assocDefId, configTypeUri); 606 } else { 607 throw new RuntimeException("Unexpected configurable: " + configurable); 608 } 609 } 610 611 // --- Store --- 612 613 private void storeViewConfig(RoleModel configurable, ViewConfigurationModel viewConfig) { 614 try { 615 for (TopicModel configTopic : viewConfig.getConfigTopics()) { 616 storeViewConfigTopic(configurable, configTopic); 617 } 618 } catch (Exception e) { 619 throw new RuntimeException("Storing view configuration failed (configurable=" + configurable + ")", e); 620 } 621 } 622 623 void storeViewConfigTopic(RoleModel configurable, TopicModel configTopic) { 624 // Note: null is passed as clientState. Called only (indirectly) from a migration ### FIXME: is this true? 625 // and in a migration we have no clientState anyway. 626 Topic topic = dms.createTopic(configTopic, null); // clientState=null 627 dms.createAssociation("dm4.core.aggregation", configurable, 628 new TopicRoleModel(topic.getId(), "dm4.core.view_config")); 629 } 630 631 // --- 632 633 /** 634 * Prerequisite: for the configurable a config topic of type configTypeUri exists in the DB. 635 */ 636 void storeViewConfigSetting(RoleModel configurable, String configTypeUri, String settingUri, Object value) { 637 try { 638 TopicModel configTopic = fetchViewConfigTopic(configurable, configTypeUri); 639 // ### TODO: do not create an attached topic here. Can we use the value storage? 640 new AttachedTopic(configTopic, dms).getCompositeValue().set(settingUri, value, null, new Directives()); 641 } catch (Exception e) { 642 throw new RuntimeException("Storing view configuration setting failed (configurable=" + configurable + 643 ", configTypeUri=\"" + configTypeUri + "\", settingUri=\"" + settingUri + "\", value=\"" + value + 644 "\")", e); 645 } 646 } 647 648 // --- Helper --- 649 650 RoleModel createConfigurableType(long typeId) { 651 return new TopicRoleModel(typeId, "dm4.core.type"); 652 } 653 654 RoleModel createConfigurableAssocDef(long assocDefId) { 655 return new AssociationRoleModel(assocDefId, "dm4.core.assoc_def"); 656 } 657 }