001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.data; 017 018import java.util.List; 019import java.util.Map; 020import java.util.Set; 021 022import org.kuali.rice.krad.data.metadata.DataObjectMetadata; 023import org.springframework.beans.BeanWrapper; 024import org.springframework.beans.BeansException; 025import org.springframework.dao.DataAccessException; 026 027/** 028 * Wraps a data object and it's associated metadata. Provides additional utility methods to access portions of the data 029 * object based on the metadata that's available. 030 * 031 * <p>This interface extends the {@link BeanWrapper} interface provided by Spring which means property references can 032 * be nested and may be auto-grown depending on the setting of {@link #isAutoGrowNestedPaths()}</p> 033 * 034 * @param <T> the type of the data object instance which is wrapped by this accessor 035 */ 036public interface DataObjectWrapper<T> extends BeanWrapper { 037 038 /** 039 * Return the type of the wrapped data object. 040 * 041 * @return the type of the wrapped data instance, or <code>null</code> if no wrapped object has been set 042 */ 043 @Override 044 Class<T> getWrappedClass(); 045 046 /** 047 * Returns the data object wrapped by this accessor. 048 * 049 * @return the data object wrapped by this accessor 050 */ 051 @Override 052 T getWrappedInstance(); 053 054 /** 055 * Returns the metadata of the data object wrapped by this accessor. 056 * 057 * @return the metadata of the data object wrapped by this accessor 058 */ 059 DataObjectMetadata getMetadata(); 060 061 /** 062 * Get the current value of the specified property, but suppresses any 063 * {@link org.springframework.beans.NullValueInNestedPathException}s that would be thrown if a null value is 064 * encountered in a nested path and just returns null instead. This method is essentially a convenience method to 065 * prevent calling code from having to wrap calls to {@link #getPropertyValue(String)} with a try-catch block to 066 * check for NullValueInNestedPathExceptions. 067 * 068 * @param propertyName the name of the property to get the value of 069 * (may be a nested path and/or an indexed/mapped property) 070 * @return the value of the property, or null if any null values were encountered in nested paths 071 * @throws org.springframework.beans.InvalidPropertyException if there is no such property or 072 * if the property isn't readable 073 * @throws org.springframework.beans.PropertyAccessException if the property was valid but the 074 * accessor method failed 075 */ 076 Object getPropertyValueNullSafe(String propertyName) throws BeansException; 077 078 /** 079 * Returns a map containing the values of the primary keys on this data object. The key of this map will be the 080 * attribute name of the primary key field on the data object and the value will the value of that primary key 081 * attribute on the data object wrapped by this accessor. If this data object has no primary key fields, an empty 082 * map will be returned. 083 * 084 * @return a map of primary key values where the key is the attribute name of the primary key field and the value 085 * is the value of that primary key field on the wrapped data object 086 */ 087 Map<String, Object> getPrimaryKeyValues(); 088 089 /** 090 * Returns the value of the primary key for the wrapped data object, or null if the wrapped object has no value for 091 * it's primary key. 092 * 093 * <p>If the primary key consists of multiple values, this method will return an instance of {@link CompoundKey}, 094 * otherwise the single primary key value will be returned. If the primary key consists of multiple values but 095 * those values are only partially populated, this method will return null.</p> 096 * 097 * @return the single primary key value for the wrapped data object, or null if it has no fully-populated primary 098 * key value 099 */ 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}