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