001package de.deepamehta.core.impl; 002 003import de.deepamehta.core.JSONEnabled; 004import de.deepamehta.core.model.AssociationDefinitionModel; 005import de.deepamehta.core.model.AssociationModel; 006import de.deepamehta.core.model.DeepaMehtaObjectModel; 007import de.deepamehta.core.model.IndexMode; 008import de.deepamehta.core.model.TopicModel; 009import de.deepamehta.core.model.TypeModel; 010import de.deepamehta.core.model.ViewConfigurationModel; 011import de.deepamehta.core.service.Directive; 012import de.deepamehta.core.service.Directives; 013import de.deepamehta.core.util.SequencedHashMap; 014 015import org.codehaus.jettison.json.JSONArray; 016import org.codehaus.jettison.json.JSONObject; 017 018import java.util.Collection; 019import java.util.Iterator; 020import java.util.List; 021import java.util.logging.Logger; 022 023 024 025class TypeModelImpl extends TopicModelImpl implements TypeModel { 026 027 // ---------------------------------------------------------------------------------------------- Instance Variables 028 029 private String dataTypeUri; // may be null in models used for an update operation 030 private List<IndexMode> indexModes; 031 private SequencedHashMap<String, AssociationDefinitionModelImpl> assocDefs; // is never null, may be empty 032 private List<String> labelConfig; // is never null, may be empty 033 private ViewConfigurationModelImpl viewConfig; // is never null 034 035 private Logger logger = Logger.getLogger(getClass().getName()); 036 037 // ---------------------------------------------------------------------------------------------------- Constructors 038 039 TypeModelImpl(TopicModelImpl typeTopic, String dataTypeUri, List<IndexMode> indexModes, 040 List<AssociationDefinitionModel> assocDefs, List<String> labelConfig, 041 ViewConfigurationModelImpl viewConfig) { 042 super(typeTopic); 043 this.dataTypeUri = dataTypeUri; 044 this.indexModes = indexModes; 045 this.assocDefs = toMap(assocDefs); 046 this.labelConfig = labelConfig; 047 this.viewConfig = viewConfig; 048 } 049 050 TypeModelImpl(TypeModelImpl type) { 051 super(type); 052 this.dataTypeUri = type.getDataTypeUri(); 053 this.indexModes = type.getIndexModes(); 054 this.assocDefs = toMap(type.getAssocDefs()); 055 this.labelConfig = type.getLabelConfig(); 056 this.viewConfig = type.getViewConfigModel(); 057 } 058 059 // -------------------------------------------------------------------------------------------------- Public Methods 060 061 062 063 // === Data Type === 064 065 @Override 066 public String getDataTypeUri() { 067 return dataTypeUri; 068 } 069 070 @Override 071 public void setDataTypeUri(String dataTypeUri) { 072 this.dataTypeUri = dataTypeUri; 073 } 074 075 076 077 // === Index Modes === 078 079 @Override 080 public List<IndexMode> getIndexModes() { 081 return indexModes; 082 } 083 084 @Override 085 public void addIndexMode(IndexMode indexMode) { 086 indexModes.add(indexMode); 087 } 088 089 090 091 // === Association Definitions === 092 093 @Override 094 public Collection<AssociationDefinitionModelImpl> getAssocDefs() { 095 return assocDefs.values(); 096 } 097 098 @Override 099 public AssociationDefinitionModelImpl getAssocDef(String assocDefUri) { 100 return getAssocDefOrThrow(assocDefUri); 101 } 102 103 @Override 104 public boolean hasAssocDef(String assocDefUri) { 105 return _getAssocDef(assocDefUri) != null; 106 } 107 108 /** 109 * @param assocDef the assoc def to add. 110 * Note: its ID might be uninitialized (-1). 111 */ 112 @Override 113 public TypeModel addAssocDef(AssociationDefinitionModel assocDef) { 114 return addAssocDefBefore(assocDef, null); // beforeAssocDefUri=null 115 } 116 117 /** 118 * @param assocDef the assoc def to add. 119 * Note: its ID might be uninitialized (-1). 120 * @param beforeAssocDefUri the URI of the assoc def <i>before</i> the given assoc def is inserted. 121 * If <code>null</code> the assoc def is appended at the end. 122 */ 123 @Override 124 public TypeModel addAssocDefBefore(AssociationDefinitionModel assocDef, String beforeAssocDefUri) { 125 try { 126 // error check 127 String assocDefUri = assocDef.getAssocDefUri(); 128 AssociationDefinitionModel existing = _getAssocDef(assocDefUri); 129 if (existing != null) { 130 throw new RuntimeException("Ambiguity: type \"" + uri + "\" has more than one \"" + assocDefUri + 131 "\" assoc defs"); 132 } 133 // 134 assocDefs.putBefore(assocDefUri, (AssociationDefinitionModelImpl) assocDef, beforeAssocDefUri); 135 return this; 136 } catch (Exception e) { 137 throw new RuntimeException("Adding an assoc def to type \"" + uri + "\" before \"" + beforeAssocDefUri + 138 "\" failed" + assocDef, e); 139 } 140 } 141 142 @Override 143 public AssociationDefinitionModel removeAssocDef(String assocDefUri) { 144 try { 145 AssociationDefinitionModel assocDef = assocDefs.remove(assocDefUri); 146 if (assocDef == null) { 147 throw new RuntimeException("Assoc def \"" + assocDefUri + "\" not found in " + assocDefs.keySet()); 148 } 149 return assocDef; 150 } catch (Exception e) { 151 throw new RuntimeException("Removing assoc def \"" + assocDefUri + "\" from type \"" + uri + "\" failed", 152 e); 153 } 154 } 155 156 157 158 // === Label Configuration === 159 160 @Override 161 public List<String> getLabelConfig() { 162 return labelConfig; 163 } 164 165 @Override 166 public TypeModel setLabelConfig(List<String> labelConfig) { 167 this.labelConfig = labelConfig; 168 return this; 169 } 170 171 172 173 // === View Configuration === 174 175 @Override 176 public ViewConfigurationModelImpl getViewConfigModel() { 177 return viewConfig; 178 } 179 180 // FIXME: server-side operations on the view config settings possibly suggest they are not acually 181 // view config settings but part of the topic type model. Possibly this method should be dropped. 182 @Override 183 public Object getViewConfig(String typeUri, String settingUri) { 184 return viewConfig.getSetting(typeUri, settingUri); 185 } 186 187 @Override 188 public void setViewConfig(ViewConfigurationModel viewConfig) { 189 this.viewConfig = (ViewConfigurationModelImpl) viewConfig; 190 } 191 192 193 194 // === Iterable Implementation === 195 196 /** 197 * Returns an interator which iterates this TypeModel's assoc def URIs. 198 */ 199 @Override 200 public Iterator<String> iterator() { 201 return assocDefs.keySet().iterator(); 202 } 203 204 205 206 // **************************** 207 // *** TopicModel Overrides *** 208 // **************************** 209 210 211 212 @Override 213 public JSONObject toJSON() { 214 try { 215 return super.toJSON() 216 .put("data_type_uri", getDataTypeUri()) 217 .put("index_mode_uris", toJSONArray(indexModes)) 218 .put("assoc_defs", toJSONArray(assocDefs.values())) 219 .put("label_config", new JSONArray(getLabelConfig())) 220 .put("view_config_topics", getViewConfigModel().toJSONArray()); 221 } catch (Exception e) { 222 throw new RuntimeException("Serialization failed (" + this + ")", e); 223 } 224 } 225 226 227 228 // **************** 229 // *** Java API *** 230 // **************** 231 232 233 234 @Override 235 public TypeModelImpl clone() { 236 try { 237 TypeModelImpl model = (TypeModelImpl) super.clone(); 238 model.assocDefs = (SequencedHashMap) model.assocDefs.clone(); 239 return model; 240 } catch (Exception e) { 241 throw new RuntimeException("Cloning a TypeModel failed", e); 242 } 243 } 244 245 @Override 246 public String toString() { 247 return "id=" + id + ", uri=\"" + uri + "\", value=\"" + value + "\", typeUri=\"" + typeUri + 248 "\", dataTypeUri=\"" + getDataTypeUri() + "\", indexModes=" + getIndexModes() + ", assocDefs=" + 249 getAssocDefs() + ", labelConfig=" + getLabelConfig() + ", " + getViewConfigModel(); 250 } 251 252 // ----------------------------------------------------------------------------------------- Package Private Methods 253 254 255 256 // === Abstract Methods === 257 258 List<? extends DeepaMehtaObjectModelImpl> getAllInstances() { 259 throw new UnsupportedOperationException(); 260 } 261 262 // --- 263 264 Directive getUpdateTypeDirective() { 265 throw new UnsupportedOperationException(); 266 } 267 268 Directive getDeleteTypeDirective() { 269 throw new UnsupportedOperationException(); 270 } 271 272 273 274 // === Core Internal Hooks === 275 276 @Override 277 void preUpdate(DeepaMehtaObjectModel newModel) { 278 // ### TODO: is it sufficient if we rehash (remove + add) at post-time? 279 if (uriChange(newModel.getUri(), uri)) { 280 removeFromTypeCache(); 281 } 282 } 283 284 @Override 285 void postUpdate(DeepaMehtaObjectModel newModel, DeepaMehtaObjectModel oldModel) { 286 if (uriChange(newModel.getUri(), oldModel.getUri())) { 287 putInTypeCache(); 288 } 289 // 290 updateType((TypeModelImpl) newModel); 291 // 292 // Note: the UPDATE_TOPIC_TYPE/UPDATE_ASSOCIATION_TYPE directive must be added *before* a possible UPDATE_TOPIC 293 // directive (added by DeepaMehtaObjectModelImpl.update()). In case of a changed type URI the webclient's type 294 // cache must be updated *before* the TopicTypeRenderer/AssociationTypeRenderer can render the type. 295 addUpdateTypeDirective(); 296 } 297 298 // --- 299 300 @Override 301 void preDelete() { 302 // 1) check pre-condition 303 int size = getAllInstances().size(); 304 if (size > 0) { 305 throw new RuntimeException(size + " \"" + value + "\" instances still exist"); 306 } 307 // 2) delete all assoc defs 308 // 309 // Note 1: we use the preDelete() hook to delete the assoc defs *before* they would get deleted by the generic 310 // deletion logic (see "delete direct associations" in DeepaMehtaObjectModelImpl.delete()). The generic deletion 311 // logic deletes the direct associations in an *arbitrary* order. The "Sequence Start" association might get 312 // deleted *before* any other assoc def. When subsequently deleting an assoc def the sequence can't be rebuild 313 // as it is corrupted. 314 // Note 2: iterating with a for-loop here would cause ConcurrentModificationException. Deleting an assoc def 315 // implies rebuilding the sequence and that iterates with a for-loop already. Instead we must create a new 316 // iterator for every single assoc def. 317 String assocDefUri; 318 while ((assocDefUri = getFirstAssocDefUri()) != null) { 319 _getAssocDef(assocDefUri).delete(); 320 } 321 } 322 323 @Override 324 void postDelete() { 325 super.postDelete(); // ### TODO: needed? 326 // 327 removeFromTypeCache(); 328 } 329 330 331 332 // === Update (memory + DB) === 333 334 void updateDataTypeUri(String dataTypeUri) { 335 setDataTypeUri(dataTypeUri); // update memory 336 storeDataTypeUri(); // update DB 337 } 338 339 void updateLabelConfig(List<String> labelConfig) { 340 setLabelConfig(labelConfig); // update memory 341 pl.typeStorage.updateLabelConfig(labelConfig, getAssocDefs()); // update DB 342 } 343 344 void _addIndexMode(IndexMode indexMode) { 345 // update memory 346 addIndexMode(indexMode); 347 // update DB 348 pl.typeStorage.storeIndexMode(uri, indexMode); 349 indexAllInstances(indexMode); 350 } 351 352 // --- 353 354 void _addAssocDefBefore(AssociationDefinitionModelImpl assocDef, String beforeAssocDefUri) { 355 try { 356 long lastAssocDefId = lastAssocDefId(); // must be determined *before* memory is updated 357 // 358 // 1) update memory 359 // Note: the assoc def's custom association type is stored as a child topic. The meta model extension that 360 // adds "Association Type" as a child to the "Composition Definition" and "Aggregation Definition" 361 // association types has itself a custom association type (named "Custom Association Type"), see migration 362 // 5. It would not be stored as storage is model driven and the (meta) model doesn't know about custom 363 // associations as this very concept is introduced only by the assoc def being added here. So, the model 364 // must be updated (in-memory) *before* the assoc def is stored. 365 addAssocDefBefore(assocDef, beforeAssocDefUri); 366 // 367 // 2) update DB 368 pl.typeStorage.storeAssociationDefinition(assocDef); 369 long beforeAssocDefId = beforeAssocDefUri != null ? getAssocDef(beforeAssocDefUri).getId() : -1; 370 long firstAssocDefId = firstAssocDefId(); // must be determined *after* memory is updated 371 pl.typeStorage.addAssocDefToSequence(getId(), assocDef.getId(), beforeAssocDefId, firstAssocDefId, 372 lastAssocDefId); 373 } catch (Exception e) { 374 throw new RuntimeException("Adding an assoc def to type \"" + uri + "\" before \"" + beforeAssocDefUri + 375 "\" failed" + assocDef, e); 376 } 377 } 378 379 void _removeAssocDef(String assocDefUri) { 380 // We trigger deleting an association definition by deleting the underlying association. This mimics deleting an 381 // association definition interactively in the webclient. Updating this type definition's memory and DB sequence 382 // is triggered then by the Type Editor plugin's preDeleteAssociation() hook. ### FIXDOC 383 // This way deleting an association definition works for both cases: 1) interactive deletion (when the user 384 // deletes an association), and 2) programmatical deletion (e.g. from a migration). 385 getAssocDef(assocDefUri).delete(); 386 } 387 388 389 390 // === Type Editor Support === 391 392 void _addAssocDef(AssociationModel assoc) { 393 _addAssocDefBefore(pl.typeStorage.newAssociationDefinition(assoc), null); // beforeAssocDefUri=null 394 // 395 addUpdateTypeDirective(); 396 } 397 398 /** 399 * Note: in contrast to the other "update" methods this one updates the memory only, not the DB! 400 * If you want to update memory and DB use {@link AssociationDefinition#update}. 401 * <p> 402 * This method is here to support a special case: the user retypes an association which results in 403 * a changed type definition. In this case the DB is already up-to-date and only the type's memory 404 * representation must be updated. So, here the DB update is the *cause* for a necessary memory-update. 405 * Normally the situation is vice-versa: the DB update is the necessary *effect* of a memory-update. 406 * 407 * @param assocDef the new association definition. 408 * Note: in contrast to the other "update" methods this one does not support partial updates. 409 * That is all association definition fields must be initialized. ### FIXDOC 410 */ 411 void _updateAssocDef(AssociationModel assoc) { 412 // Note: if the assoc def's custom association type is changed the assoc def URI changes as well. 413 // So we must identify the assoc def to update **by ID** and rehash (that is remove + add). 414 String[] assocDefUris = findAssocDefUris(assoc.getId()); 415 AssociationDefinitionModel oldAssocDef = getAssocDef(assocDefUris[0]); 416 if (assoc == oldAssocDef) { 417 // edited via type topic -- abort 418 return; 419 } 420 // Note: we must not manipulate the assoc model in-place. The Webclient expects by-ID roles. 421 AssociationModel newAssocModel = mf.newAssociationModel(assoc); 422 AssociationModel oldAssocModel = oldAssocDef; 423 // Note: an assoc def expects by-URI roles. 424 newAssocModel.setRoleModel1(oldAssocModel.getRoleModel1()); 425 newAssocModel.setRoleModel2(oldAssocModel.getRoleModel2()); 426 // 427 AssociationDefinitionModel newAssocDef = mf.newAssociationDefinitionModel(newAssocModel, 428 oldAssocDef.getParentCardinalityUri(), 429 oldAssocDef.getChildCardinalityUri(), oldAssocDef.getViewConfigModel() 430 ); 431 String oldAssocDefUri = oldAssocDef.getAssocDefUri(); 432 String newAssocDefUri = newAssocDef.getAssocDefUri(); 433 if (oldAssocDefUri.equals(newAssocDefUri)) { 434 replaceAssocDef(newAssocDef); 435 } else { 436 replaceAssocDef(newAssocDef, oldAssocDefUri, assocDefUris[1]); 437 // 438 // Note: if the custom association type has changed and the assoc def is part the label config 439 // we must replace the assoc def URI in the label config 440 replaceInLabelConfig(newAssocDefUri, oldAssocDefUri); 441 } 442 // 443 addUpdateTypeDirective(); 444 } 445 446 /** 447 * Removes an association from memory and rebuilds the sequence in DB. Note: the underlying 448 * association is *not* removed from DB. 449 * This method is called (by the Type Editor plugin's preDeleteAssociation() hook) when the 450 * deletion of an association that represents an association definition is imminent. ### FIXDOC 451 */ 452 void _removeAssocDefFromMemoryAndRebuildSequence(AssociationModel assoc) { 453 String[] assocDefUris = findAssocDefUris(assoc.getId()); 454 String assocDefUri = getAssocDef(assocDefUris[0]).getAssocDefUri(); 455 // update memory 456 removeAssocDef(assocDefUri); 457 removeFromLabelConfig(assocDefUri); 458 // update DB 459 pl.typeStorage.rebuildSequence(this); 460 // 461 addUpdateTypeDirective(); 462 } 463 464 465 466 // === Access Control === 467 468 final <M extends TypeModelImpl> M filterReadableAssocDefs() { 469 try { 470 Iterator<String> i = iterator(); 471 while (i.hasNext()) { 472 String assocDefUri = i.next(); 473 if (!_getAssocDef(assocDefUri).isReadable()) { 474 i.remove(); 475 } 476 } 477 return (M) this; 478 } catch (Exception e) { 479 throw new RuntimeException("Filtering readable assoc defs of type \"" + uri + "\" failed", e); 480 } 481 } 482 483 484 485 // === 486 487 void rehashAssocDef(long assocDefId) { 488 String[] assocDefUris = findAssocDefUris(assocDefId); 489 rehashAssocDef(assocDefUris[0], assocDefUris[1]); 490 } 491 492 // ------------------------------------------------------------------------------------------------- Private Methods 493 494 private void addUpdateTypeDirective() { 495 Directives.get().add(getUpdateTypeDirective(), instantiate()); 496 } 497 498 499 500 // === Update (memory + DB) === 501 502 private void updateType(TypeModelImpl newModel) { 503 _updateDataTypeUri(newModel.getDataTypeUri()); 504 _updateAssocDefs(newModel.getAssocDefs()); 505 _updateSequence(newModel.getAssocDefs()); 506 _updateLabelConfig(newModel.getLabelConfig()); 507 } 508 509 // --- 510 511 private void _updateDataTypeUri(String newDataTypeUri) { 512 if (newDataTypeUri != null) { 513 String dataTypeUri = getDataTypeUri(); 514 if (!dataTypeUri.equals(newDataTypeUri)) { 515 logger.info("### Changing data type URI from \"" + dataTypeUri + "\" -> \"" + newDataTypeUri + "\""); 516 updateDataTypeUri(newDataTypeUri); 517 } 518 } 519 } 520 521 private void _updateAssocDefs(Collection<AssociationDefinitionModelImpl> newAssocDefs) { 522 for (AssociationDefinitionModelImpl assocDef : newAssocDefs) { 523 // Note: if the assoc def's custom association type was changed the assoc def URI changes as well. 524 // So we must identify the assoc def to update **by ID**. 525 // ### TODO: drop updateAssocDef() and rehash here (that is remove + add). 526 String[] assocDefUris = findAssocDefUris(assocDef.getId()); 527 getAssocDef(assocDefUris[0]).update(assocDef); 528 } 529 } 530 531 private void _updateSequence(Collection<AssociationDefinitionModelImpl> newAssocDefs) { 532 try { 533 // ### TODO: only update sequence if there is a change request 534 // 535 // update memory 536 logger.info("##### Updating sequence (" + newAssocDefs.size() + "/" + getAssocDefs().size() + 537 " assoc defs)"); 538 rehashAssocDefs(newAssocDefs); 539 // update DB 540 pl.typeStorage.rebuildSequence(this); 541 } catch (Exception e) { 542 throw new RuntimeException("Updating the assoc def sequence failed", e); 543 } 544 } 545 546 private void _updateLabelConfig(List<String> newLabelConfig) { 547 try { 548 if (!getLabelConfig().equals(newLabelConfig)) { 549 logger.info("### Changing label configuration from " + getLabelConfig() + " -> " + newLabelConfig); 550 updateLabelConfig(newLabelConfig); 551 } 552 } catch (Exception e) { 553 throw new RuntimeException("Updating label configuration of type \"" + uri + "\" failed", e); 554 } 555 } 556 557 558 559 // === Store (DB only) === 560 561 private void storeDataTypeUri() { 562 // remove current assignment 563 getRelatedTopic("dm4.core.aggregation", "dm4.core.type", "dm4.core.default", "dm4.core.data_type") 564 .getRelatingAssociation().delete(); 565 // create new assignment 566 pl.typeStorage.storeDataType(uri, dataTypeUri); 567 } 568 569 private void indexAllInstances(IndexMode indexMode) { 570 List<? extends DeepaMehtaObjectModelImpl> objects = getAllInstances(); 571 // 572 String str = "\"" + value + "\" (" + uri + ") instances"; 573 if (indexModes.size() > 0) { 574 if (objects.size() > 0) { 575 logger.info("### Indexing " + objects.size() + " " + str + " (indexMode=" + indexMode + ")"); 576 } else { 577 logger.info("### Indexing " + str + " ABORTED -- no instances in DB"); 578 } 579 } else { 580 logger.info("### Indexing " + str + " ABORTED -- no index mode set"); 581 } 582 // 583 for (DeepaMehtaObjectModelImpl obj : objects) { 584 obj.indexSimpleValue(indexMode); 585 } 586 } 587 588 589 590 // === Association Definitions (memory access) === 591 592 /** 593 * Finds an assoc def by ID and returns its URI (at index 0). Returns the URI of the next-in-sequence 594 * assoc def as well (at index 1), or null if the found assoc def is the last one. 595 */ 596 private String[] findAssocDefUris(long assocDefId) { 597 if (assocDefId == -1) { 598 throw new IllegalArgumentException("findAssocDefUris() called with assocDefId=-1"); 599 } 600 String[] assocDefUris = new String[2]; 601 Iterator<String> i = iterator(); 602 while (i.hasNext()) { 603 String assocDefUri = i.next(); 604 long _assocDefId = checkAssocDefId(_getAssocDef(assocDefUri)); 605 if (_assocDefId == assocDefId) { 606 assocDefUris[0] = assocDefUri; 607 if (i.hasNext()) { 608 assocDefUris[1] = i.next(); 609 } 610 break; 611 } 612 } 613 if (assocDefUris[0] == null) { 614 throw new RuntimeException("Assoc def with ID " + assocDefId + " not found in assoc defs of type \"" + uri + 615 "\" (" + assocDefs.keySet() + ")"); 616 } 617 return assocDefUris; 618 } 619 620 // ### TODO: not called 621 private boolean hasSameAssocDefSequence(Collection<? extends AssociationDefinitionModel> assocDefs) { 622 Collection<? extends AssociationDefinitionModel> _assocDefs = getAssocDefs(); 623 if (assocDefs.size() != _assocDefs.size()) { 624 return false; 625 } 626 // 627 Iterator<? extends AssociationDefinitionModel> i = assocDefs.iterator(); 628 for (AssociationDefinitionModel _assocDef : _assocDefs) { 629 AssociationDefinitionModel assocDef = i.next(); 630 // Note: if the assoc def's custom association type changed the assoc def URI changes as well. 631 // So we must identify the assoc defs to compare **by ID**. 632 long assocDefId = checkAssocDefId(assocDef); 633 long _assocDefId = checkAssocDefId(_assocDef); 634 if (assocDefId != _assocDefId) { 635 return false; 636 } 637 } 638 // 639 return true; 640 } 641 642 // --- 643 644 private void rehashAssocDef(String assocDefUri, String beforeAssocDefUri) { 645 AssociationDefinitionModel assocDef = removeAssocDef(assocDefUri); 646 logger.info("### Rehashing assoc def \"" + assocDefUri + "\" -> \"" + assocDef.getAssocDefUri() + 647 "\" (put " + (beforeAssocDefUri != null ? "before \"" + beforeAssocDefUri + "\"" : "at end") + ")"); 648 addAssocDefBefore(assocDef, beforeAssocDefUri); 649 } 650 651 private void rehashAssocDefs(Collection<AssociationDefinitionModelImpl> newAssocDefs) { 652 for (AssociationDefinitionModel assocDef : newAssocDefs) { 653 rehashAssocDef(assocDef.getAssocDefUri(), null); 654 } 655 } 656 657 private void replaceAssocDef(AssociationDefinitionModel assocDef) { 658 replaceAssocDef(assocDef, assocDef.getAssocDefUri(), null); 659 } 660 661 private void replaceAssocDef(AssociationDefinitionModel assocDef, String oldAssocDefUri, String beforeAssocDefUri) { 662 removeAssocDef(oldAssocDefUri); 663 addAssocDefBefore(assocDef, beforeAssocDefUri); 664 } 665 666 // --- 667 668 private AssociationDefinitionModelImpl getAssocDefOrThrow(String assocDefUri) { 669 AssociationDefinitionModelImpl assocDef = _getAssocDef(assocDefUri); 670 if (assocDef == null) { 671 throw new RuntimeException("Assoc def \"" + assocDefUri + "\" not found in " + assocDefs.keySet()); 672 } 673 return assocDef; 674 } 675 676 private AssociationDefinitionModelImpl _getAssocDef(String assocDefUri) { 677 return assocDefs.get(assocDefUri); 678 } 679 680 // --- 681 682 /** 683 * Returns the ID of the last association definition of this type or 684 * <code>-1</code> if there are no association definitions. 685 */ 686 private long lastAssocDefId() { 687 long lastAssocDefId = -1; 688 for (AssociationDefinitionModel assocDef : getAssocDefs()) { 689 lastAssocDefId = assocDef.getId(); 690 } 691 return lastAssocDefId; 692 } 693 694 private long firstAssocDefId() { 695 return getAssocDefs().iterator().next().getId(); 696 } 697 698 private String getFirstAssocDefUri() { 699 Iterator<String> i = iterator(); 700 return i.hasNext() ? i.next() : null; 701 } 702 703 // --- 704 705 private long checkAssocDefId(AssociationDefinitionModel assocDef) { 706 long assocDefId = assocDef.getId(); 707 if (assocDefId == -1) { 708 throw new RuntimeException("The assoc def ID is uninitialized (-1): " + assocDef); 709 } 710 return assocDefId; 711 } 712 713 // --- 714 715 private SequencedHashMap<String, AssociationDefinitionModelImpl> toMap( 716 Collection<? extends AssociationDefinitionModel> assocDefs) { 717 SequencedHashMap<String, AssociationDefinitionModelImpl> _assocDefs = new SequencedHashMap(); 718 for (AssociationDefinitionModel assocDef : assocDefs) { 719 _assocDefs.put(assocDef.getAssocDefUri(), (AssociationDefinitionModelImpl) assocDef); 720 } 721 return _assocDefs; 722 } 723 724 725 726 // === Label Configuration (memory access) === 727 728 private void replaceInLabelConfig(String newAssocDefUri, String oldAssocDefUri) { 729 List<String> labelConfig = getLabelConfig(); 730 int i = labelConfig.indexOf(oldAssocDefUri); 731 if (i != -1) { 732 logger.info("### Label config: replacing \"" + oldAssocDefUri + "\" -> \"" + newAssocDefUri + 733 "\" (position " + i + ")"); 734 labelConfig.set(i, newAssocDefUri); 735 } 736 } 737 738 private void removeFromLabelConfig(String assocDefUri) { 739 List<String> labelConfig = getLabelConfig(); 740 int i = labelConfig.indexOf(assocDefUri); 741 if (i != -1) { 742 logger.info("### Label config: removing \"" + assocDefUri + "\" (position " + i + ")"); 743 labelConfig.remove(i); 744 } 745 } 746 747 748 749 // === Type Cache (memory access) === 750 751 private void putInTypeCache() { 752 pl.typeStorage.putInTypeCache(this); 753 } 754 755 /** 756 * Removes this type from type cache and adds a DELETE TYPE directive to the given set of directives. 757 */ 758 private void removeFromTypeCache() { 759 pl.typeStorage.removeFromTypeCache(uri); 760 // 761 Directive dir = getDeleteTypeDirective(); // abstract 762 Directives.get().add(dir, new JSONWrapper("uri", uri)); 763 } 764 765 766 767 // === Serialization === 768 769 private JSONArray toJSONArray(List<IndexMode> indexModes) { 770 JSONArray indexModeUris = new JSONArray(); 771 for (IndexMode indexMode : indexModes) { 772 indexModeUris.put(indexMode.toUri()); 773 } 774 return indexModeUris; 775 } 776 777 private JSONArray toJSONArray(Collection<? extends AssociationDefinitionModel> assocDefs) { 778 JSONArray _assocDefs = new JSONArray(); 779 for (AssociationDefinitionModel assocDef : assocDefs) { 780 _assocDefs.put(assocDef.toJSON()); 781 } 782 return _assocDefs; 783 } 784 785 786 787 // ------------------------------------------------------------------------------------------------- Private Classes 788 789 private static final class JSONWrapper implements JSONEnabled { 790 791 private JSONObject wrapped; 792 793 private JSONWrapper(String key, Object value) { 794 try { 795 wrapped = new JSONObject(); 796 wrapped.put(key, value); 797 } catch (Exception e) { 798 throw new RuntimeException("Constructing a JSONWrapper failed", e); 799 } 800 } 801 802 @Override 803 public JSONObject toJSON() { 804 return wrapped; 805 } 806 } 807}