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