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    /* TopicModelImpl(long id, String typeUri) {
107        super(id, typeUri);
108    } */
109
110    @Override
111    public TopicModelImpl newTopicModel(long id, ChildTopicsModel childTopics) {
112        return newTopicModel(id, null, null, null, childTopics);
113    }
114
115    @Override
116    public TopicModelImpl newTopicModel(JSONObject topic) {
117        try {
118            return new TopicModelImpl(newDeepaMehtaObjectModel(topic));
119        } catch (Exception e) {
120            throw new RuntimeException("Parsing TopicModel failed (JSONObject=" + topic + ")", e);
121        }
122    }
123
124
125
126    // === AssociationModel ===
127
128    @Override
129    public AssociationModelImpl newAssociationModel(long id, String uri, String typeUri, RoleModel roleModel1,
130                                                RoleModel roleModel2, SimpleValue value, ChildTopicsModel childTopics) {
131        return new AssociationModelImpl(newDeepaMehtaObjectModel(id, uri, typeUri, value, childTopics),
132            (RoleModelImpl) roleModel1, (RoleModelImpl) roleModel2);
133    }
134
135    @Override
136    public AssociationModelImpl newAssociationModel(String typeUri, RoleModel roleModel1, RoleModel roleModel2) {
137        return newAssociationModel(-1, null, typeUri, roleModel1, roleModel2, null, null);
138    }
139
140    @Override
141    public AssociationModelImpl newAssociationModel(String typeUri, RoleModel roleModel1, RoleModel roleModel2,
142                                                                                      ChildTopicsModel childTopics) {
143        return newAssociationModel(-1, null, typeUri, roleModel1, roleModel2, null, childTopics);
144    }
145
146    // ### TODO: don't leave the assoc uninitialized. Refactoring needed. See comment in TypeCache#put methods.
147    @Override
148    public AssociationModelImpl newAssociationModel() {
149        return newAssociationModel(-1, null, null, null, null, null, null);
150    }
151
152    // ### TODO: don't leave the assoc uninitialized. Refactoring needed. See comment in TypeCache#put methods.
153    @Override
154    public AssociationModelImpl newAssociationModel(ChildTopicsModel childTopics) {
155        return newAssociationModel(-1, null, null, null, null, null, childTopics);
156    }
157
158    @Override
159    public AssociationModelImpl newAssociationModel(long id, String uri, String typeUri, RoleModel roleModel1,
160                                                                                     RoleModel roleModel2) {
161        return newAssociationModel(id, uri, typeUri, roleModel1, roleModel2, null, null);
162    }
163
164    @Override
165    public AssociationModelImpl newAssociationModel(AssociationModel assoc) {
166        return new AssociationModelImpl((AssociationModelImpl) assoc);
167    }
168
169    @Override
170    public AssociationModelImpl newAssociationModel(JSONObject assoc) {
171        try {
172            return new AssociationModelImpl(newDeepaMehtaObjectModel(assoc),
173                assoc.has("role_1") ? parseRole(assoc.getJSONObject("role_1")) : null,
174                assoc.has("role_2") ? parseRole(assoc.getJSONObject("role_2")) : null
175            );
176        } catch (Exception e) {
177            throw new RuntimeException("Parsing AssociationModel failed (JSONObject=" + assoc + ")", e);
178        }
179    }
180
181    // ---
182
183    private RoleModelImpl parseRole(JSONObject roleModel) {
184        if (roleModel.has("topic_id") || roleModel.has("topic_uri")) {
185            return newTopicRoleModel(roleModel);
186        } else if (roleModel.has("assoc_id")) {
187            return newAssociationRoleModel(roleModel);
188        } else {
189            throw new RuntimeException("Parsing TopicRoleModel/AssociationRoleModel failed (JSONObject=" +
190                roleModel + ")");
191        }
192    }
193
194
195
196    // === DeepaMehtaObjectModel ===
197
198    /**
199     * @param   id          Optional (-1 is a valid value and represents "not set").
200     * @param   uri         Optional (<code>null</code> is a valid value).
201     * @param   typeUri     Mandatory in the context of a create operation.
202     *                      Optional (<code>null</code> is a valid value) in the context of an update operation.
203     * @param   value       Optional (<code>null</code> is a valid value).
204     * @param   childTopics Optional (<code>null</code> is a valid value and is transformed into an empty composite).
205     */
206    DeepaMehtaObjectModelImpl newDeepaMehtaObjectModel(long id, String uri, String typeUri, SimpleValue value,
207                                                                                         ChildTopicsModel childTopics) {
208        return new DeepaMehtaObjectModelImpl(id, uri, typeUri, value, (ChildTopicsModelImpl) childTopics, pl());
209    }
210
211    DeepaMehtaObjectModelImpl newDeepaMehtaObjectModel(JSONObject object) throws JSONException {
212        return newDeepaMehtaObjectModel(
213            object.optLong("id", -1),
214            object.optString("uri", null),
215            object.optString("type_uri", null),
216            object.has("value") ? new SimpleValue(object.get("value")) : null,
217            object.has("childs") ? newChildTopicsModel(object.getJSONObject("childs")) : null
218        );
219    }
220
221
222
223    // === ChildTopicsModel ===
224
225    @Override
226    public ChildTopicsModelImpl newChildTopicsModel() {
227        return new ChildTopicsModelImpl(new HashMap(), this);
228    }
229
230    @Override
231    public ChildTopicsModelImpl newChildTopicsModel(JSONObject values) {
232        try {
233            Map<String, Object> childTopics = new HashMap();
234            Iterator<String> i = values.keys();
235            while (i.hasNext()) {
236                String assocDefUri = i.next();
237                String childTypeUri = childTypeUri(assocDefUri);
238                Object value = values.get(assocDefUri);
239                if (!(value instanceof JSONArray)) {
240                    childTopics.put(assocDefUri, createTopicModel(childTypeUri, value));
241                } else {
242                    JSONArray valueArray = (JSONArray) value;
243                    List<RelatedTopicModel> topics = new ArrayList();
244                    childTopics.put(assocDefUri, topics);
245                    for (int j = 0; j < valueArray.length(); j++) {
246                        topics.add(createTopicModel(childTypeUri, valueArray.get(j)));
247                    }
248                }
249            }
250            return new ChildTopicsModelImpl(childTopics, this);
251        } catch (Exception e) {
252            throw new RuntimeException("Parsing ChildTopicsModel failed (JSONObject=" + values + ")", e);
253        }
254    }
255
256    @Override
257    public String childTypeUri(String assocDefUri) {
258        return assocDefUri.split("#")[0];
259    }
260
261    // ---
262
263    /**
264     * Creates a topic model from a JSON value.
265     *
266     * Both topic serialization formats are supported:
267     * 1) canonic format -- contains entire topic models.
268     * 2) simplified format -- contains the topic value only (simple or composite).
269     */
270    private RelatedTopicModel createTopicModel(String childTypeUri, Object value) throws JSONException {
271        if (value instanceof JSONObject) {
272            JSONObject val = (JSONObject) value;
273            // we detect the canonic format by checking for mandatory topic properties
274            if (val.has("value") || val.has("childs")) {
275                // canonic format (topic or topic reference)
276                AssociationModel relatingAssoc = null;
277                if (val.has("assoc")) {
278                    relatingAssoc = newAssociationModel(val.getJSONObject("assoc"));
279                }
280                if (val.has("value")) {
281                    RelatedTopicModel topicRef = createReferenceModel(val.get("value"), relatingAssoc);
282                    if (topicRef != null) {
283                        return topicRef;
284                    }
285                }
286                //
287                initTypeUri(val, childTypeUri);
288                //
289                TopicModel topic = newTopicModel(val);
290                if (relatingAssoc != null) {
291                    return newRelatedTopicModel(topic, relatingAssoc);
292                } else {
293                    return newRelatedTopicModel(topic);
294                }
295            } else {
296                // simplified format (composite topic)
297                return newRelatedTopicModel(newTopicModel(childTypeUri, newChildTopicsModel(val)));
298            }
299        } else {
300            // simplified format (simple topic or topic reference)
301            RelatedTopicModel topicRef = createReferenceModel(value, null);
302            if (topicRef != null) {
303                return topicRef;
304            }
305            // simplified format (simple topic)
306            return newRelatedTopicModel(newTopicModel(childTypeUri, new SimpleValue(value)));
307        }
308    }
309
310    private RelatedTopicModel createReferenceModel(Object value, AssociationModel relatingAssoc) {
311        if (value instanceof String) {
312            String val = (String) value;
313            if (val.startsWith(REF_ID_PREFIX)) {
314                long topicId = refTopicId(val);
315                if (relatingAssoc != null) {
316                    return newTopicReferenceModel(topicId, relatingAssoc);
317                } else {
318                    return newTopicReferenceModel(topicId);
319                }
320            } else if (val.startsWith(REF_URI_PREFIX)) {
321                String topicUri = refTopicUri(val);
322                if (relatingAssoc != null) {
323                    return newTopicReferenceModel(topicUri, relatingAssoc);
324                } else {
325                    return newTopicReferenceModel(topicUri);
326                }
327            } else if (val.startsWith(DEL_ID_PREFIX)) {
328                return newTopicDeletionModel(delTopicId(val));
329            } else if (val.startsWith(DEL_URI_PREFIX)) {
330                return newTopicDeletionModel(delTopicUri(val));
331            }
332        }
333        return null;
334    }
335
336    private void initTypeUri(JSONObject value, String childTypeUri) throws JSONException {
337        if (!value.has("type_uri")) {
338            value.put("type_uri", childTypeUri);
339        } else {
340            // sanity check
341            String typeUri = value.getString("type_uri");
342            if (!typeUri.equals(childTypeUri)) {
343                throw new IllegalArgumentException("A \"" + childTypeUri + "\" topic model has type_uri=\"" +
344                    typeUri + "\"");
345            }
346        }
347    }
348
349    // ---
350
351    private long refTopicId(String val) {
352        return Long.parseLong(val.substring(REF_ID_PREFIX.length()));
353    }
354
355    private String refTopicUri(String val) {
356        return val.substring(REF_URI_PREFIX.length());
357    }
358
359    private long delTopicId(String val) {
360        return Long.parseLong(val.substring(DEL_ID_PREFIX.length()));
361    }
362
363    private String delTopicUri(String val) {
364        return val.substring(DEL_URI_PREFIX.length());
365    }
366
367
368
369    // === TopicRoleModel ===
370
371    @Override
372    public TopicRoleModelImpl newTopicRoleModel(long topicId, String roleTypeUri) {
373        return new TopicRoleModelImpl(topicId, roleTypeUri, pl());
374    }
375
376    @Override
377    public TopicRoleModelImpl newTopicRoleModel(String topicUri, String roleTypeUri) {
378        return new TopicRoleModelImpl(topicUri, roleTypeUri, pl());
379    }
380
381    @Override
382    public TopicRoleModelImpl newTopicRoleModel(JSONObject topicRoleModel) {
383        try {
384            long topicId       = topicRoleModel.optLong("topic_id", -1);
385            String topicUri    = topicRoleModel.optString("topic_uri", null);
386            String roleTypeUri = topicRoleModel.getString("role_type_uri");
387            //
388            if (topicId == -1 && topicUri == null) {
389                throw new IllegalArgumentException("Neiter \"topic_id\" nor \"topic_uri\" is set");
390            }
391            if (topicId != -1 && topicUri != null) {
392                throw new IllegalArgumentException("\"topic_id\" and \"topic_uri\" must not be set at the same time");
393            }
394            //
395            if (topicId != -1) {
396                return newTopicRoleModel(topicId, roleTypeUri);
397            } else {
398                return newTopicRoleModel(topicUri, roleTypeUri);
399            }
400        } catch (Exception e) {
401            throw new RuntimeException("Parsing TopicRoleModel failed (JSONObject=" + topicRoleModel + ")", e);
402        }
403    }
404
405
406
407    // === AssociationRoleModel ===
408
409    @Override
410    public AssociationRoleModelImpl newAssociationRoleModel(long assocId, String roleTypeUri) {
411        return new AssociationRoleModelImpl(assocId, roleTypeUri, pl());
412    }    
413
414    @Override
415    public AssociationRoleModelImpl newAssociationRoleModel(JSONObject assocRoleModel) {
416        try {
417            long assocId       = assocRoleModel.getLong("assoc_id");
418            String roleTypeUri = assocRoleModel.getString("role_type_uri");
419            return newAssociationRoleModel(assocId, roleTypeUri);
420        } catch (Exception e) {
421            throw new RuntimeException("Parsing AssociationRoleModel failed (JSONObject=" + assocRoleModel + ")", e);
422        }
423    }    
424
425
426
427    // === RelatedTopicModel ===
428
429    @Override
430    public RelatedTopicModelImpl newRelatedTopicModel(long topicId) {
431        return new RelatedTopicModelImpl(newTopicModel(topicId), newAssociationModel());
432    }
433
434    @Override
435    public RelatedTopicModelImpl newRelatedTopicModel(long topicId, AssociationModel relatingAssoc) {
436        return new RelatedTopicModelImpl(newTopicModel(topicId), (AssociationModelImpl) relatingAssoc);
437    }
438
439    @Override
440    public RelatedTopicModelImpl newRelatedTopicModel(String topicUri) {
441        return new RelatedTopicModelImpl(newTopicModel(topicUri, (String) null), newAssociationModel());
442                                                          // topicTypeUri=null
443    }
444
445    @Override
446    public RelatedTopicModelImpl newRelatedTopicModel(String topicUri, AssociationModel relatingAssoc) {
447        return new RelatedTopicModelImpl(newTopicModel(topicUri, (String) null), (AssociationModelImpl) relatingAssoc);
448                                                          // topicTypeUri=null
449    }
450
451    @Override
452    public RelatedTopicModelImpl newRelatedTopicModel(String topicTypeUri, SimpleValue value) {
453        return new RelatedTopicModelImpl(newTopicModel(topicTypeUri, value), newAssociationModel());
454    }
455
456    @Override
457    public RelatedTopicModelImpl newRelatedTopicModel(String topicTypeUri, ChildTopicsModel childTopics) {
458        return new RelatedTopicModelImpl(newTopicModel(topicTypeUri, childTopics), newAssociationModel());
459    }
460
461    @Override
462    public RelatedTopicModelImpl newRelatedTopicModel(TopicModel topic) {
463        return new RelatedTopicModelImpl((TopicModelImpl) topic, newAssociationModel());
464    }
465
466    @Override
467    public RelatedTopicModelImpl newRelatedTopicModel(TopicModel topic, AssociationModel relatingAssoc) {
468        return new RelatedTopicModelImpl((TopicModelImpl) topic, (AssociationModelImpl) relatingAssoc);
469    }
470
471
472
473    // === RelatedAssociationModel ===
474
475    @Override
476    public RelatedAssociationModel newRelatedAssociationModel(AssociationModel assoc, AssociationModel relatingAssoc) {
477        return new RelatedAssociationModelImpl((AssociationModelImpl) assoc, (AssociationModelImpl) relatingAssoc);
478    }
479
480
481
482    // === TopicReferenceModel ===
483
484    @Override
485    public TopicReferenceModel newTopicReferenceModel(long topicId) {
486        return new TopicReferenceModelImpl(newRelatedTopicModel(topicId));
487    }
488
489    @Override
490    public TopicReferenceModel newTopicReferenceModel(long topicId, AssociationModel relatingAssoc) {
491        return new TopicReferenceModelImpl(newRelatedTopicModel(topicId, relatingAssoc));
492    }
493
494    @Override
495    public TopicReferenceModel newTopicReferenceModel(String topicUri) {
496        return new TopicReferenceModelImpl(newRelatedTopicModel(topicUri));
497    }
498
499    @Override
500    public TopicReferenceModel newTopicReferenceModel(String topicUri, AssociationModel relatingAssoc) {
501        return new TopicReferenceModelImpl(newRelatedTopicModel(topicUri, relatingAssoc));
502    }
503
504    @Override
505    public TopicReferenceModel newTopicReferenceModel(long topicId, ChildTopicsModel relatingAssocChildTopics) {
506        return new TopicReferenceModelImpl(
507            newRelatedTopicModel(topicId, newAssociationModel(relatingAssocChildTopics))
508        );
509    }
510
511    @Override
512    public TopicReferenceModel newTopicReferenceModel(String topicUri, ChildTopicsModel relatingAssocChildTopics) {
513        return new TopicReferenceModelImpl(
514            newRelatedTopicModel(topicUri, newAssociationModel(relatingAssocChildTopics))
515        );
516    }
517
518
519
520    // === TopicDeletionModel ===
521
522    @Override
523    public TopicDeletionModel newTopicDeletionModel(long topicId) {
524        return new TopicDeletionModelImpl(newRelatedTopicModel(topicId));
525    }
526
527    @Override
528    public TopicDeletionModel newTopicDeletionModel(String topicUri) {
529        return new TopicDeletionModelImpl(newRelatedTopicModel(topicUri));
530    }
531
532
533
534    // === TopicTypeModel ===
535
536    @Override
537    public TopicTypeModelImpl newTopicTypeModel(TopicModel typeTopic, String dataTypeUri,
538                                            List<IndexMode> indexModes, List<AssociationDefinitionModel> assocDefs,
539                                            List<String> labelConfig, ViewConfigurationModel viewConfig) {
540        return new TopicTypeModelImpl(newTypeModel(typeTopic, dataTypeUri, indexModes, assocDefs, labelConfig,
541            (ViewConfigurationModelImpl) viewConfig));
542    }
543
544    @Override
545    public TopicTypeModelImpl newTopicTypeModel(String uri, String value, String dataTypeUri) {
546        return new TopicTypeModelImpl(newTypeModel(uri, "dm4.core.topic_type", new SimpleValue(value), dataTypeUri));
547    }
548
549    @Override
550    public TopicTypeModelImpl newTopicTypeModel(JSONObject topicType) {
551        try {
552            return new TopicTypeModelImpl(newTypeModel(topicType.put("type_uri", "dm4.core.topic_type")));
553        } catch (Exception e) {
554            throw new RuntimeException("Parsing TopicTypeModel failed (JSONObject=" + topicType + ")", e);
555        }
556    }
557
558
559
560    // === AssociationTypeModel ===
561
562    @Override
563    public AssociationTypeModelImpl newAssociationTypeModel(TopicModel typeTopic, String dataTypeUri,
564                                                 List<IndexMode> indexModes, List<AssociationDefinitionModel> assocDefs,
565                                                 List<String> labelConfig, ViewConfigurationModel viewConfig) {
566        return new AssociationTypeModelImpl(newTypeModel(typeTopic, dataTypeUri, indexModes, assocDefs, labelConfig,
567            (ViewConfigurationModelImpl) viewConfig));
568    }
569
570    @Override
571    public AssociationTypeModelImpl newAssociationTypeModel(String uri, String value, String dataTypeUri) {
572        return new AssociationTypeModelImpl(newTypeModel(uri, "dm4.core.assoc_type", new SimpleValue(value),
573            dataTypeUri));
574    }
575
576    @Override
577    public AssociationTypeModelImpl newAssociationTypeModel(JSONObject assocType) {
578        try {
579            return new AssociationTypeModelImpl(newTypeModel(assocType.put("type_uri", "dm4.core.assoc_type")));
580        } catch (Exception e) {
581            throw new RuntimeException("Parsing AssociationTypeModel failed (JSONObject=" + assocType + ")", e);
582        }
583    }
584
585
586
587    // === TypeModel ===
588
589    TypeModelImpl newTypeModel(TopicModel typeTopic, String dataTypeUri, List<IndexMode> indexModes,
590                                  List<AssociationDefinitionModel> assocDefs, List<String> labelConfig,
591                                  ViewConfigurationModelImpl viewConfig) {
592        return new TypeModelImpl((TopicModelImpl) typeTopic, dataTypeUri, indexModes, assocDefs, labelConfig,
593            viewConfig);
594    }
595
596    TypeModelImpl newTypeModel(String uri, String typeUri, SimpleValue value, String dataTypeUri) {
597        return new TypeModelImpl(newTopicModel(uri, typeUri, value), dataTypeUri,
598            new ArrayList(), new ArrayList(), new ArrayList(), newViewConfigurationModel()
599        );
600    }
601
602    TypeModelImpl newTypeModel(JSONObject typeModel) throws JSONException {
603        TopicModelImpl typeTopic = newTopicModel(typeModel);
604        return new TypeModelImpl(typeTopic,
605            typeModel.getString("data_type_uri"),
606            parseIndexModes(typeModel.optJSONArray("index_mode_uris")),
607            parseAssocDefs(typeModel.optJSONArray("assoc_defs"), typeTopic.getUri()),
608            parseLabelConfig(typeModel.optJSONArray("label_config")),
609            newViewConfigurationModel(typeModel.optJSONArray("view_config_topics"))
610        );
611    }
612
613    // ---
614
615    private List<IndexMode> parseIndexModes(JSONArray indexModeUris) {
616        try {
617            List<IndexMode> indexModes = new ArrayList();
618            if (indexModeUris != null) {
619                for (int i = 0; i < indexModeUris.length(); i++) {
620                    indexModes.add(IndexMode.fromUri(indexModeUris.getString(i)));
621                }
622            }
623            return indexModes;
624        } catch (Exception e) {
625            throw new RuntimeException("Parsing index modes failed (JSONArray=" + indexModeUris + ")", e);
626        }
627    }
628
629    private List<AssociationDefinitionModel> parseAssocDefs(JSONArray assocDefs, String parentTypeUri) throws
630                                                                                                       JSONException {
631        List<AssociationDefinitionModel> _assocDefs = new ArrayList();
632        if (assocDefs != null) {
633            for (int i = 0; i < assocDefs.length(); i++) {
634                JSONObject assocDef = assocDefs.getJSONObject(i)
635                    .put("parent_type_uri", parentTypeUri);
636                _assocDefs.add(newAssociationDefinitionModel(assocDef));
637            }
638        }
639        return _assocDefs;
640    }
641
642    private List<String> parseLabelConfig(JSONArray labelConfig) {
643        return labelConfig != null ? DeepaMehtaUtils.toList(labelConfig) : new ArrayList();
644    }
645
646
647
648    // === AssociationDefinitionModel ===
649
650    @Override
651    public AssociationDefinitionModelImpl newAssociationDefinitionModel(
652                                                    long id, String uri, String assocTypeUri, String customAssocTypeUri,
653                                                    String parentTypeUri, String childTypeUri,
654                                                    String parentCardinalityUri, String childCardinalityUri,
655                                                    ViewConfigurationModel viewConfig) {
656        return new AssociationDefinitionModelImpl(
657            newAssociationModel(id, uri, assocTypeUri, parentRole(parentTypeUri), childRole(childTypeUri),
658                null, childTopics(customAssocTypeUri)
659            ),
660            parentCardinalityUri, childCardinalityUri, (ViewConfigurationModelImpl) viewConfig
661        );
662    }
663
664    @Override
665    public AssociationDefinitionModelImpl newAssociationDefinitionModel(String assocTypeUri,
666                                                    String parentTypeUri, String childTypeUri,
667                                                    String parentCardinalityUri, String childCardinalityUri) {
668        return newAssociationDefinitionModel(-1, null, assocTypeUri, null, parentTypeUri, childTypeUri,
669            parentCardinalityUri, childCardinalityUri, null);
670    }
671
672    @Override
673    public AssociationDefinitionModelImpl newAssociationDefinitionModel(String assocTypeUri, String customAssocTypeUri,
674                                                    String parentTypeUri, String childTypeUri,
675                                                    String parentCardinalityUri, String childCardinalityUri) {
676        return newAssociationDefinitionModel(-1, null, assocTypeUri, customAssocTypeUri, parentTypeUri, childTypeUri,
677            parentCardinalityUri, childCardinalityUri, null);
678    }
679
680    /**
681     * @param   assoc   the underlying association.
682     *                  IMPORTANT: the association must identify its players <i>by URI</i> (not by ID). ### still true?
683     */
684    @Override
685    public AssociationDefinitionModelImpl newAssociationDefinitionModel(AssociationModel assoc,
686                                                    String parentCardinalityUri, String childCardinalityUri,
687                                                    ViewConfigurationModel viewConfig) {
688        return new AssociationDefinitionModelImpl((AssociationModelImpl) assoc, parentCardinalityUri,
689            childCardinalityUri, (ViewConfigurationModelImpl) viewConfig);
690    }
691
692    /**
693     * Note: the AssociationDefinitionModel constructed by this constructor remains partially uninitialized,
694     * which is OK for an update assoc def operation. It can not be used for a create operation.
695     *
696    AssociationDefinitionModelImpl(AssociationModel assoc) {
697        // ### FIXME: the assoc must identify its players **by URI**
698        super(assoc);
699    } */
700
701    @Override
702    public AssociationDefinitionModelImpl newAssociationDefinitionModel(JSONObject assocDef) {
703        try {
704            AssociationModelImpl assoc = newAssociationModel(assocDef.optLong("id", -1), null,
705                assocDef.getString("assoc_type_uri"),
706                parentRole(assocDef.getString("parent_type_uri")),
707                childRole(assocDef.getString("child_type_uri")),
708                null, childTopics(assocDef)
709            );
710            //
711            if (!assocDef.has("parent_cardinality_uri") && !assoc.getTypeUri().equals("dm4.core.composition_def")) {
712                throw new RuntimeException("\"parent_cardinality_uri\" is missing");
713            }
714            //
715            return new AssociationDefinitionModelImpl(assoc,
716                assocDef.optString("parent_cardinality_uri", "dm4.core.one"),
717                assocDef.getString("child_cardinality_uri"),
718                newViewConfigurationModel(assocDef.optJSONArray("view_config_topics"))
719            );
720        } catch (Exception e) {
721            throw new RuntimeException("Parsing AssociationDefinitionModel failed (JSONObject=" + assocDef + ")", e);
722        }
723    }
724
725    // ---
726
727    private TopicRoleModel parentRole(String parentTypeUri) {
728        return newTopicRoleModel(parentTypeUri, "dm4.core.parent_type");
729    }
730
731    private TopicRoleModel childRole(String childTypeUri) {
732        return newTopicRoleModel(childTypeUri, "dm4.core.child_type");
733    }
734
735    // ---
736
737    private ChildTopicsModel childTopics(JSONObject assocDef) throws JSONException {
738        // Note: getString() called on a key with JSON null value would return the string "null"
739        return childTopics(assocDef.isNull("custom_assoc_type_uri") ? null :
740            assocDef.getString("custom_assoc_type_uri"));
741    }
742
743    private ChildTopicsModel childTopics(String customAssocTypeUri) {
744        if (customAssocTypeUri != null) {
745            if (customAssocTypeUri.startsWith(DEL_URI_PREFIX)) {
746                return newChildTopicsModel().putDeletionRef("dm4.core.assoc_type#dm4.core.custom_assoc_type",
747                    delTopicUri(customAssocTypeUri));
748            } else {
749                return newChildTopicsModel().putRef("dm4.core.assoc_type#dm4.core.custom_assoc_type",
750                    customAssocTypeUri);
751            }
752        } else {
753            return null;
754        }
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   configurable    A topic type, an association type, or an association definition. ### FIXDOC
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}