001 package de.deepamehta.core.impl; 002 003 import de.deepamehta.core.AssociationDefinition; 004 import de.deepamehta.core.DeepaMehtaObject; 005 import de.deepamehta.core.JSONEnabled; 006 import de.deepamehta.core.Type; 007 import de.deepamehta.core.ViewConfiguration; 008 import de.deepamehta.core.model.AssociationDefinitionModel; 009 import de.deepamehta.core.model.IndexMode; 010 import de.deepamehta.core.model.RoleModel; 011 import de.deepamehta.core.model.TypeModel; 012 import de.deepamehta.core.service.Directive; 013 import de.deepamehta.core.service.Directives; 014 import de.deepamehta.core.util.SequencedHashMap; 015 016 import org.codehaus.jettison.json.JSONObject; 017 018 import java.util.Collection; 019 import java.util.Iterator; 020 import java.util.List; 021 import java.util.logging.Logger; 022 023 024 025 abstract class AttachedType extends AttachedTopic implements Type { 026 027 // ---------------------------------------------------------------------------------------------- Instance Variables 028 029 private SequencedHashMap<String, AssociationDefinition> assocDefs; // Attached object cache 030 private ViewConfiguration viewConfig; // Attached object cache 031 032 private Logger logger = Logger.getLogger(getClass().getName()); 033 034 // ---------------------------------------------------------------------------------------------------- Constructors 035 036 AttachedType(TypeModel model, EmbeddedService dms) { 037 super(model, dms); 038 // init attached object cache 039 initAssocDefs(); 040 initViewConfig(); 041 } 042 043 // -------------------------------------------------------------------------------------------------- Public Methods 044 045 046 047 // ****************************************** 048 // *** AttachedDeepaMehtaObject Overrides *** 049 // ****************************************** 050 051 052 053 // === Updating === 054 055 @Override 056 public void update(TypeModel model) { 057 boolean uriChanged = hasUriChanged(model.getUri()); 058 if (uriChanged) { 059 _removeFromTypeCache(); 060 } 061 // 062 super.update(model); 063 // 064 if (uriChanged) { 065 putInTypeCache(); // abstract 066 } 067 // 068 updateDataTypeUri(model.getDataTypeUri()); 069 updateAssocDefs(model.getAssocDefs()); 070 updateSequence(model.getAssocDefs()); 071 updateLabelConfig(model.getLabelConfig()); 072 } 073 074 075 076 // === Deletion === 077 078 @Override 079 public void delete() { 080 String operation = "Deleting " + className() + " \"" + getUri() + "\" (named \"" + getSimpleValue() + "\")"; 081 try { 082 logger.info(operation); 083 // 084 int size = getAllInstances().size(); 085 if (size > 0) { 086 throw new RuntimeException(size + " \"" + getSimpleValue() + "\" instances still exist"); 087 } 088 // 089 super.delete(); // delete type topic 090 // 091 _removeFromTypeCache(); 092 } catch (Exception e) { 093 throw new RuntimeException(operation + " failed", e); 094 } 095 } 096 097 098 099 // *************************** 100 // *** Type Implementation *** 101 // *************************** 102 103 104 105 // === Model === 106 107 // --- Data Type --- 108 109 @Override 110 public String getDataTypeUri() { 111 return getModel().getDataTypeUri(); 112 } 113 114 @Override 115 public void setDataTypeUri(String dataTypeUri) { 116 // update memory 117 getModel().setDataTypeUri(dataTypeUri); 118 // update DB 119 storeDataTypeUri(dataTypeUri); 120 } 121 122 // --- Index Modes --- 123 124 @Override 125 public List<IndexMode> getIndexModes() { 126 return getModel().getIndexModes(); 127 } 128 129 @Override 130 public void addIndexMode(IndexMode indexMode) { 131 // update memory 132 getModel().addIndexMode(indexMode); 133 // update DB 134 dms.typeStorage.storeIndexMode(getUri(), indexMode); 135 indexAllInstances(indexMode); 136 } 137 138 // --- Association Definitions --- 139 140 @Override 141 public Collection<AssociationDefinition> getAssocDefs() { 142 return assocDefs.values(); 143 } 144 145 @Override 146 public AssociationDefinition getAssocDef(String childTypeUri) { 147 AssociationDefinition assocDef = assocDefs.get(childTypeUri); 148 if (assocDef == null) { 149 throw new RuntimeException("Schema violation: association definition \"" + 150 childTypeUri + "\" not found in " + this); 151 } 152 return assocDef; 153 } 154 155 @Override 156 public boolean hasAssocDef(String childTypeUri) { 157 return assocDefs.get(childTypeUri) != null; 158 } 159 160 @Override 161 public Type addAssocDef(AssociationDefinitionModel assocDef) { 162 return addAssocDefBefore(assocDef, null); // beforeChildTypeUri=null 163 } 164 165 @Override 166 public Type addAssocDefBefore(AssociationDefinitionModel assocDef, String beforeChildTypeUri) { 167 // Note: the last assoc def must be determined *before* the memory is updated 168 AssociationDefinitionModel lastAssocDef = lastAssocDef(); 169 // 1) update memory 170 getModel().addAssocDefBefore(assocDef, beforeChildTypeUri); // update model 171 _addAssocDefBefore(assocDef, beforeChildTypeUri); // update attached object cache 172 // 2) update DB 173 dms.typeStorage.storeAssociationDefinition(assocDef); 174 // update sequence 175 long assocDefId = assocDef.getId(); 176 if (beforeChildTypeUri == null) { 177 // append at end 178 dms.typeStorage.appendToSequence(getId(), assocDefId, lastAssocDef); 179 } else if (firstAssocDef().getId() == assocDefId) { 180 // insert at start 181 dms.typeStorage.insertAtSequenceStart(getId(), assocDefId); 182 } else { 183 // insert in the middle 184 long beforeAssocDefId = getAssocDef(beforeChildTypeUri).getId(); 185 dms.typeStorage.insertIntoSequence(assocDefId, beforeAssocDefId); 186 } 187 return this; 188 } 189 190 @Override 191 public void updateAssocDef(AssociationDefinitionModel assocDef) { 192 // update memory 193 getModel().updateAssocDef(assocDef); // update model 194 _addAssocDef(assocDef); // update attached object cache 195 // update DB 196 // ### Note: the DB is not updated here! In case of interactive assoc type change the association is 197 // already updated in DB. => See interface comment. 198 } 199 200 @Override 201 public Type removeAssocDef(String childTypeUri) { 202 // We trigger deleting an association definition by deleting the underlying association. This mimics deleting an 203 // association definition interactively in the webclient. Updating this type definition's memory and DB sequence 204 // is triggered then by the Type Editor plugin's preDeleteAssociation() hook. 205 // This way deleting an association definition works for both cases: 1) interactive deletion (when the user 206 // deletes an association), and 2) programmatical deletion (e.g. from a migration). 207 getAssocDef(childTypeUri).delete(); 208 return this; 209 } 210 211 // --- Label Configuration --- 212 213 @Override 214 public List<String> getLabelConfig() { 215 return getModel().getLabelConfig(); 216 } 217 218 @Override 219 public void setLabelConfig(List<String> labelConfig) { 220 // update memory 221 getModel().setLabelConfig(labelConfig); 222 // update DB 223 dms.typeStorage.updateLabelConfig(labelConfig, getAssocDefs()); 224 } 225 226 // --- View Configuration --- 227 228 @Override 229 public ViewConfiguration getViewConfig() { 230 return viewConfig; 231 } 232 233 // FIXME: to be dropped 234 @Override 235 public Object getViewConfig(String typeUri, String settingUri) { 236 return getModel().getViewConfig(typeUri, settingUri); 237 } 238 239 // --- 240 241 @Override 242 public TypeModel getModel() { 243 return (TypeModel) super.getModel(); 244 } 245 246 247 248 // ----------------------------------------------------------------------------------------- Package Private Methods 249 250 abstract void putInTypeCache(); 251 252 abstract void removeFromTypeCache(); 253 254 // --- 255 256 abstract Directive getDeleteTypeDirective(); 257 258 abstract List<? extends DeepaMehtaObject> getAllInstances(); 259 260 // --- 261 262 void removeAssocDefFromMemoryAndRebuildSequence(String childTypeUri) { 263 // update memory 264 getModel().removeAssocDef(childTypeUri); // update model 265 _removeAssocDef(childTypeUri); // update attached object cache 266 // update DB 267 dms.typeStorage.rebuildSequence(this); 268 } 269 270 // ------------------------------------------------------------------------------------------------- Private Methods 271 272 273 274 // === Update === 275 276 private boolean hasUriChanged(String newUri) { 277 return newUri != null && !getUri().equals(newUri); 278 } 279 280 // --- 281 282 private void updateDataTypeUri(String newDataTypeUri) { 283 if (newDataTypeUri != null) { 284 String dataTypeUri = getDataTypeUri(); 285 if (!dataTypeUri.equals(newDataTypeUri)) { 286 logger.info("### Changing data type URI from \"" + dataTypeUri + "\" -> \"" + newDataTypeUri + "\""); 287 setDataTypeUri(newDataTypeUri); 288 } 289 } 290 } 291 292 private void storeDataTypeUri(String dataTypeUri) { 293 // remove current assignment 294 getRelatedTopic("dm4.core.aggregation", "dm4.core.type", "dm4.core.default", "dm4.core.data_type") 295 .getRelatingAssociation().delete(); 296 // create new assignment 297 dms.typeStorage.storeDataType(getUri(), dataTypeUri); 298 } 299 300 // --- 301 302 private void indexAllInstances(IndexMode indexMode) { 303 List<? extends DeepaMehtaObject> objects = getAllInstances(); 304 // 305 String str = "\"" + getSimpleValue() + "\" (" + getUri() + ") instances"; 306 if (getIndexModes().size() > 0) { 307 if (objects.size() > 0) { 308 logger.info("### Indexing " + objects.size() + " " + str + " (indexMode=" + indexMode + ")"); 309 } else { 310 logger.info("### Indexing " + str + " ABORTED -- no instances in DB"); 311 } 312 } else { 313 logger.info("### Indexing " + str + " ABORTED -- no index mode set"); 314 } 315 // 316 for (DeepaMehtaObject obj : objects) { 317 dms.valueStorage.indexSimpleValue(obj.getModel(), indexMode); 318 } 319 } 320 321 // --- 322 323 private void updateAssocDefs(Collection<AssociationDefinitionModel> newAssocDefs) { 324 for (AssociationDefinitionModel assocDef : newAssocDefs) { 325 getAssocDef(assocDef.getChildTypeUri()).update(assocDef); 326 } 327 } 328 329 // --- 330 331 private void updateSequence(Collection<AssociationDefinitionModel> newAssocDefs) { 332 if (!hasSequenceChanged(newAssocDefs)) { 333 return; 334 } 335 logger.info("### Changing assoc def sequence"); 336 // update memory 337 getModel().removeAllAssocDefs(); 338 for (AssociationDefinitionModel assocDef : newAssocDefs) { 339 getModel().addAssocDef(assocDef); 340 } 341 initAssocDefs(); // attached object cache 342 // update DB 343 dms.typeStorage.rebuildSequence(this); 344 } 345 346 private boolean hasSequenceChanged(Collection<AssociationDefinitionModel> newAssocDefs) { 347 Collection<AssociationDefinition> assocDefs = getAssocDefs(); 348 if (assocDefs.size() != newAssocDefs.size()) { 349 throw new RuntimeException("adding/removing of assoc defs not yet supported via updateTopicType() call"); 350 } 351 // 352 Iterator<AssociationDefinitionModel> i = newAssocDefs.iterator(); 353 for (AssociationDefinition assocDef : assocDefs) { 354 AssociationDefinitionModel newAssocDef = i.next(); 355 if (!assocDef.getChildTypeUri().equals(newAssocDef.getChildTypeUri())) { 356 return true; 357 } 358 } 359 // 360 return false; 361 } 362 363 // --- 364 365 private void updateLabelConfig(List<String> newLabelConfig) { 366 if (!getLabelConfig().equals(newLabelConfig)) { 367 logger.info("### Changing label configuration"); 368 setLabelConfig(newLabelConfig); 369 } 370 } 371 372 373 374 // === Helper === 375 376 /** 377 * Returns the last association definition of this type or 378 * <code>null</code> if there are no association definitions. 379 * 380 * ### TODO: move to class TypeModel? 381 */ 382 private AssociationDefinitionModel lastAssocDef() { 383 AssociationDefinitionModel lastAssocDef = null; 384 for (AssociationDefinitionModel assocDef : getModel().getAssocDefs()) { 385 lastAssocDef = assocDef; 386 } 387 return lastAssocDef; 388 } 389 390 private AssociationDefinitionModel firstAssocDef() { 391 return getModel().getAssocDefs().iterator().next(); 392 } 393 394 // --- Attached Object Cache --- 395 396 // ### FIXME: make it private 397 protected void initAssocDefs() { 398 this.assocDefs = new SequencedHashMap(); 399 for (AssociationDefinitionModel model : getModel().getAssocDefs()) { 400 _addAssocDef(model); 401 } 402 } 403 404 private void _addAssocDef(AssociationDefinitionModel model) { 405 _addAssocDefBefore(model, null); // beforeChildTypeUri=null 406 } 407 408 /** 409 * @param beforeChildTypeUri the assoc def <i>before</i> the assoc def is inserted into the sequence. 410 * If <code>null</code> the assoc def is appended at the end. 411 */ 412 private void _addAssocDefBefore(AssociationDefinitionModel model, String beforeChildTypeUri) { 413 assocDefs.putBefore(model.getChildTypeUri(), new AttachedAssociationDefinition(model, dms), beforeChildTypeUri); 414 } 415 416 private void _removeAssocDef(String childTypeUri) { 417 // error check 418 getAssocDef(childTypeUri); 419 // 420 assocDefs.remove(childTypeUri); 421 } 422 423 // --- 424 425 private void initViewConfig() { 426 RoleModel configurable = dms.typeStorage.createConfigurableType(getId()); // ### type ID is uninitialized 427 this.viewConfig = new AttachedViewConfiguration(configurable, getModel().getViewConfigModel(), dms); 428 } 429 430 431 432 // === 433 434 /** 435 * Removes this type from type cache and adds a DELETE TYPE directive to the given set of directives. 436 */ 437 private void _removeFromTypeCache() { 438 removeFromTypeCache(); // abstract 439 addDeleteTypeDirective(); 440 } 441 442 private void addDeleteTypeDirective() { 443 Directive dir = getDeleteTypeDirective(); // abstract 444 Directives.get().add(dir, new JSONWrapper("uri", getUri())); 445 } 446 447 // ------------------------------------------------------------------------------------------------- Private Classes 448 449 private class JSONWrapper implements JSONEnabled { 450 451 private JSONObject wrapped; 452 453 private JSONWrapper(String key, Object value) { 454 try { 455 wrapped = new JSONObject(); 456 wrapped.put(key, value); 457 } catch (Exception e) { 458 throw new RuntimeException("Constructing a JSONWrapper failed", e); 459 } 460 } 461 462 @Override 463 public JSONObject toJSON() { 464 return wrapped; 465 } 466 } 467 }