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