001package de.deepamehta.core.impl; 002 003import de.deepamehta.core.model.AssociationDefinitionModel; 004import de.deepamehta.core.model.AssociationModel; 005import de.deepamehta.core.model.ChildTopicsModel; 006import de.deepamehta.core.model.DeepaMehtaObjectModel; 007import de.deepamehta.core.model.RelatedTopicModel; 008import de.deepamehta.core.model.SimpleValue; 009import de.deepamehta.core.model.TopicModel; 010import de.deepamehta.core.model.TopicReferenceModel; 011import de.deepamehta.core.model.TypeModel; 012 013import java.util.List; 014import java.util.logging.Logger; 015 016 017 018/** 019 * Helper for storing/fetching simple values and composite value models. 020 * 021 * ### TODO: unify with DeepaMehtaObjectModelImpl and then drop this class? 022 */ 023class ValueStorage { 024 025 // ---------------------------------------------------------------------------------------------- Instance Variables 026 027 private PersistenceLayer pl; 028 029 private Logger logger = Logger.getLogger(getClass().getName()); 030 031 // ---------------------------------------------------------------------------------------------------- Constructors 032 033 ValueStorage(PersistenceLayer pl) { 034 this.pl = pl; 035 } 036 037 // ----------------------------------------------------------------------------------------- Package Private Methods 038 039 /** 040 * Fetches the child topic models (recursively) of the given parent object model and updates it in-place. 041 * ### TODO: recursion is required in some cases (e.g. when fetching a topic through REST API) but is possibly 042 * overhead in others (e.g. when updating composite structures). 043 */ 044 void fetchChildTopics(DeepaMehtaObjectModel parent) { 045 for (AssociationDefinitionModel assocDef : ((DeepaMehtaObjectModelImpl) parent).getType().getAssocDefs()) { 046 fetchChildTopics(parent, assocDef); 047 } 048 } 049 050 /** 051 * Fetches the child topic models (recursively) of the given parent object model and updates it in-place. 052 * ### TODO: recursion is required in some cases (e.g. when fetching a topic through REST API) but is possibly 053 * overhead in others (e.g. when updating composite structures). 054 * <p> 055 * Works for both, "one" and "many" association definitions. 056 * 057 * @param assocDef The child topic models according to this association definition are fetched. 058 */ 059 void fetchChildTopics(DeepaMehtaObjectModel parent, AssociationDefinitionModel assocDef) { 060 try { 061 ChildTopicsModel childTopics = parent.getChildTopicsModel(); 062 String cardinalityUri = assocDef.getChildCardinalityUri(); 063 String assocDefUri = assocDef.getAssocDefUri(); 064 if (cardinalityUri.equals("dm4.core.one")) { 065 RelatedTopicModel childTopic = fetchChildTopic(parent.getId(), assocDef); 066 // Note: topics just created have no child topics yet 067 if (childTopic != null) { 068 childTopics.put(assocDefUri, childTopic); 069 fetchChildTopics(childTopic); // recursion 070 } 071 } else if (cardinalityUri.equals("dm4.core.many")) { 072 for (RelatedTopicModel childTopic : fetchChildTopics(parent.getId(), assocDef)) { 073 childTopics.add(assocDefUri, childTopic); 074 fetchChildTopics(childTopic); // recursion 075 } 076 } else { 077 throw new RuntimeException("\"" + cardinalityUri + "\" is an unexpected cardinality URI"); 078 } 079 } catch (Exception e) { 080 throw new RuntimeException("Fetching the \"" + assocDef.getAssocDefUri() + "\" child topics of object " + 081 parent.getId() + " failed", e); 082 } 083 } 084 085 // --- 086 087 /** 088 * Stores and indexes the specified model's value, either a simple value or a composite value (child topics). 089 * Depending on the model type's data type dispatches either to storeSimpleValue() or to storeChildTopics(). 090 * <p> 091 * Called to store the initial value of a newly created topic/association. 092 */ 093 void storeValue(DeepaMehtaObjectModelImpl model) { 094 if (model.getType().getDataTypeUri().equals("dm4.core.composite")) { 095 storeChildTopics(model); 096 model.calculateLabelAndUpdate(); 097 } else { 098 model.storeSimpleValue(); 099 } 100 } 101 102 // ------------------------------------------------------------------------------------------------- Private Methods 103 104 /** 105 * Stores the composite value (child topics) of the specified topic or association model. 106 * Called to store the initial value of a newly created topic/association. 107 * <p> 108 * Note: the given model can contain childs not defined in the type definition. 109 * Only the childs defined in the type definition are stored. 110 */ 111 private void storeChildTopics(DeepaMehtaObjectModelImpl parent) { 112 ChildTopicsModelImpl model = null; 113 try { 114 model = parent.getChildTopicsModel(); 115 for (AssociationDefinitionModel assocDef : parent.getType().getAssocDefs()) { 116 String assocDefUri = assocDef.getAssocDefUri(); 117 String cardinalityUri = assocDef.getChildCardinalityUri(); 118 if (cardinalityUri.equals("dm4.core.one")) { 119 RelatedTopicModelImpl childTopic = model.getTopicOrNull(assocDefUri); 120 if (childTopic != null) { // skip if not contained in create request 121 storeChildTopic(childTopic, parent, assocDef); 122 } 123 } else if (cardinalityUri.equals("dm4.core.many")) { 124 List<RelatedTopicModelImpl> childTopics = model.getTopicsOrNull(assocDefUri); 125 if (childTopics != null) { // skip if not contained in create request 126 for (RelatedTopicModelImpl childTopic : childTopics) { 127 storeChildTopic(childTopic, parent, assocDef); 128 } 129 } 130 } else { 131 throw new RuntimeException("\"" + cardinalityUri + "\" is an unexpected cardinality URI"); 132 } 133 } 134 } catch (Exception e) { 135 throw new RuntimeException("Storing the child topics of object " + parent.getId() + " failed (" + 136 model + ")", e); 137 } 138 } 139 140 private void storeChildTopic(RelatedTopicModelImpl childTopic, DeepaMehtaObjectModel parent, 141 AssociationDefinitionModel assocDef) { 142 if (childTopic instanceof TopicReferenceModel) { 143 resolveReference((TopicReferenceModel) childTopic); 144 } else { 145 pl.createTopic(childTopic); 146 } 147 associateChildTopic(parent, childTopic, assocDef); 148 } 149 150 // --- 151 152 /** 153 * Replaces a reference with the real thing. 154 */ 155 void resolveReference(TopicReferenceModel topicRef) { 156 topicRef.set(fetchReferencedTopic(topicRef)); 157 } 158 159 private DeepaMehtaObjectModel fetchReferencedTopic(TopicReferenceModel topicRef) { 160 // Note: the resolved topic must be fetched including its child topics. 161 // They might be required for label calculation and/or at client-side. 162 if (topicRef.isReferenceById()) { 163 return pl.fetchTopic(topicRef.getId()).loadChildTopics(); 164 } else if (topicRef.isReferenceByUri()) { 165 TopicModelImpl topic = pl.fetchTopic("uri", new SimpleValue(topicRef.getUri())); 166 if (topic == null) { 167 throw new RuntimeException("Topic with URI \"" + topicRef.getUri() + "\" not found"); 168 } 169 return topic.loadChildTopics(); 170 } else { 171 throw new RuntimeException("Invalid topic reference (" + topicRef + ")"); 172 } 173 } 174 175 // --- 176 177 /** 178 * Creates an association between the given parent object ("Parent" role) and the child topic ("Child" role). 179 * The association type is taken from the given association definition. 180 */ 181 void associateChildTopic(DeepaMehtaObjectModel parent, RelatedTopicModel childTopic, 182 AssociationDefinitionModel assocDef) { 183 AssociationModel assoc = childTopic.getRelatingAssociation(); 184 assoc.setTypeUri(assocDef.getInstanceLevelAssocTypeUri()); 185 assoc.setRoleModel1(parent.createRoleModel("dm4.core.parent")); 186 assoc.setRoleModel2(childTopic.createRoleModel("dm4.core.child")); 187 pl.createAssociation((AssociationModelImpl) assoc); 188 } 189 190 191 192 // === Helper === 193 194 /** 195 * Fetches and returns a child topic or <code>null</code> if no such topic extists. 196 */ 197 private RelatedTopicModel fetchChildTopic(long parentId, AssociationDefinitionModel assocDef) { 198 return pl.fetchRelatedTopic( 199 parentId, 200 assocDef.getInstanceLevelAssocTypeUri(), 201 "dm4.core.parent", "dm4.core.child", 202 assocDef.getChildTypeUri() 203 ); 204 } 205 206 private List<RelatedTopicModelImpl> fetchChildTopics(long parentId, AssociationDefinitionModel assocDef) { 207 return pl.fetchRelatedTopics( 208 parentId, 209 assocDef.getInstanceLevelAssocTypeUri(), 210 "dm4.core.parent", "dm4.core.child", 211 assocDef.getChildTypeUri() 212 ); 213 } 214}