001package de.deepamehta.core.impl; 002 003import de.deepamehta.core.AssociationDefinition; 004import de.deepamehta.core.model.AssociationDefinitionModel; 005import de.deepamehta.core.model.AssociationModel; 006import de.deepamehta.core.model.ChildTopicsModel; 007import de.deepamehta.core.model.DeepaMehtaObjectModel; 008import de.deepamehta.core.model.RelatedTopicModel; 009import de.deepamehta.core.model.TopicDeletionModel; 010import de.deepamehta.core.model.TopicModel; 011import de.deepamehta.core.model.TopicRoleModel; 012import de.deepamehta.core.model.TypeModel; 013import de.deepamehta.core.model.ViewConfigurationModel; 014 015import org.codehaus.jettison.json.JSONException; 016import org.codehaus.jettison.json.JSONObject; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.List; 021import java.util.logging.Logger; 022 023 024 025/** 026 * Definition of an association between 2 topic types -- part of DeepaMehta's type system, 027 * like an association in a class diagram. Used to represent both, aggregations and compositions. 028 * ### FIXDOC: also assoc types have assoc defs 029 * 030 * @author <a href="mailto:jri@deepamehta.de">Jörg Richter</a> 031 */ 032class AssociationDefinitionModelImpl extends AssociationModelImpl implements AssociationDefinitionModel { 033 034 // ---------------------------------------------------------------------------------------------- Instance Variables 035 036 private String parentCardinalityUri; 037 private String childCardinalityUri; 038 039 private ViewConfigurationModelImpl viewConfig; // is never null 040 041 private Logger logger = Logger.getLogger(getClass().getName()); 042 043 // ---------------------------------------------------------------------------------------------------- Constructors 044 045 /** 046 * ### TODO: add include-in-label parameter? 047 * 048 * @param customAssocTypeUri if null no custom association type will be set. 049 */ 050 AssociationDefinitionModelImpl(AssociationModelImpl assoc, String parentCardinalityUri, String childCardinalityUri, 051 ViewConfigurationModelImpl viewConfig) { 052 super(assoc); 053 this.parentCardinalityUri = parentCardinalityUri; 054 this.childCardinalityUri = childCardinalityUri; 055 this.viewConfig = viewConfig != null ? viewConfig : mf.newViewConfigurationModel(); 056 // ### TODO: why null check? Compare to TypeModelImpl constructor 057 } 058 059 // -------------------------------------------------------------------------------------------------- Public Methods 060 061 @Override 062 public String getAssocDefUri() { 063 String customAssocTypeUri = getCustomAssocTypeUriOrNull(); 064 return getChildTypeUri() + (customAssocTypeUri !=null ? "#" + customAssocTypeUri : ""); 065 } 066 067 @Override 068 public String getCustomAssocTypeUri() { 069 TopicModel customAssocType = getCustomAssocType(); 070 return customAssocType != null ? customAssocType.getUri() : null; 071 } 072 073 /** 074 * The type to be used to create an association instance based on this association definition. 075 */ 076 @Override 077 public String getInstanceLevelAssocTypeUri() { 078 String customAssocTypeUri = getCustomAssocTypeUri(); 079 return customAssocTypeUri !=null ? customAssocTypeUri : defaultInstanceLevelAssocTypeUri(); 080 } 081 082 @Override 083 public String getParentTypeUri() { 084 return ((TopicRoleModel) getRoleModel("dm4.core.parent_type")).getTopicUri(); 085 } 086 087 @Override 088 public String getChildTypeUri() { 089 return ((TopicRoleModel) getRoleModel("dm4.core.child_type")).getTopicUri(); 090 } 091 092 @Override 093 public String getParentCardinalityUri() { 094 return parentCardinalityUri; 095 } 096 097 @Override 098 public String getChildCardinalityUri() { 099 return childCardinalityUri; 100 } 101 102 @Override 103 public ViewConfigurationModelImpl getViewConfigModel() { 104 return viewConfig; 105 } 106 107 // --- 108 109 @Override 110 public void setParentCardinalityUri(String parentCardinalityUri) { 111 this.parentCardinalityUri = parentCardinalityUri; 112 } 113 114 @Override 115 public void setChildCardinalityUri(String childCardinalityUri) { 116 this.childCardinalityUri = childCardinalityUri; 117 } 118 119 @Override 120 public void setViewConfigModel(ViewConfigurationModel viewConfig) { 121 this.viewConfig = (ViewConfigurationModelImpl) viewConfig; 122 } 123 124 // --- 125 126 @Override 127 public JSONObject toJSON() { 128 try { 129 return super.toJSON() 130 .put("parent_cardinality_uri", parentCardinalityUri) 131 .put("child_cardinality_uri", childCardinalityUri) 132 .put("view_config_topics", viewConfig.toJSONArray()); 133 } catch (Exception e) { 134 throw new RuntimeException("Serialization failed (" + this + ")", e); 135 } 136 } 137 138 // --- 139 140 @Override 141 public String toString() { 142 return "\n association definition (" + super.toString() + 143 ",\n parent cardinality=\"" + parentCardinalityUri + 144 "\",\n child cardinality=\"" + childCardinalityUri + 145 "\",\n " + viewConfig + ")\n"; 146 } 147 148 // ----------------------------------------------------------------------------------------- Package Private Methods 149 150 151 152 @Override 153 String className() { 154 return "association definition"; 155 } 156 157 @Override 158 AssociationDefinitionImpl instantiate() { 159 return new AssociationDefinitionImpl(this, pl); 160 } 161 162 163 164 // === Core Internal Hooks === 165 166 @Override 167 void postUpdate(DeepaMehtaObjectModel newModel, DeepaMehtaObjectModel oldModel) { 168 super.postUpdate(newModel, oldModel); 169 // 170 updateCardinality((AssociationDefinitionModel) newModel); 171 // 172 // rehash 173 boolean changeCustomAssocType = customAssocTypeChange((AssociationDefinitionModel) newModel, 174 (AssociationDefinitionModel) oldModel); 175 if (changeCustomAssocType) { 176 logger.info("### Changed custom association type URI from \"" + 177 ((AssociationDefinitionModelImpl) oldModel).getCustomAssocTypeUri() + "\" -> \"" + 178 ((AssociationDefinitionModelImpl) newModel).getCustomAssocTypeUriOrNull() + "\""); 179 getParentType().rehashAssocDef(newModel.getId()); 180 } 181 } 182 183 184 185 // === Update (memory + DB) === 186 187 void updateParentCardinalityUri(String parentCardinalityUri) { 188 setParentCardinalityUri(parentCardinalityUri); // update memory 189 pl.typeStorage.storeParentCardinalityUri(id, parentCardinalityUri); // update DB 190 } 191 192 void updateChildCardinalityUri(String childCardinalityUri) { 193 setChildCardinalityUri(childCardinalityUri); // update memory 194 pl.typeStorage.storeChildCardinalityUri(id, childCardinalityUri); // update DB 195 } 196 197 198 199 // === Access Control === 200 201 boolean isReadable() { 202 try { 203 // 1) check assoc def 204 if (!pl.hasReadAccess(this)) { 205 logger.info("### Assoc def \"" + getAssocDefUri() + "\" not READable"); 206 return false; 207 } 208 // Note: there is no need to explicitly check READability for the assoc def's child type. 209 // If the child type is not READable the entire assoc def is not READable as well. 210 // 211 // 2) check custom assoc type, if set 212 TopicModelImpl assocType = getCustomAssocType(); 213 if (assocType != null && !pl.hasReadAccess(assocType)) { 214 logger.info("### Assoc def \"" + getAssocDefUri() + "\" not READable (custom assoc type not READable)"); 215 return false; 216 } 217 // 218 return true; 219 } catch (Exception e) { 220 throw new RuntimeException("Checking assoc def READability failed (" + this + ")", e); 221 } 222 } 223 224 225 226 // === 227 228 /** 229 * ### TODO: make private 230 * 231 * @return <code>null</code> if this assoc def's custom assoc type model is null or represents a deletion ref. 232 * Otherwise returns the custom assoc type URI. 233 */ 234 String getCustomAssocTypeUriOrNull() { 235 return getCustomAssocType() instanceof TopicDeletionModel ? null : getCustomAssocTypeUri(); 236 } 237 238 // --- 239 240 TypeModelImpl getParentType() { 241 return pl.typeStorage.getType(getParentTypeUri()); 242 } 243 244 // ------------------------------------------------------------------------------------------------- Private Methods 245 246 247 248 // === Update === 249 250 private void updateCardinality(AssociationDefinitionModel newModel) { 251 updateParentCardinality(newModel.getParentCardinalityUri()); 252 updateChildCardinality(newModel.getChildCardinalityUri()); 253 } 254 255 // --- 256 257 private void updateParentCardinality(String newParentCardinalityUri) { 258 // abort if no update is requested 259 if (newParentCardinalityUri == null) { 260 return; 261 } 262 // 263 String parentCardinalityUri = getParentCardinalityUri(); 264 if (!parentCardinalityUri.equals(newParentCardinalityUri)) { 265 logger.info("### Changing parent cardinality URI from \"" + parentCardinalityUri + "\" -> \"" + 266 newParentCardinalityUri + "\""); 267 updateParentCardinalityUri(newParentCardinalityUri); 268 } 269 } 270 271 private void updateChildCardinality(String newChildCardinalityUri) { 272 // abort if no update is requested 273 if (newChildCardinalityUri == null) { 274 return; 275 } 276 // 277 String childCardinalityUri = getChildCardinalityUri(); 278 if (!childCardinalityUri.equals(newChildCardinalityUri)) { 279 logger.info("### Changing child cardinality URI from \"" + childCardinalityUri + "\" -> \"" + 280 newChildCardinalityUri + "\""); 281 updateChildCardinalityUri(newChildCardinalityUri); 282 } 283 } 284 285 286 287 // ==== 288 289 private boolean customAssocTypeChange(AssociationDefinitionModel newModel, AssociationDefinitionModel oldModel) { 290 String oldUri = oldModel.getCustomAssocTypeUri(); // null if no assoc type is set 291 String newUri = ((AssociationDefinitionModelImpl) newModel).getCustomAssocTypeUriOrNull(); // null if del ref 292 if (newUri != null) { 293 // new value is neither a deletion ref nor null, compare it to old value (which may be null) 294 return !newUri.equals(oldUri); 295 } else { 296 // compare old value to null if new value is a deletion ref or null 297 // ### FIXME: must differentiate "no change requested" (= null) and "remove current assignment" (= del ref)? 298 return oldUri != null; 299 } 300 } 301 302 private TopicModelImpl getCustomAssocType() { 303 TopicModelImpl customAssocType = getChildTopicsModel().getTopicOrNull( 304 "dm4.core.assoc_type#dm4.core.custom_assoc_type"); 305 // Note: we can't do this sanity check because a type model would not even deserialize. 306 // The type model JSON constructor repeatedly calls addAssocDef() which hashes by assoc def URI. ### still true? 307 /* if (customAssocType instanceof TopicDeletionModel) { 308 throw new RuntimeException("Tried to get an assoc def's custom assoc type when it is a deletion " + 309 "reference (" + this + ")"); 310 } */ 311 return customAssocType; 312 } 313 314 private String defaultInstanceLevelAssocTypeUri() { 315 if (typeUri.equals("dm4.core.aggregation_def")) { 316 return "dm4.core.aggregation"; 317 } else if (typeUri.equals("dm4.core.composition_def")) { 318 return "dm4.core.composition"; 319 } else { 320 throw new RuntimeException("Unexpected association type URI: \"" + typeUri + "\""); 321 } 322 } 323}