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 }