1 /** 2 * Copyright 2005-2015 The Kuali Foundation 3 * 4 * Licensed under the Educational Community License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.opensource.org/licenses/ecl2.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.kuali.rice.krad.data; 17 18 import java.util.List; 19 import java.util.Map; 20 import java.util.Set; 21 22 import org.kuali.rice.krad.data.metadata.DataObjectMetadata; 23 import org.springframework.beans.BeanWrapper; 24 import org.springframework.beans.BeansException; 25 import org.springframework.dao.DataAccessException; 26 27 /** 28 * Wraps a data object and it's associated metadata. Provides additional utility methods to access portions of the data 29 * object based on the metadata that's available. 30 * 31 * <p>This interface extends the {@link BeanWrapper} interface provided by Spring which means property references can 32 * be nested and may be auto-grown depending on the setting of {@link #isAutoGrowNestedPaths()}</p> 33 * 34 * @param <T> the type of the data object instance which is wrapped by this accessor 35 */ 36 public interface DataObjectWrapper<T> extends BeanWrapper { 37 38 /** 39 * Return the type of the wrapped data object. 40 * 41 * @return the type of the wrapped data instance, or <code>null</code> if no wrapped object has been set 42 */ 43 @Override 44 Class<T> getWrappedClass(); 45 46 /** 47 * Returns the data object wrapped by this accessor. 48 * 49 * @return the data object wrapped by this accessor 50 */ 51 @Override 52 T getWrappedInstance(); 53 54 /** 55 * Returns the metadata of the data object wrapped by this accessor. 56 * 57 * @return the metadata of the data object wrapped by this accessor 58 */ 59 DataObjectMetadata getMetadata(); 60 61 /** 62 * Get the current value of the specified property, but suppresses any 63 * {@link org.springframework.beans.NullValueInNestedPathException}s that would be thrown if a null value is 64 * encountered in a nested path and just returns null instead. This method is essentially a convenience method to 65 * prevent calling code from having to wrap calls to {@link #getPropertyValue(String)} with a try-catch block to 66 * check for NullValueInNestedPathExceptions. 67 * 68 * @param propertyName the name of the property to get the value of 69 * (may be a nested path and/or an indexed/mapped property) 70 * @return the value of the property, or null if any null values were encountered in nested paths 71 * @throws org.springframework.beans.InvalidPropertyException if there is no such property or 72 * if the property isn't readable 73 * @throws org.springframework.beans.PropertyAccessException if the property was valid but the 74 * accessor method failed 75 */ 76 Object getPropertyValueNullSafe(String propertyName) throws BeansException; 77 78 /** 79 * Returns a map containing the values of the primary keys on this data object. The key of this map will be the 80 * attribute name of the primary key field on the data object and the value will the value of that primary key 81 * attribute on the data object wrapped by this accessor. If this data object has no primary key fields, an empty 82 * map will be returned. 83 * 84 * @return a map of primary key values where the key is the attribute name of the primary key field and the value 85 * is the value of that primary key field on the wrapped data object 86 */ 87 Map<String, Object> getPrimaryKeyValues(); 88 89 /** 90 * Returns the value of the primary key for the wrapped data object, or null if the wrapped object has no value for 91 * it's primary key. 92 * 93 * <p>If the primary key consists of multiple values, this method will return an instance of {@link CompoundKey}, 94 * otherwise the single primary key value will be returned. If the primary key consists of multiple values but 95 * those values are only partially populated, this method will return null.</p> 96 * 97 * @return the single primary key value for the wrapped data object, or null if it has no fully-populated primary 98 * key value 99 */ 100 Object getPrimaryKeyValue(); 101 102 /** 103 * Determines if the given data object is equal to the data object wrapped by this accessor based on primary key 104 * values only. If the given object is null, then this method will always return false. 105 * 106 * @param object the object to compare to the data object wrapped by this accessor 107 * @return true if the primary key values are equal, false otherwise 108 */ 109 boolean equalsByPrimaryKey(T object); 110 111 /** 112 * Returns whether all fields in the primary key are populated with a non-null/non-blank value. 113 */ 114 boolean areAllPrimaryKeyAttributesPopulated(); 115 116 /** 117 * Returns whether any fields in the primary key is populated with a non-null/non-blank value. 118 */ 119 boolean areAnyPrimaryKeyAttributesPopulated(); 120 121 /** 122 * Returns the list of field of the primary key which have a null or blank value. 123 */ 124 List<String> getUnpopulatedPrimaryKeyAttributeNames(); 125 126 /** 127 * Returns the value of the foreign key for the specified relationship on the wrapped data object, or null if the 128 * wrapped object has no value for the requested foreign key. 129 * 130 * <p>If the foreign key is a compound/composite and consists of multiple values, this method will return an 131 * instance of {@link CompoundKey}, otherwise the single foreign key value will be returned. If the foreign key is 132 * compound/composite but 133 * those values are only partially populated, this method will return null.</p> 134 * 135 * <p>It is common that a data object may have more than one field or set of fields that constitute a specific 136 * foreign key for the specified relationship. In such cases there would be an attribute (or attributes) which 137 * represent the foreign key as well as the related object itself. For example, consider the following 138 * scenario:</p> 139 * 140 * <pre> 141 * {@code 142 * public class One { 143 * String twoId; 144 * Two two; 145 * } 146 * } 147 * </pre> 148 * 149 * <p>In this case, {@code twoId} is an attribute that serves as a foreign key to {@code Two}, but the {@code two} 150 * attribute would contain an internal {@code twoId} attribute which is the primary key value for {@code Two} and 151 * represents the foreign key in this case.</p> 152 * 153 * <p>In cases like above, the {@code twoId} attribute on the {@code One} class would take precedence unless it 154 * contains a null value, in which case this method will attempt to extract a non-null foreign key value from the 155 * related object.</p> 156 * 157 * @param relationshipName the name of the relationship on the wrapped data object for which to determine the 158 * foreign key value 159 * @return the single foreign key value on the wrapped data object for the given relationship name, or null if it 160 * has no fully-populated foreign key value 161 * @throws IllegalArgumentException if the given relationshipName does not represent a valid relationship for this 162 * data object 163 */ 164 Object getForeignKeyValue(String relationshipName); 165 166 /** 167 * As {@link #getForeignKeyValue(String)} except only returns the value for the "attribute" foreign key value. If 168 * the wrapped data object has no attribute foreign key for the given relationship, this method will return null. 169 * 170 * @param relationshipName the name of the relationship on the wrapped data object for which to determine the 171 * foreign key value 172 * @return the single foreign key attribute value on the wrapped data object for the given relationship name, or 173 * null if it has no fully-populated foreign key attribute value 174 * @throws IllegalArgumentException if the given relationshipName does not represent a valid relationship for this 175 * data object 176 */ 177 Object getForeignKeyAttributeValue(String relationshipName); 178 179 /** 180 * Get property type for property name on object, this can be a nested property and method will 181 * recursively use the metadata to find type. 182 * @param objectType - Root object type 183 * @param propertyName - Property name 184 * @return Class of propertyName 185 */ 186 Class<?> getPropertyTypeNullSafe(Class<?> objectType, String propertyName); 187 188 /** 189 * Executes reference linking using the wrapped object as the root and the set of changed paths. 190 * 191 * <p>Executes reference linker as per the algorithm described on 192 * {@link org.kuali.rice.krad.data.util.ReferenceLinker}</p> 193 * 194 * @param changedPropertyPaths the Set of changed property paths relative to the wrapped object 195 * 196 * @see org.kuali.rice.krad.data.util.ReferenceLinker#linkChanges(Object, java.util.Set) 197 */ 198 void linkChanges(Set<String> changedPropertyPaths); 199 200 /** 201 * Links foreign key values on the wrapped data object and then recurses through all child relationships and 202 * collections which are cascaded during persistence and does the same. 203 * 204 * <p>In this context, linking of foreign key values means that on data objects that have both a field (or fields) 205 * that represent a foreign key to a relationship as well as an actual reference object for that relationship, if 206 * that foreign key field(s) is read only, then it will copy the value of the primary key(s) on the reference object 207 * to the associated foreign key field(s).</p> 208 * 209 * <p>If onlyLinkReadOnly is true, this method will only link values into foreign keys that are "read-only" 210 * according to the metadata of the data object. This is to avoid situations where the foreign key field itself is 211 * the "master" value for the key. In those situations you do not want a read-only reference object to obliterate 212 * or corrupt the master key.</p> 213 * 214 * @param onlyLinkReadOnly indicates whether or not only read-only foreign keys should be linked 215 */ 216 void linkForeignKeys(boolean onlyLinkReadOnly); 217 218 /** 219 * Links foreign keys non-recursively using the relationship with the given name on the wrapped data object. 220 * 221 * <p>If onlyLinkReadOnly is true then it will only perform linking for foreign key fields that are "read-only" 222 * according to the metadata for the data object. This is to avoid situations where the foreign key field itself is 223 * the "master" value for the key. In those situations you do not want a read-only reference object to obliterate 224 * or corrupt the master key.</p> 225 * 226 * @param relationshipName the name of the relationship on the wrapped data object for which to link foreign keys, 227 * must not be a nested path 228 * @param onlyLinkReadOnly indicates whether or not only read-only foreign keys should be linked 229 */ 230 void linkForeignKeys(String relationshipName, boolean onlyLinkReadOnly); 231 232 /** 233 * Fetches and populates the value for the relationship with the given name on the wrapped data object. 234 * 235 * <p>This is done by identifying the current foreign key attribute value for the relationship using the algorithm 236 * described on {@link #getForeignKeyAttributeValue(String)} and then loading the related object using that foreign 237 * key value, updating the relationship value on the wrapped data object afterward.</p> 238 * 239 * <p>If the foreign key value is null or the loading of the related object using the foreign key returns a null 240 * value, this method will set the relationship value to null.</p> 241 * 242 * <p>This method is equivalent to invoking {@link #fetchRelationship(String, boolean, boolean)} passing "true" for 243 * both {@code useForeignKeyAttribute} and {@code nullifyDanglingRelationship}.</p> 244 * 245 * @param relationshipName the name of the relationship on the wrapped data object to refresh 246 * 247 * @throws IllegalArgumentException if the given relationshipName does not represent a valid relationship for this 248 * data object 249 * @throws DataAccessException if there is a data access problem when attempting to refresh the relationship 250 */ 251 void fetchRelationship(String relationshipName); 252 253 /** 254 * Fetches and populates the value for the relationship with the given name on the wrapped object. 255 * 256 * <p>The value of {@code useForeignKeyAttribute} will be used to determine whether the foreign key attribute is 257 * used to fetch the relationship (if this parameter is "true"), or whether the primary key on the related object 258 * itself will be used (if it is false). In the case that the primary key is used, once the relationship has been 259 * fetched, the foreign key field value (if one exists) will also be updated to remain in sync with the 260 * reference object.</p> 261 * 262 * <p>If no related object is found when attempting to fetch by the key values, then the behavior of this method 263 * will depend upon the value passed for {@code nullifyDanglingRelationship}. If false, this method will not modify 264 * the wrapped object. If true, then the related object will be set to null.</p> 265 * 266 * @param relationshipName the name of the relationship on the wrapped data object to refresh 267 * @param useForeignKeyAttribute if true, use the foreign key attribute to fetch the relationship, otherwise use the 268 * primary key value on the related object 269 * @param nullifyDanglingRelationship if true and no relationship value is found for the given key then set the 270 * related object to null, otherwise leave the existing object state alone 271 * 272 * @throws IllegalArgumentException if the given relationshipName does not represent a valid relationship for this 273 * data object 274 * @throws DataAccessException if there is a data access problem when attempting to refresh the relationship 275 */ 276 void fetchRelationship(String relationshipName, boolean useForeignKeyAttribute, boolean nullifyDanglingRelationship); 277 278 /** 279 * Fetches and populates referenced objects within the wrapped object. See {@link #fetchRelationship(String)} for 280 * details on how the relationships will be fetched. 281 * 282 * By default, this method will go through and instantiate all lazy-loaded, non-persisted-with-parent reference 283 * objects and collections on the master object. See {@link MaterializeOption} for the available options. 284 * 285 * This method does <b>not</b> recurse into child objects. For that, use the 286 * {@link #materializeReferencedObjectsToDepth(int, MaterializeOption...)} method. 287 * 288 * @see #fetchRelationship(String) 289 * @see MaterializeOption 290 * 291 * @param options 292 * An optional list of {@link MaterializeOption} objects which may adjust the behavior of this method. 293 * 294 * @throws DataAccessException 295 * if there is a data access problem when attempting to refresh the relationship 296 */ 297 void materializeReferencedObjects(MaterializeOption... options); 298 299 /** 300 * Fetches and populates referenced objects within the wrapped object. See {@link #fetchRelationship(String)} for 301 * details on how the relationships will be fetched. 302 * 303 * By default, this method will go through and instantiate all lazy-loaded, non-persisted-with-parent reference 304 * objects and collections on the master object. See {@link MaterializeOption} for the available options. 305 * 306 * @see #fetchRelationship(String) 307 * @see MaterializeOption 308 * 309 * @param maxDepth 310 * The number of levels of child objects to refresh. A value of 1 will only materialize the child object 311 * on the wrapped object. 312 * @param options 313 * An optional list of {@link MaterializeOption} objects which may adjust the behavior of this method. 314 * 315 * @throws DataAccessException 316 * if there is a data access problem when attempting to refresh the relationship 317 */ 318 void materializeReferencedObjectsToDepth(int maxDepth, MaterializeOption... options); 319 }