001package de.deepamehta.core.impl;
002
003import de.deepamehta.core.model.AssociationModel;
004import de.deepamehta.core.model.AssociationDefinitionModel;
005import de.deepamehta.core.model.AssociationRoleModel;
006import de.deepamehta.core.model.AssociationTypeModel;
007import de.deepamehta.core.model.ChildTopicsModel;
008import de.deepamehta.core.model.DeepaMehtaObjectModel;
009import de.deepamehta.core.model.IndexMode;
010import de.deepamehta.core.model.RelatedAssociationModel;
011import de.deepamehta.core.model.RelatedTopicModel;
012import de.deepamehta.core.model.RoleModel;
013import de.deepamehta.core.model.SimpleValue;
014import de.deepamehta.core.model.TopicModel;
015import de.deepamehta.core.model.TopicDeletionModel;
016import de.deepamehta.core.model.TopicReferenceModel;
017import de.deepamehta.core.model.TopicRoleModel;
018import de.deepamehta.core.model.TopicTypeModel;
019import de.deepamehta.core.model.TypeModel;
020import de.deepamehta.core.model.ViewConfigurationModel;
021import de.deepamehta.core.model.facets.FacetValueModel;
022import de.deepamehta.core.model.topicmaps.AssociationViewModel;
023import de.deepamehta.core.model.topicmaps.TopicViewModel;
024import de.deepamehta.core.model.topicmaps.ViewProperties;
025import de.deepamehta.core.service.ModelFactory;
026import de.deepamehta.core.util.DeepaMehtaUtils;
027
028import org.codehaus.jettison.json.JSONArray;
029import org.codehaus.jettison.json.JSONException;
030import org.codehaus.jettison.json.JSONObject;
031
032import java.util.ArrayList;
033import java.util.HashMap;
034import java.util.Iterator;
035import java.util.List;
036import java.util.Map;
037
038
039
040// ### TODO: should methods return model *impl* objects? -> Yes!
041public class ModelFactoryImpl implements ModelFactory {
042
043    // ------------------------------------------------------------------------------------------------------- Constants
044
045    private static final String REF_ID_PREFIX  = "ref_id:";
046    private static final String REF_URI_PREFIX = "ref_uri:";
047    private static final String DEL_ID_PREFIX  = "del_id:";
048    private static final String DEL_URI_PREFIX = "del_uri:";
049
050    // ---------------------------------------------------------------------------------------------- Instance Variables
051
052    PersistenceLayer pl;
053
054    // -------------------------------------------------------------------------------------------------- Public Methods
055
056
057
058    // === TopicModel ===
059
060    @Override
061    public TopicModelImpl newTopicModel(long id, String uri, String typeUri, SimpleValue value,
062                                                                         ChildTopicsModel childTopics) {
063        return new TopicModelImpl(newDeepaMehtaObjectModel(id, uri, typeUri, value, childTopics));
064    }
065
066    @Override
067    public TopicModelImpl newTopicModel(ChildTopicsModel childTopics) {
068        return newTopicModel(-1, null, null, null, childTopics);
069    }
070
071    @Override
072    public TopicModelImpl newTopicModel(String typeUri) {
073        return newTopicModel(-1, null, typeUri, null, null);
074    }
075
076    @Override
077    public TopicModelImpl newTopicModel(String typeUri, SimpleValue value) {
078        return newTopicModel(-1, null, typeUri, value, null);
079    }
080
081    @Override
082    public TopicModelImpl newTopicModel(String typeUri, ChildTopicsModel childTopics) {
083        return newTopicModel(-1, null, typeUri, null, childTopics);
084    }
085
086    @Override
087    public TopicModelImpl newTopicModel(String uri, String typeUri) {
088        return newTopicModel(-1, uri, typeUri, null, null);
089    }
090
091    @Override
092    public TopicModelImpl newTopicModel(String uri, String typeUri, SimpleValue value) {
093        return newTopicModel(-1, uri, typeUri, value, null);
094    }
095
096    @Override
097    public TopicModelImpl newTopicModel(String uri, String typeUri, ChildTopicsModel childTopics) {
098        return newTopicModel(-1, uri, typeUri, null, childTopics);
099    }
100
101    @Override
102    public TopicModelImpl newTopicModel(long id) {
103        return newTopicModel(id, null, null, null, null);
104    }
105
106    @Override
107    public TopicModelImpl newTopicModel(long id, ChildTopicsModel childTopics) {
108        return newTopicModel(id, null, null, null, childTopics);
109    }
110
111    @Override
112    public TopicModelImpl newTopicModel(TopicModel topic) {
113        return new TopicModelImpl((TopicModelImpl) topic);
114    }
115
116    @Override
117    public TopicModelImpl newTopicModel(JSONObject topic) {
118        try {
119            return new TopicModelImpl(newDeepaMehtaObjectModel(topic));
120        } catch (Exception e) {
121            throw new RuntimeException("Parsing TopicModel failed (JSONObject=" + topic + ")", e);
122        }
123    }
124
125
126
127    // === AssociationModel ===
128
129    @Override
130    public AssociationModelImpl newAssociationModel(long id, String uri, String typeUri, RoleModel roleModel1,
131                                                RoleModel roleModel2, SimpleValue value, ChildTopicsModel childTopics) {
132        return new AssociationModelImpl(newDeepaMehtaObjectModel(id, uri, typeUri, value, childTopics),
133            (RoleModelImpl) roleModel1, (RoleModelImpl) roleModel2);
134    }
135
136    @Override
137    public AssociationModelImpl newAssociationModel(String typeUri, RoleModel roleModel1, RoleModel roleModel2) {
138        return newAssociationModel(-1, null, typeUri, roleModel1, roleModel2, null, null);
139    }
140
141    @Override
142    public AssociationModelImpl newAssociationModel(String typeUri, RoleModel roleModel1, RoleModel roleModel2,
143                                                                                      ChildTopicsModel childTopics) {
144        return newAssociationModel(-1, null, typeUri, roleModel1, roleModel2, null, childTopics);
145    }
146
147    // ### TODO: don't leave the assoc uninitialized. Refactoring needed. See comment in TypeCache#put methods.
148    @Override
149    public AssociationModelImpl newAssociationModel() {
150        return newAssociationModel(-1, null, null, null, null, null, null);
151    }
152
153    // ### TODO: don't leave the assoc uninitialized. Refactoring needed. See comment in TypeCache#put methods.
154    @Override
155    public AssociationModelImpl newAssociationModel(ChildTopicsModel childTopics) {
156        return newAssociationModel(-1, null, null, null, null, null, childTopics);
157    }
158
159    @Override
160    public AssociationModelImpl newAssociationModel(long id, String uri, String typeUri, RoleModel roleModel1,
161                                                                                     RoleModel roleModel2) {
162        return newAssociationModel(id, uri, typeUri, roleModel1, roleModel2, null, null);
163    }
164
165    @Override
166    public AssociationModelImpl newAssociationModel(AssociationModel assoc) {
167        return new AssociationModelImpl((AssociationModelImpl) assoc);
168    }
169
170    @Override
171    public AssociationModelImpl newAssociationModel(JSONObject assoc) {
172        try {
173            return new AssociationModelImpl(newDeepaMehtaObjectModel(assoc),
174                assoc.has("role_1") ? parseRole(assoc.getJSONObject("role_1")) : null,
175                assoc.has("role_2") ? parseRole(assoc.getJSONObject("role_2")) : null
176            );
177        } catch (Exception e) {
178            throw new RuntimeException("Parsing AssociationModel failed (JSONObject=" + assoc + ")", e);
179        }
180    }
181
182    // ---
183
184    private RoleModelImpl parseRole(JSONObject roleModel) {
185        if (roleModel.has("topic_id") || roleModel.has("topic_uri")) {
186            return newTopicRoleModel(roleModel);
187        } else if (roleModel.has("assoc_id")) {
188            return newAssociationRoleModel(roleModel);
189        } else {
190            throw new RuntimeException("Parsing TopicRoleModel/AssociationRoleModel failed (JSONObject=" +
191                roleModel + ")");
192        }
193    }
194
195
196
197    // === DeepaMehtaObjectModel ===
198
199    /**
200     * @param   id          Optional (-1 is a valid value and represents "not set").
201     * @param   uri         Optional (<code>null</code> is a valid value).
202     * @param   typeUri     Mandatory in the context of a create operation.
203     *                      Optional (<code>null</code> is a valid value) in the context of an update operation.
204     * @param   value       Optional (<code>null</code> is a valid value).
205     * @param   childTopics Optional (<code>null</code> is a valid value and is transformed into an empty composite).
206     */
207    DeepaMehtaObjectModelImpl newDeepaMehtaObjectModel(long id, String uri, String typeUri, SimpleValue value,
208                                                                                         ChildTopicsModel childTopics) {
209        return new DeepaMehtaObjectModelImpl(id, uri, typeUri, value, (ChildTopicsModelImpl) childTopics, pl());
210    }
211
212    DeepaMehtaObjectModelImpl newDeepaMehtaObjectModel(JSONObject object) throws JSONException {
213        return newDeepaMehtaObjectModel(
214            object.optLong("id", -1),
215            object.optString("uri", null),
216            object.optString("type_uri", null),
217            object.has("value") ? new SimpleValue(object.get("value")) : null,
218            object.has("childs") ? newChildTopicsModel(object.getJSONObject("childs")) : null
219        );
220    }
221
222
223
224    // === ChildTopicsModel ===
225
226    @Override
227    public ChildTopicsModelImpl newChildTopicsModel() {
228        return new ChildTopicsModelImpl(new HashMap(), this);
229    }
230
231    @Override
232    public ChildTopicsModelImpl newChildTopicsModel(JSONObject values) {
233        try {
234            Map<String, Object> childTopics = new HashMap();
235            Iterator<String> i = values.keys();
236            while (i.hasNext()) {
237                String assocDefUri = i.next();
238                String childTypeUri = childTypeUri(assocDefUri);
239                Object value = values.get(assocDefUri);
240                if (!(value instanceof JSONArray)) {
241                    childTopics.put(assocDefUri, createTopicModel(childTypeUri, value));
242                } else {
243                    JSONArray valueArray = (JSONArray) value;
244                    List<RelatedTopicModel> topics = new ArrayList();
245                    childTopics.put(assocDefUri, topics);
246                    for (int j = 0; j < valueArray.length(); j++) {
247                        topics.add(createTopicModel(childTypeUri, valueArray.get(j)));
248                    }
249                }
250            }
251            return new ChildTopicsModelImpl(childTopics, this);
252        } catch (Exception e) {
253            throw new RuntimeException("Parsing ChildTopicsModel failed (JSONObject=" + values + ")", e);
254        }
255    }
256
257    @Override
258    public String childTypeUri(String assocDefUri) {
259        return assocDefUri.split("#")[0];
260    }
261
262    // ---
263
264    /**
265     * Creates a topic model from a JSON value.
266     *
267     * Both topic serialization formats are supported:
268     * 1) canonic format -- contains entire topic models.
269     * 2) simplified format -- contains the topic value only (simple or composite).
270     */
271    private RelatedTopicModel createTopicModel(String childTypeUri, Object value) throws JSONException {
272        if (value instanceof JSONObject) {
273            JSONObject val = (JSONObject) value;
274            // we detect the canonic format by checking for mandatory topic properties
275            if (val.has("value") || val.has("childs")) {
276                // canonic format (topic or topic reference)
277                AssociationModel relatingAssoc = null;
278                if (val.has("assoc")) {
279                    relatingAssoc = newAssociationModel(val.getJSONObject("assoc"));
280                }
281                if (val.has("value")) {
282                    RelatedTopicModel topicRef = createReferenceModel(val.get("value"), relatingAssoc);
283                    if (topicRef != null) {
284                        return topicRef;
285                    }
286                }
287                //
288                initTypeUri(val, childTypeUri);
289                //
290                TopicModel topic = newTopicModel(val);
291                if (relatingAssoc != null) {
292                    return newRelatedTopicModel(topic, relatingAssoc);
293                } else {
294                    return newRelatedTopicModel(topic);
295                }
296            } else {
297                // simplified format (composite topic)
298                return newRelatedTopicModel(newTopicModel(childTypeUri, newChildTopicsModel(val)));
299            }
300        } else {
301            // simplified format (simple topic or topic reference)
302            RelatedTopicModel topicRef = createReferenceModel(value, null);
303            if (topicRef != null) {
304                return topicRef;
305            }
306            // simplified format (simple topic)
307            return newRelatedTopicModel(newTopicModel(childTypeUri, new SimpleValue(value)));
308        }
309    }
310
311    private RelatedTopicModel createReferenceModel(Object value, AssociationModel relatingAssoc) {
312        if (value instanceof String) {
313            String val = (String) value;
314            if (val.startsWith(REF_ID_PREFIX)) {
315                long topicId = refTopicId(val);
316                if (relatingAssoc != null) {
317                    return newTopicReferenceModel(topicId, relatingAssoc);
318                } else {
319                    return newTopicReferenceModel(topicId);
320                }
321            } else if (val.startsWith(REF_URI_PREFIX)) {
322                String topicUri = refTopicUri(val);
323                if (relatingAssoc != null) {
324                    return newTopicReferenceModel(topicUri, relatingAssoc);
325                } else {
326                    return newTopicReferenceModel(topicUri);
327                }
328            } else if (val.startsWith(DEL_ID_PREFIX)) {
329                return newTopicDeletionModel(delTopicId(val));
330            } else if (val.startsWith(DEL_URI_PREFIX)) {
331                return newTopicDeletionModel(delTopicUri(val));
332            }
333        }
334        return null;
335    }
336
337    private void initTypeUri(JSONObject value, String childTypeUri) throws JSONException {
338        if (!value.has("type_uri")) {
339            value.put("type_uri", childTypeUri);
340        } else {
341            // sanity check
342            String typeUri = value.getString("type_uri");
343            if (!typeUri.equals(childTypeUri)) {
344                throw new IllegalArgumentException("A \"" + childTypeUri + "\" topic model has type_uri=\"" +
345                    typeUri + "\"");
346            }
347        }
348    }
349
350    // ---
351
352    private long refTopicId(String val) {
353        return Long.parseLong(val.substring(REF_ID_PREFIX.length()));
354    }
355
356    private String refTopicUri(String val) {
357        return val.substring(REF_URI_PREFIX.length());
358    }
359
360    private long delTopicId(String val) {
361        return Long.parseLong(val.substring(DEL_ID_PREFIX.length()));
362    }
363
364    private String delTopicUri(String val) {
365        return val.substring(DEL_URI_PREFIX.length());
366    }
367
368
369
370    // === TopicRoleModel ===
371
372    @Override
373    public TopicRoleModelImpl newTopicRoleModel(long topicId, String roleTypeUri) {
374        return new TopicRoleModelImpl(topicId, roleTypeUri, pl());
375    }
376
377    @Override
378    public TopicRoleModelImpl newTopicRoleModel(String topicUri, String roleTypeUri) {
379        return new TopicRoleModelImpl(topicUri, roleTypeUri, pl());
380    }
381
382    @Override
383    public TopicRoleModelImpl newTopicRoleModel(JSONObject topicRoleModel) {
384        try {
385            long topicId       = topicRoleModel.optLong("topic_id", -1);
386            String topicUri    = topicRoleModel.optString("topic_uri", null);
387            String roleTypeUri = topicRoleModel.getString("role_type_uri");
388            //
389            if (topicId == -1 && topicUri == null) {
390                throw new IllegalArgumentException("Neiter \"topic_id\" nor \"topic_uri\" is set");
391            }
392            if (topicId != -1 && topicUri != null) {
393                throw new IllegalArgumentException("\"topic_id\" and \"topic_uri\" must not be set at the same time");
394            }
395            //
396            if (topicId != -1) {
397                return newTopicRoleModel(topicId, roleTypeUri);
398            } else {
399                return newTopicRoleModel(topicUri, roleTypeUri);
400            }
401        } catch (Exception e) {
402            throw new RuntimeException("Parsing TopicRoleModel failed (JSONObject=" + topicRoleModel + ")", e);
403        }
404    }
405
406
407
408    // === AssociationRoleModel ===
409
410    @Override
411    public AssociationRoleModelImpl newAssociationRoleModel(long assocId, String roleTypeUri) {
412        return new AssociationRoleModelImpl(assocId, roleTypeUri, pl());
413    }    
414
415    @Override
416    public AssociationRoleModelImpl newAssociationRoleModel(JSONObject assocRoleModel) {
417        try {
418            long assocId       = assocRoleModel.getLong("assoc_id");
419            String roleTypeUri = assocRoleModel.getString("role_type_uri");
420            return newAssociationRoleModel(assocId, roleTypeUri);
421        } catch (Exception e) {
422            throw new RuntimeException("Parsing AssociationRoleModel failed (JSONObject=" + assocRoleModel + ")", e);
423        }
424    }    
425
426
427
428    // === RelatedTopicModel ===
429
430    @Override
431    public RelatedTopicModelImpl newRelatedTopicModel(long topicId) {
432        return new RelatedTopicModelImpl(newTopicModel(topicId), newAssociationModel());
433    }
434
435    @Override
436    public RelatedTopicModelImpl newRelatedTopicModel(long topicId, AssociationModel relatingAssoc) {
437        return new RelatedTopicModelImpl(newTopicModel(topicId), (AssociationModelImpl) relatingAssoc);
438    }
439
440    @Override
441    public RelatedTopicModelImpl newRelatedTopicModel(String topicUri) {
442        return new RelatedTopicModelImpl(newTopicModel(topicUri, (String) null), newAssociationModel());
443                                                          // topicTypeUri=null
444    }
445
446    @Override
447    public RelatedTopicModelImpl newRelatedTopicModel(String topicUri, AssociationModel relatingAssoc) {
448        return new RelatedTopicModelImpl(newTopicModel(topicUri, (String) null), (AssociationModelImpl) relatingAssoc);
449                                                          // topicTypeUri=null
450    }
451
452    @Override
453    public RelatedTopicModelImpl newRelatedTopicModel(String topicTypeUri, SimpleValue value) {
454        return new RelatedTopicModelImpl(newTopicModel(topicTypeUri, value), newAssociationModel());
455    }
456
457    @Override
458    public RelatedTopicModelImpl newRelatedTopicModel(String topicTypeUri, ChildTopicsModel childTopics) {
459        return new RelatedTopicModelImpl(newTopicModel(topicTypeUri, childTopics), newAssociationModel());
460    }
461
462    @Override
463    public RelatedTopicModelImpl newRelatedTopicModel(TopicModel topic) {
464        return new RelatedTopicModelImpl((TopicModelImpl) topic, newAssociationModel());
465    }
466
467    @Override
468    public RelatedTopicModelImpl newRelatedTopicModel(TopicModel topic, AssociationModel relatingAssoc) {
469        return new RelatedTopicModelImpl((TopicModelImpl) topic, (AssociationModelImpl) relatingAssoc);
470    }
471
472
473
474    // === RelatedAssociationModel ===
475
476    @Override
477    public RelatedAssociationModel newRelatedAssociationModel(AssociationModel assoc, AssociationModel relatingAssoc) {
478        return new RelatedAssociationModelImpl((AssociationModelImpl) assoc, (AssociationModelImpl) relatingAssoc);
479    }
480
481
482
483    // === TopicReferenceModel ===
484
485    @Override
486    public TopicReferenceModel newTopicReferenceModel(long topicId) {
487        return new TopicReferenceModelImpl(newRelatedTopicModel(topicId));
488    }
489
490    @Override
491    public TopicReferenceModel newTopicReferenceModel(long topicId, AssociationModel relatingAssoc) {
492        return new TopicReferenceModelImpl(newRelatedTopicModel(topicId, relatingAssoc));
493    }
494
495    @Override
496    public TopicReferenceModel newTopicReferenceModel(String topicUri) {
497        return new TopicReferenceModelImpl(newRelatedTopicModel(topicUri));
498    }
499
500    @Override
501    public TopicReferenceModel newTopicReferenceModel(String topicUri, AssociationModel relatingAssoc) {
502        return new TopicReferenceModelImpl(newRelatedTopicModel(topicUri, relatingAssoc));
503    }
504
505    @Override
506    public TopicReferenceModel newTopicReferenceModel(long topicId, ChildTopicsModel relatingAssocChildTopics) {
507        return new TopicReferenceModelImpl(
508            newRelatedTopicModel(topicId, newAssociationModel(relatingAssocChildTopics))
509        );
510    }
511
512    @Override
513    public TopicReferenceModel newTopicReferenceModel(String topicUri, ChildTopicsModel relatingAssocChildTopics) {
514        return new TopicReferenceModelImpl(
515            newRelatedTopicModel(topicUri, newAssociationModel(relatingAssocChildTopics))
516        );
517    }
518
519
520
521    // === TopicDeletionModel ===
522
523    @Override
524    public TopicDeletionModel newTopicDeletionModel(long topicId) {
525        return new TopicDeletionModelImpl(newRelatedTopicModel(topicId));
526    }
527
528    @Override
529    public TopicDeletionModel newTopicDeletionModel(String topicUri) {
530        return new TopicDeletionModelImpl(newRelatedTopicModel(topicUri));
531    }
532
533
534
535    // === TopicTypeModel ===
536
537    @Override
538    public TopicTypeModelImpl newTopicTypeModel(TopicModel typeTopic, String dataTypeUri,
539                                            List<IndexMode> indexModes, List<AssociationDefinitionModel> assocDefs,
540                                            List<String> labelConfig, ViewConfigurationModel viewConfig) {
541        return new TopicTypeModelImpl(newTypeModel(typeTopic, dataTypeUri, indexModes, assocDefs, labelConfig,
542            (ViewConfigurationModelImpl) viewConfig));
543    }
544
545    @Override
546    public TopicTypeModelImpl newTopicTypeModel(String uri, String value, String dataTypeUri) {
547        return new TopicTypeModelImpl(newTypeModel(uri, "dm4.core.topic_type", new SimpleValue(value), dataTypeUri));
548    }
549
550    @Override
551    public TopicTypeModelImpl newTopicTypeModel(JSONObject topicType) {
552        try {
553            return new TopicTypeModelImpl(newTypeModel(topicType.put("type_uri", "dm4.core.topic_type")));
554        } catch (Exception e) {
555            throw new RuntimeException("Parsing TopicTypeModel failed (JSONObject=" + topicType + ")", e);
556        }
557    }
558
559
560
561    // === AssociationTypeModel ===
562
563    @Override
564    public AssociationTypeModelImpl newAssociationTypeModel(TopicModel typeTopic, String dataTypeUri,
565                                                 List<IndexMode> indexModes, List<AssociationDefinitionModel> assocDefs,
566                                                 List<String> labelConfig, ViewConfigurationModel viewConfig) {
567        return new AssociationTypeModelImpl(newTypeModel(typeTopic, dataTypeUri, indexModes, assocDefs, labelConfig,
568            (ViewConfigurationModelImpl) viewConfig));
569    }
570
571    @Override
572    public AssociationTypeModelImpl newAssociationTypeModel(String uri, String value, String dataTypeUri) {
573        return new AssociationTypeModelImpl(newTypeModel(uri, "dm4.core.assoc_type", new SimpleValue(value),
574            dataTypeUri));
575    }
576
577    @Override
578    public AssociationTypeModelImpl newAssociationTypeModel(JSONObject assocType) {
579        try {
580            return new AssociationTypeModelImpl(newTypeModel(assocType.put("type_uri", "dm4.core.assoc_type")));
581        } catch (Exception e) {
582            throw new RuntimeException("Parsing AssociationTypeModel failed (JSONObject=" + assocType + ")", e);
583        }
584    }
585
586
587
588    // === TypeModel ===
589
590    TypeModelImpl newTypeModel(TopicModel typeTopic, String dataTypeUri, List<IndexMode> indexModes,
591                                  List<AssociationDefinitionModel> assocDefs, List<String> labelConfig,
592                                  ViewConfigurationModelImpl viewConfig) {
593        return new TypeModelImpl((TopicModelImpl) typeTopic, dataTypeUri, indexModes, assocDefs, labelConfig,
594            viewConfig);
595    }
596
597    TypeModelImpl newTypeModel(String uri, String typeUri, SimpleValue value, String dataTypeUri) {
598        return new TypeModelImpl(newTopicModel(uri, typeUri, value), dataTypeUri,
599            new ArrayList(), new ArrayList(), new ArrayList(), newViewConfigurationModel()
600        );
601    }
602
603    TypeModelImpl newTypeModel(JSONObject typeModel) throws JSONException {
604        TopicModelImpl typeTopic = newTopicModel(typeModel);
605        return new TypeModelImpl(typeTopic,
606            typeModel.optString("data_type_uri", null),
607            parseIndexModes(typeModel.optJSONArray("index_mode_uris")),
608            parseAssocDefs(typeModel.optJSONArray("assoc_defs"), typeTopic.getUri()),
609            parseLabelConfig(typeModel.optJSONArray("label_config")),
610            newViewConfigurationModel(typeModel.optJSONArray("view_config_topics"))
611        );
612    }
613
614    // ---
615
616    private List<IndexMode> parseIndexModes(JSONArray indexModeUris) {
617        try {
618            List<IndexMode> indexModes = new ArrayList();
619            if (indexModeUris != null) {
620                for (int i = 0; i < indexModeUris.length(); i++) {
621                    indexModes.add(IndexMode.fromUri(indexModeUris.getString(i)));
622                }
623            }
624            return indexModes;
625        } catch (Exception e) {
626            throw new RuntimeException("Parsing index modes failed (JSONArray=" + indexModeUris + ")", e);
627        }
628    }
629
630    private List<AssociationDefinitionModel> parseAssocDefs(JSONArray assocDefs, String parentTypeUri) throws
631                                                                                                       JSONException {
632        List<AssociationDefinitionModel> _assocDefs = new ArrayList();
633        if (assocDefs != null) {
634            for (int i = 0; i < assocDefs.length(); i++) {
635                JSONObject assocDef = assocDefs.getJSONObject(i)
636                    .put("parent_type_uri", parentTypeUri);
637                _assocDefs.add(newAssociationDefinitionModel(assocDef));
638            }
639        }
640        return _assocDefs;
641    }
642
643    private List<String> parseLabelConfig(JSONArray labelConfig) {
644        return labelConfig != null ? DeepaMehtaUtils.toList(labelConfig) : new ArrayList();
645    }
646
647
648
649    // === AssociationDefinitionModel ===
650
651    @Override
652    public AssociationDefinitionModelImpl newAssociationDefinitionModel(
653                                                    long id, String uri, String assocTypeUri, String customAssocTypeUri,
654                                                    String parentTypeUri, String childTypeUri,
655                                                    String parentCardinalityUri, String childCardinalityUri,
656                                                    ViewConfigurationModel viewConfig) {
657        return new AssociationDefinitionModelImpl(
658            newAssociationModel(id, uri, assocTypeUri, parentRole(parentTypeUri), childRole(childTypeUri),
659                null, childTopics(customAssocTypeUri)
660            ),
661            parentCardinalityUri, childCardinalityUri, (ViewConfigurationModelImpl) viewConfig
662        );
663    }
664
665    @Override
666    public AssociationDefinitionModelImpl newAssociationDefinitionModel(String assocTypeUri,
667                                                    String parentTypeUri, String childTypeUri,
668                                                    String parentCardinalityUri, String childCardinalityUri) {
669        return newAssociationDefinitionModel(-1, null, assocTypeUri, null, parentTypeUri, childTypeUri,
670            parentCardinalityUri, childCardinalityUri, null);
671    }
672
673    @Override
674    public AssociationDefinitionModelImpl newAssociationDefinitionModel(String assocTypeUri, String customAssocTypeUri,
675                                                    String parentTypeUri, String childTypeUri,
676                                                    String parentCardinalityUri, String childCardinalityUri) {
677        return newAssociationDefinitionModel(-1, null, assocTypeUri, customAssocTypeUri, parentTypeUri, childTypeUri,
678            parentCardinalityUri, childCardinalityUri, null);
679    }
680
681    /**
682     * @param   assoc   the underlying association.
683     *                  IMPORTANT: the association must identify its players <i>by URI</i> (not by ID). ### still true?
684     */
685    @Override
686    public AssociationDefinitionModelImpl newAssociationDefinitionModel(AssociationModel assoc,
687                                                    String parentCardinalityUri, String childCardinalityUri,
688                                                    ViewConfigurationModel viewConfig) {
689        return new AssociationDefinitionModelImpl((AssociationModelImpl) assoc, parentCardinalityUri,
690            childCardinalityUri, (ViewConfigurationModelImpl) viewConfig);
691    }
692
693    @Override
694    public AssociationDefinitionModelImpl newAssociationDefinitionModel(JSONObject assocDef) {
695        try {
696            AssociationModelImpl assoc = newAssociationModel(assocDef.optLong("id", -1), null,
697                assocDef.getString("assoc_type_uri"),
698                parentRole(assocDef.getString("parent_type_uri")),
699                childRole(assocDef.getString("child_type_uri")),
700                null, childTopics(assocDef)
701            );
702            //
703            if (!assocDef.has("parent_cardinality_uri") && !assoc.getTypeUri().equals("dm4.core.composition_def")) {
704                throw new RuntimeException("\"parent_cardinality_uri\" is missing");
705            }
706            //
707            return new AssociationDefinitionModelImpl(assoc,
708                assocDef.optString("parent_cardinality_uri", "dm4.core.one"),
709                assocDef.getString("child_cardinality_uri"),
710                newViewConfigurationModel(assocDef.optJSONArray("view_config_topics"))
711            );
712        } catch (Exception e) {
713            throw new RuntimeException("Parsing AssociationDefinitionModel failed (JSONObject=" + assocDef + ")", e);
714        }
715    }
716
717    // ---
718
719    private TopicRoleModel parentRole(String parentTypeUri) {
720        return newTopicRoleModel(parentTypeUri, "dm4.core.parent_type");
721    }
722
723    private TopicRoleModel childRole(String childTypeUri) {
724        return newTopicRoleModel(childTypeUri, "dm4.core.child_type");
725    }
726
727    // ---
728
729    private ChildTopicsModel childTopics(JSONObject assocDef) throws JSONException {
730        // Note: getString() called on a key with JSON null value would return the string "null"
731        return childTopics(assocDef.isNull("custom_assoc_type_uri") ? null :
732            assocDef.getString("custom_assoc_type_uri"));
733    }
734
735    private ChildTopicsModel childTopics(String customAssocTypeUri) {
736        if (customAssocTypeUri != null) {
737            if (customAssocTypeUri.startsWith(DEL_URI_PREFIX)) {
738                return newChildTopicsModel().putDeletionRef("dm4.core.assoc_type#dm4.core.custom_assoc_type",
739                    delTopicUri(customAssocTypeUri));
740            } else {
741                return newChildTopicsModel().putRef("dm4.core.assoc_type#dm4.core.custom_assoc_type",
742                    customAssocTypeUri);
743            }
744        } else {
745            return null;
746        }
747    }
748
749
750
751    // === ViewConfigurationModel ===
752
753    @Override
754    public ViewConfigurationModelImpl newViewConfigurationModel() {
755        return new ViewConfigurationModelImpl(new HashMap());
756    }    
757
758    @Override
759    public ViewConfigurationModelImpl newViewConfigurationModel(Iterable<? extends TopicModel> configTopics) {
760        Map<String, TopicModelImpl> _configTopics = new HashMap();
761        for (TopicModel configTopic : configTopics) {
762            _configTopics.put(configTopic.getTypeUri(), (TopicModelImpl) configTopic);
763        }
764        return new ViewConfigurationModelImpl(_configTopics);
765    }    
766
767    /**
768     * @param   configurable    A topic type, an association type, or an association definition. ### FIXDOC
769     */
770    @Override
771    public ViewConfigurationModelImpl newViewConfigurationModel(JSONArray configTopics) {
772        try {
773            Map<String, TopicModelImpl> _configTopics = new HashMap();
774            if (configTopics != null) {
775                for (int i = 0; i < configTopics.length(); i++) {
776                    TopicModelImpl configTopic = newTopicModel(configTopics.getJSONObject(i));
777                    _configTopics.put(configTopic.getTypeUri(), configTopic);
778                }
779            }
780            return new ViewConfigurationModelImpl(_configTopics);
781        } catch (Exception e) {
782            throw new RuntimeException("Parsing ViewConfigurationModel failed (JSONArray=" + configTopics + ")", e);
783        }
784    }    
785
786
787
788    // === Topicmaps ===
789
790    @Override
791    public TopicViewModel newTopicViewModel(TopicModel topic, ViewProperties viewProps) {
792        return new TopicViewModelImpl((TopicModelImpl) topic, viewProps);
793    }
794
795    @Override
796    public AssociationViewModel newAssociationViewModel(AssociationModel assoc) {
797        return new AssociationViewModelImpl((AssociationModelImpl) assoc);
798    }
799
800
801
802    // === Facets ===
803
804    @Override
805    public FacetValueModel newFacetValueModel(String childTypeUri) {
806        return new FacetValueModelImpl(childTypeUri, this);
807    }
808
809    @Override
810    public FacetValueModel newFacetValueModel(JSONObject facetValue) {
811        try {
812            ChildTopicsModelImpl childTopics = newChildTopicsModel(facetValue);
813            if (childTopics.size() != 1) {
814                throw new RuntimeException("There are " + childTopics.size() + " child topic entries (expected is 1)");
815            }
816            return new FacetValueModelImpl(childTopics);
817        } catch (Exception e) {
818            throw new RuntimeException("Parsing FacetValueModel failed (JSONObject=" + facetValue + ")", e);
819        }
820    }
821
822    // ------------------------------------------------------------------------------------------------- Private Methods
823
824    private PersistenceLayer pl() {
825        if (pl == null) {
826            throw new RuntimeException("before using the ModelFactory a PersistenceLayer must be set");
827        }
828        return pl;
829    }
830}