001package de.deepamehta.core.impl; 002 003import de.deepamehta.core.model.AssociationDefinitionModel; 004import de.deepamehta.core.model.ChildTopicsModel; 005import de.deepamehta.core.model.DeepaMehtaObjectModel; 006import de.deepamehta.core.model.TopicModel; 007import de.deepamehta.core.model.TopicRoleModel; 008import de.deepamehta.core.model.ViewConfigurationModel; 009 010import org.codehaus.jettison.json.JSONObject; 011 012import java.util.ArrayList; 013import java.util.Collection; 014import java.util.List; 015import java.util.logging.Logger; 016 017 018 019class AssociationDefinitionModelImpl extends AssociationModelImpl implements AssociationDefinitionModel { 020 021 // ---------------------------------------------------------------------------------------------- Instance Variables 022 023 private String parentCardinalityUri; 024 private String childCardinalityUri; 025 026 private ViewConfigurationModelImpl viewConfig; // is never null 027 028 private Logger logger = Logger.getLogger(getClass().getName()); 029 030 // ---------------------------------------------------------------------------------------------------- Constructors 031 032 /** 033 * Remains partially uninitialzed. Only usable as an update-model (not as a create-model). 034 */ 035 AssociationDefinitionModelImpl(AssociationModelImpl assoc) { 036 this(assoc, null, null, null); 037 } 038 039 /** 040 * @param assoc the underlying association. 041 */ 042 AssociationDefinitionModelImpl(AssociationModelImpl assoc, String parentCardinalityUri, String childCardinalityUri, 043 ViewConfigurationModelImpl viewConfig) { 044 super(assoc); 045 this.parentCardinalityUri = parentCardinalityUri; 046 this.childCardinalityUri = childCardinalityUri; 047 this.viewConfig = viewConfig != null ? viewConfig : mf.newViewConfigurationModel(); 048 // ### TODO: why null check? Compare to TypeModelImpl constructor -> see previous constructor 049 } 050 051 // -------------------------------------------------------------------------------------------------- Public Methods 052 053 @Override 054 public String getAssocDefUri() { 055 String customAssocTypeUri = getCustomAssocTypeUri(); 056 return getChildTypeUri() + (customAssocTypeUri !=null ? "#" + customAssocTypeUri : ""); 057 } 058 059 @Override 060 public String getCustomAssocTypeUri() { 061 TopicModel customAssocType = getCustomAssocType(); 062 return customAssocType != null ? customAssocType.getUri() : null; 063 } 064 065 @Override 066 public String getInstanceLevelAssocTypeUri() { 067 String customAssocTypeUri = getCustomAssocTypeUri(); 068 return customAssocTypeUri !=null ? customAssocTypeUri : defaultInstanceLevelAssocTypeUri(); 069 } 070 071 @Override 072 public String getParentTypeUri() { 073 return ((TopicRoleModel) getRoleModel("dm4.core.parent_type")).getTopicUri(); 074 } 075 076 @Override 077 public String getChildTypeUri() { 078 return ((TopicRoleModel) getRoleModel("dm4.core.child_type")).getTopicUri(); 079 } 080 081 @Override 082 public String getParentCardinalityUri() { 083 return parentCardinalityUri; 084 } 085 086 @Override 087 public String getChildCardinalityUri() { 088 return childCardinalityUri; 089 } 090 091 @Override 092 public ViewConfigurationModelImpl getViewConfigModel() { 093 return viewConfig; 094 } 095 096 // --- 097 098 @Override 099 public void setParentCardinalityUri(String parentCardinalityUri) { 100 this.parentCardinalityUri = parentCardinalityUri; 101 } 102 103 @Override 104 public void setChildCardinalityUri(String childCardinalityUri) { 105 this.childCardinalityUri = childCardinalityUri; 106 } 107 108 @Override 109 public void setViewConfigModel(ViewConfigurationModel viewConfig) { 110 this.viewConfig = (ViewConfigurationModelImpl) viewConfig; 111 } 112 113 // --- 114 115 @Override 116 public JSONObject toJSON() { 117 try { 118 return super.toJSON() 119 .put("parent_cardinality_uri", parentCardinalityUri) 120 .put("child_cardinality_uri", childCardinalityUri) 121 .put("view_config_topics", viewConfig.toJSONArray()); 122 } catch (Exception e) { 123 throw new RuntimeException("Serialization failed (" + this + ")", e); 124 } 125 } 126 127 // --- 128 129 @Override 130 public String toString() { 131 return "\n association definition (" + super.toString() + 132 ",\n parent cardinality=\"" + parentCardinalityUri + 133 "\",\n child cardinality=\"" + childCardinalityUri + 134 "\",\n " + viewConfig + ")\n"; 135 } 136 137 // ----------------------------------------------------------------------------------------- Package Private Methods 138 139 140 141 @Override 142 String className() { 143 return "association definition"; 144 } 145 146 @Override 147 AssociationDefinitionImpl instantiate() { 148 return new AssociationDefinitionImpl(this, pl); 149 } 150 151 @Override 152 AssociationDefinitionModelImpl createModelWithChildTopics(ChildTopicsModel childTopics) { 153 return mf.newAssociationDefinitionModel(childTopics); 154 } 155 156 157 158 // === Core Internal Hooks === 159 160 /** 161 * 2 assoc def specific tasks must be performed once the underlying association is updated: 162 * - Update the assoc def's cardinality (in type cache + DB). Cardinality is technically not part of the type 163 * model. So, it is not handled by the generic (model-driven) object update procedure. 164 * - Possibly rehash the assoc def in type cache. Rehashing is required if the custom assoc type has changed. 165 * <p> 166 * Pre condition: these 3 assoc def parts are already up-to-date through the generic (model-driven) object update 167 * procedure: 168 * - Assoc Def type (type URI). 169 * - Custom assoc type (child topics). 170 * - "Include in Label" flag (child topics). 171 * <p> 172 * Called when update() is called on an AssociationDefinitionModel object. This is in 2 cases: 173 * - Edit a type interactively (a type topic is selected). 174 * - Programmatically call getChildTopics().set() on an AssociationDefinitionModel object, e.g. from a migration. 175 * <p> 176 * <i>Not</i> called when an association which also acts as an assoc def is edited interactively (an association is 177 * selected). In this case: 178 * - Cardinality doesn't need to be updated as Cardinality can't be edited interactively through an association. 179 * - Rehashing is already performed in TypeModelImpl#_updateAssocDef (called from AssociationModelImpl#postUpdate) 180 * 181 * @param updateModel 182 * the update data/instructions. 183 * Note: on post-update time updateModel and this (assoc def) model may differ at least because 184 * a) updateModel might contain only certain assoc def parts; this is called a "partial update" 185 * b) updateModel might contain refs and deletion-refs; this model never contains refs 186 */ 187 @Override 188 void postUpdate(DeepaMehtaObjectModel updateModel, DeepaMehtaObjectModel oldObject) { 189 super.postUpdate(updateModel, oldObject); 190 // 191 updateCardinality((AssociationDefinitionModel) updateModel); 192 } 193 194 195 196 // === Update (memory + DB) === 197 198 void updateParentCardinalityUri(String parentCardinalityUri) { 199 setParentCardinalityUri(parentCardinalityUri); // update memory 200 pl.typeStorage.storeParentCardinalityUri(id, parentCardinalityUri); // update DB 201 } 202 203 void updateChildCardinalityUri(String childCardinalityUri) { 204 setChildCardinalityUri(childCardinalityUri); // update memory 205 pl.typeStorage.storeChildCardinalityUri(id, childCardinalityUri); // update DB 206 } 207 208 209 210 // === Label Configuration === 211 212 final boolean includeInLabel() { 213 TopicModel includeInLabel = getChildTopicsModel().getTopicOrNull("dm4.core.include_in_label"); 214 if (includeInLabel == null) { 215 throw new RuntimeException("Assoc def \"" + getAssocDefUri() + "\" has no \"Include in Label\" topic"); 216 } 217 return includeInLabel.getSimpleValue().booleanValue(); 218 } 219 220 221 222 // === Access Control === 223 224 boolean isReadable() { 225 try { 226 // 1) check assoc def 227 if (!pl.hasReadAccess(this)) { 228 logger.info("### Assoc def \"" + getAssocDefUri() + "\" not READable"); 229 return false; 230 } 231 // Note: there is no need to explicitly check READability for the assoc def's child type. 232 // If the child type is not READable the entire assoc def is not READable as well. 233 // 234 // 2) check custom assoc type, if set 235 TopicModelImpl assocType = getCustomAssocType(); 236 if (assocType != null && !pl.hasReadAccess(assocType)) { 237 logger.info("### Assoc def \"" + getAssocDefUri() + "\" not READable (custom assoc type not READable)"); 238 return false; 239 } 240 // 241 return true; 242 } catch (Exception e) { 243 throw new RuntimeException("Checking assoc def READability failed (" + this + ")", e); 244 } 245 } 246 247 // ------------------------------------------------------------------------------------------------- Private Methods 248 249 250 251 // === Update === 252 253 private void updateCardinality(AssociationDefinitionModel newAssocDef) { 254 updateParentCardinality(newAssocDef.getParentCardinalityUri()); 255 updateChildCardinality(newAssocDef.getChildCardinalityUri()); 256 } 257 258 // --- 259 260 private void updateParentCardinality(String newParentCardinalityUri) { 261 // abort if no update is requested 262 if (newParentCardinalityUri == null) { 263 return; 264 } 265 // 266 String parentCardinalityUri = getParentCardinalityUri(); 267 if (!parentCardinalityUri.equals(newParentCardinalityUri)) { 268 logger.info("### Changing parent cardinality URI from \"" + parentCardinalityUri + "\" -> \"" + 269 newParentCardinalityUri + "\""); 270 updateParentCardinalityUri(newParentCardinalityUri); 271 } 272 } 273 274 private void updateChildCardinality(String newChildCardinalityUri) { 275 // abort if no update is requested 276 if (newChildCardinalityUri == null) { 277 return; 278 } 279 // 280 String childCardinalityUri = getChildCardinalityUri(); 281 if (!childCardinalityUri.equals(newChildCardinalityUri)) { 282 logger.info("### Changing child cardinality URI from \"" + childCardinalityUri + "\" -> \"" + 283 newChildCardinalityUri + "\""); 284 updateChildCardinalityUri(newChildCardinalityUri); 285 } 286 } 287 288 289 290 // === 291 292 private TopicModelImpl getCustomAssocType() { 293 return getChildTopicsModel().getTopicOrNull("dm4.core.assoc_type#dm4.core.custom_assoc_type"); 294 } 295 296 private String defaultInstanceLevelAssocTypeUri() { 297 if (typeUri.equals("dm4.core.aggregation_def")) { 298 return "dm4.core.aggregation"; 299 } else if (typeUri.equals("dm4.core.composition_def")) { 300 return "dm4.core.composition"; 301 } else { 302 throw new RuntimeException("Unexpected association type URI: \"" + typeUri + "\""); 303 } 304 } 305}