001package systems.dmx.core.impl; 002 003import systems.dmx.core.model.AssociationDefinitionModel; 004import systems.dmx.core.model.ChildTopicsModel; 005import systems.dmx.core.model.DMXObjectModel; 006import systems.dmx.core.model.TopicModel; 007import systems.dmx.core.model.TopicRoleModel; 008import systems.dmx.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("dmx.core.parent_type")).getTopicUri(); 074 } 075 076 @Override 077 public String getChildTypeUri() { 078 return ((TopicRoleModel) getRoleModel("dmx.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 getViewConfig() { 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 setViewConfig(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("parentCardinalityUri", parentCardinalityUri) 120 .put("childCardinalityUri", childCardinalityUri) 121 .put("viewConfigTopics", viewConfig.toJSONArray()); 122 } catch (Exception e) { 123 throw new RuntimeException("Serialization failed", e); 124 } 125 } 126 127 // ----------------------------------------------------------------------------------------- Package Private Methods 128 129 130 131 @Override 132 String className() { 133 return "association definition"; 134 } 135 136 @Override 137 AssociationDefinitionImpl instantiate() { 138 return new AssociationDefinitionImpl(this, pl); 139 } 140 141 @Override 142 AssociationDefinitionModelImpl createModelWithChildTopics(ChildTopicsModel childTopics) { 143 return mf.newAssociationDefinitionModel(typeUri, childTopics); 144 } 145 146 147 148 // === Core Internal Hooks === 149 150 /** 151 * 2 assoc def specific tasks must be performed once the underlying association is updated: 152 * - Update the assoc def's cardinality (in type cache + DB). Cardinality is technically not part of the type 153 * model. So, it is not handled by the generic (model-driven) object update procedure. 154 * - Possibly rehash the assoc def in type cache. Rehashing is required if the custom assoc type has changed. 155 * <p> 156 * Pre condition: these 3 assoc def parts are already up-to-date through the generic (model-driven) object update 157 * procedure: 158 * - Assoc Def type (type URI). 159 * - Custom assoc type (child topics). 160 * - "Include in Label" flag (child topics). 161 * <p> 162 * Called when update() is called on an AssociationDefinitionModel object. This is in 2 cases: 163 * - Edit a type interactively (a type topic is selected). 164 * - Programmatically call getChildTopics().set() on an AssociationDefinitionModel object, e.g. from a migration. 165 * <p> 166 * <i>Not</i> called when an association which also acts as an assoc def is edited interactively (an association is 167 * selected). In this case: 168 * - Cardinality doesn't need to be updated as Cardinality can't be edited interactively through an association. 169 * - Rehashing is already performed in TypeModelImpl#_updateAssocDef (called from AssociationModelImpl#postUpdate) 170 * 171 * @param updateModel 172 * the update data/instructions. 173 * Note: on post-update time updateModel and this (assoc def) model may differ at least because 174 * a) updateModel might contain only certain assoc def parts; this is called a "partial update" 175 * b) updateModel might contain refs and deletion-refs; this model never contains refs 176 */ 177 @Override 178 void postUpdate(DMXObjectModel updateModel, DMXObjectModel oldObject) { 179 super.postUpdate(updateModel, oldObject); 180 // 181 updateCardinality((AssociationDefinitionModel) updateModel); 182 } 183 184 185 186 // === Update (memory + DB) === 187 188 void updateParentCardinalityUri(String parentCardinalityUri) { 189 setParentCardinalityUri(parentCardinalityUri); // update memory 190 pl.typeStorage.storeParentCardinalityUri(id, parentCardinalityUri); // update DB 191 } 192 193 void updateChildCardinalityUri(String childCardinalityUri) { 194 setChildCardinalityUri(childCardinalityUri); // update memory 195 pl.typeStorage.storeChildCardinalityUri(id, childCardinalityUri); // update DB 196 } 197 198 199 200 // === Identity Configuration === 201 202 final boolean isIdentityAttr() { 203 TopicModel isIdentityAttr = getChildTopicsModel().getTopicOrNull("dmx.core.identity_attr"); 204 if (isIdentityAttr == null) { 205 // ### TODO: should a isIdentityAttr topic always exist? 206 // throw new RuntimeException("Assoc def \"" + getAssocDefUri() + "\" has no \"Identity Attribute\" topic"); 207 return false; 208 } 209 return isIdentityAttr.getSimpleValue().booleanValue(); 210 } 211 212 213 214 // === Label Configuration === 215 216 final boolean includeInLabel() { 217 TopicModel includeInLabel = getChildTopicsModel().getTopicOrNull("dmx.core.include_in_label"); 218 if (includeInLabel == null) { 219 // ### TODO: should a includeInLabel topic always exist? 220 // throw new RuntimeException("Assoc def \"" + getAssocDefUri() + "\" has no \"Include in Label\" topic"); 221 return false; 222 } 223 return includeInLabel.getSimpleValue().booleanValue(); 224 } 225 226 227 228 // === Access Control === 229 230 boolean isReadable() { 231 try { 232 // 1) check assoc def 233 if (!pl.hasReadAccess(this)) { 234 logger.info("### Assoc def \"" + getAssocDefUri() + "\" not READable"); 235 return false; 236 } 237 // Note: there is no need to explicitly check READability for the assoc def's child type. 238 // If the child type is not READable the entire assoc def is not READable as well. 239 // 240 // 2) check custom assoc type, if set 241 TopicModelImpl assocType = getCustomAssocType(); 242 if (assocType != null && !pl.hasReadAccess(assocType)) { 243 logger.info("### Assoc def \"" + getAssocDefUri() + "\" not READable (custom assoc type not READable)"); 244 return false; 245 } 246 // 247 return true; 248 } catch (Exception e) { 249 throw new RuntimeException("Checking assoc def READability failed (" + this + ")", e); 250 } 251 } 252 253 // ------------------------------------------------------------------------------------------------- Private Methods 254 255 256 257 // === Update === 258 259 private void updateCardinality(AssociationDefinitionModel newAssocDef) { 260 updateParentCardinality(newAssocDef.getParentCardinalityUri()); 261 updateChildCardinality(newAssocDef.getChildCardinalityUri()); 262 } 263 264 // --- 265 266 private void updateParentCardinality(String newParentCardinalityUri) { 267 // abort if no update is requested 268 if (newParentCardinalityUri == null) { 269 return; 270 } 271 // 272 String parentCardinalityUri = getParentCardinalityUri(); 273 if (!parentCardinalityUri.equals(newParentCardinalityUri)) { 274 logger.info("### Changing parent cardinality URI: \"" + parentCardinalityUri + "\" -> \"" + 275 newParentCardinalityUri + "\""); 276 updateParentCardinalityUri(newParentCardinalityUri); 277 } 278 } 279 280 private void updateChildCardinality(String newChildCardinalityUri) { 281 // abort if no update is requested 282 if (newChildCardinalityUri == null) { 283 return; 284 } 285 // 286 String childCardinalityUri = getChildCardinalityUri(); 287 if (!childCardinalityUri.equals(newChildCardinalityUri)) { 288 logger.info("### Changing child cardinality URI: \"" + childCardinalityUri + "\" -> \"" + 289 newChildCardinalityUri + "\""); 290 updateChildCardinalityUri(newChildCardinalityUri); 291 } 292 } 293 294 295 296 // === 297 298 private TopicModelImpl getCustomAssocType() { 299 return getChildTopicsModel().getTopicOrNull("dmx.core.assoc_type#dmx.core.custom_assoc_type"); 300 } 301 302 private String defaultInstanceLevelAssocTypeUri() { 303 if (typeUri.equals("dmx.core.aggregation_def")) { 304 return "dmx.core.aggregation"; 305 } else if (typeUri.equals("dmx.core.composition_def")) { 306 return "dmx.core.composition"; 307 } else { 308 throw new RuntimeException("Unexpected association type URI: \"" + typeUri + "\""); 309 } 310 } 311}