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 }