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                                                ViewConfigurationModel viewConfig) {
541        return new TopicTypeModelImpl(newTypeModel(typeTopic, dataTypeUri, indexModes, assocDefs,
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                                                 ViewConfigurationModel viewConfig) {
567        return new AssociationTypeModelImpl(newTypeModel(typeTopic, dataTypeUri, indexModes, assocDefs,
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, ViewConfigurationModelImpl viewConfig) {
592        return new TypeModelImpl((TopicModelImpl) typeTopic, dataTypeUri, indexModes, assocDefs, viewConfig);
593    }
594
595    TypeModelImpl newTypeModel(String uri, String typeUri, SimpleValue value, String dataTypeUri) {
596        return new TypeModelImpl(newTopicModel(uri, typeUri, value), dataTypeUri, new ArrayList(), new ArrayList(),
597            newViewConfigurationModel());
598    }
599
600    TypeModelImpl newTypeModel(JSONObject typeModel) throws JSONException {
601        TopicModelImpl typeTopic = newTopicModel(typeModel);
602        return new TypeModelImpl(typeTopic,
603            typeModel.optString("data_type_uri", null),
604            parseIndexModes(typeModel.optJSONArray("index_mode_uris")),                 // optJSONArray may return null
605            parseAssocDefs(typeModel.optJSONArray("assoc_defs"), typeTopic.getUri()),   // optJSONArray may return null
606            newViewConfigurationModel(typeModel.optJSONArray("view_config_topics")));   // optJSONArray may return null
607    }
608
609    // ---
610
611    private List<IndexMode> parseIndexModes(JSONArray indexModeUris) {
612        try {
613            List<IndexMode> indexModes = new ArrayList();
614            if (indexModeUris != null) {
615                for (int i = 0; i < indexModeUris.length(); i++) {
616                    indexModes.add(IndexMode.fromUri(indexModeUris.getString(i)));
617                }
618            }
619            return indexModes;
620        } catch (Exception e) {
621            throw new RuntimeException("Parsing index modes failed (JSONArray=" + indexModeUris + ")", e);
622        }
623    }
624
625    private List<AssociationDefinitionModel> parseAssocDefs(JSONArray assocDefs, String parentTypeUri) throws
626                                                                                                       JSONException {
627        List<AssociationDefinitionModel> _assocDefs = new ArrayList();
628        if (assocDefs != null) {
629            for (int i = 0; i < assocDefs.length(); i++) {
630                JSONObject assocDef = assocDefs.getJSONObject(i)
631                    .put("parent_type_uri", parentTypeUri);
632                _assocDefs.add(newAssociationDefinitionModel(assocDef));
633            }
634        }
635        return _assocDefs;
636    }
637
638
639
640    // === AssociationDefinitionModel ===
641
642    @Override
643    public AssociationDefinitionModelImpl newAssociationDefinitionModel(String assocTypeUri,
644                                                    String parentTypeUri, String childTypeUri,
645                                                    String parentCardinalityUri, String childCardinalityUri) {
646        return newAssociationDefinitionModel(-1, null, assocTypeUri, null, false, parentTypeUri, childTypeUri,
647            parentCardinalityUri, childCardinalityUri, null);
648    }
649
650    @Override
651    public AssociationDefinitionModelImpl newAssociationDefinitionModel(String assocTypeUri,
652                                                    String customAssocTypeUri, boolean includeInLabel,
653                                                    String parentTypeUri, String childTypeUri,
654                                                    String parentCardinalityUri, String childCardinalityUri) {
655        return newAssociationDefinitionModel(-1, null, assocTypeUri, customAssocTypeUri, includeInLabel,
656            parentTypeUri, childTypeUri, parentCardinalityUri, childCardinalityUri, null);
657    }
658
659    /**
660     * @param   assoc   the underlying association.
661     *                  IMPORTANT: the association must identify its players <i>by URI</i> (not by ID). ### still true?
662     */
663    @Override
664    public AssociationDefinitionModelImpl newAssociationDefinitionModel(AssociationModel assoc,
665                                                    String parentCardinalityUri, String childCardinalityUri,
666                                                    ViewConfigurationModel viewConfig) {
667        return new AssociationDefinitionModelImpl((AssociationModelImpl) assoc, parentCardinalityUri,
668            childCardinalityUri, (ViewConfigurationModelImpl) viewConfig);
669    }
670
671    @Override
672    public AssociationDefinitionModelImpl newAssociationDefinitionModel(JSONObject assocDef) {
673        try {
674            AssociationModelImpl assoc = newAssociationModel(assocDef.optLong("id", -1), null,
675                assocDef.getString("assoc_type_uri"),
676                parentRole(assocDef.getString("parent_type_uri")),
677                childRole(assocDef.getString("child_type_uri")),
678                null, childTopics(assocDef)
679            );
680            //
681            if (!assocDef.has("parent_cardinality_uri") && !assoc.getTypeUri().equals("dm4.core.composition_def")) {
682                throw new RuntimeException("\"parent_cardinality_uri\" is missing");
683            }
684            //
685            return new AssociationDefinitionModelImpl(assoc,
686                assocDef.optString("parent_cardinality_uri", "dm4.core.one"),
687                assocDef.getString("child_cardinality_uri"),
688                newViewConfigurationModel(assocDef.optJSONArray("view_config_topics"))
689            );
690        } catch (Exception e) {
691            throw new RuntimeException("Parsing AssociationDefinitionModel failed (JSONObject=" + assocDef + ")", e);
692        }
693    }
694
695    /**
696     * Internal.
697     */
698    AssociationDefinitionModelImpl newAssociationDefinitionModel(long id, String uri, String assocTypeUri,
699                                                    String customAssocTypeUri, boolean includeInLabel,
700                                                    String parentTypeUri, String childTypeUri,
701                                                    String parentCardinalityUri, String childCardinalityUri,
702                                                    ViewConfigurationModel viewConfig) {
703        return new AssociationDefinitionModelImpl(
704            newAssociationModel(id, uri, assocTypeUri, parentRole(parentTypeUri), childRole(childTypeUri),
705                null, childTopics(customAssocTypeUri, includeInLabel)       // value=null
706            ),
707            parentCardinalityUri, childCardinalityUri,
708            (ViewConfigurationModelImpl) viewConfig);
709    }
710
711    /**
712     * Internal.
713     */
714    AssociationDefinitionModelImpl newAssociationDefinitionModel(ChildTopicsModel childTopics) {
715        return new AssociationDefinitionModelImpl(newAssociationModel(childTopics));
716    }
717
718    // ---
719
720    private TopicRoleModel parentRole(String parentTypeUri) {
721        return newTopicRoleModel(parentTypeUri, "dm4.core.parent_type");
722    }
723
724    private TopicRoleModel childRole(String childTypeUri) {
725        return newTopicRoleModel(childTypeUri, "dm4.core.child_type");
726    }
727
728    // ---
729
730    private ChildTopicsModel childTopics(JSONObject assocDef) throws JSONException {
731        // Note: getString()/optString() on a key with JSON null value would return the string "null"
732        String customAssocTypeUri = assocDef.isNull("custom_assoc_type_uri") ? null :
733            assocDef.getString("custom_assoc_type_uri");
734        boolean includeInLabel = assocDef.optBoolean("include_in_label");
735        //
736        return childTopics(customAssocTypeUri, includeInLabel);
737    }
738
739    private ChildTopicsModel childTopics(String customAssocTypeUri, boolean includeInLabel) {
740        ChildTopicsModel childTopics = newChildTopicsModel();
741        //
742        childTopics.put("dm4.core.include_in_label", includeInLabel);
743        //
744        if (customAssocTypeUri != null) {
745            if (customAssocTypeUri.startsWith(DEL_URI_PREFIX)) {
746                childTopics.putDeletionRef("dm4.core.assoc_type#dm4.core.custom_assoc_type",
747                    delTopicUri(customAssocTypeUri));
748            } else {
749                childTopics.putRef("dm4.core.assoc_type#dm4.core.custom_assoc_type",
750                    customAssocTypeUri);
751            }
752        }
753        //
754        return childTopics;
755    }
756
757
758
759    // === ViewConfigurationModel ===
760
761    @Override
762    public ViewConfigurationModelImpl newViewConfigurationModel() {
763        return new ViewConfigurationModelImpl(new HashMap());
764    }    
765
766    @Override
767    public ViewConfigurationModelImpl newViewConfigurationModel(Iterable<? extends TopicModel> configTopics) {
768        Map<String, TopicModelImpl> _configTopics = new HashMap();
769        for (TopicModel configTopic : configTopics) {
770            _configTopics.put(configTopic.getTypeUri(), (TopicModelImpl) configTopic);
771        }
772        return new ViewConfigurationModelImpl(_configTopics);
773    }    
774
775    /**
776     * @param   configTopics    may be null
777     */
778    @Override
779    public ViewConfigurationModelImpl newViewConfigurationModel(JSONArray configTopics) {
780        try {
781            Map<String, TopicModelImpl> _configTopics = new HashMap();
782            if (configTopics != null) {
783                for (int i = 0; i < configTopics.length(); i++) {
784                    TopicModelImpl configTopic = newTopicModel(configTopics.getJSONObject(i));
785                    _configTopics.put(configTopic.getTypeUri(), configTopic);
786                }
787            }
788            return new ViewConfigurationModelImpl(_configTopics);
789        } catch (Exception e) {
790            throw new RuntimeException("Parsing ViewConfigurationModel failed (JSONArray=" + configTopics + ")", e);
791        }
792    }    
793
794
795
796    // === Topicmaps ===
797
798    @Override
799    public TopicViewModel newTopicViewModel(TopicModel topic, ViewProperties viewProps) {
800        return new TopicViewModelImpl((TopicModelImpl) topic, viewProps);
801    }
802
803    @Override
804    public AssociationViewModel newAssociationViewModel(AssociationModel assoc) {
805        return new AssociationViewModelImpl((AssociationModelImpl) assoc);
806    }
807
808
809
810    // === Facets ===
811
812    @Override
813    public FacetValueModel newFacetValueModel(String childTypeUri) {
814        return new FacetValueModelImpl(childTypeUri, this);
815    }
816
817    @Override
818    public FacetValueModel newFacetValueModel(JSONObject facetValue) {
819        try {
820            ChildTopicsModelImpl childTopics = newChildTopicsModel(facetValue);
821            if (childTopics.size() != 1) {
822                throw new RuntimeException("There are " + childTopics.size() + " child topic entries (expected is 1)");
823            }
824            return new FacetValueModelImpl(childTopics);
825        } catch (Exception e) {
826            throw new RuntimeException("Parsing FacetValueModel failed (JSONObject=" + facetValue + ")", e);
827        }
828    }
829
830    // ------------------------------------------------------------------------------------------------- Private Methods
831
832    private PersistenceLayer pl() {
833        if (pl == null) {
834            throw new RuntimeException("before using the ModelFactory a PersistenceLayer must be set");
835        }
836        return pl;
837    }
838}