View Javadoc
1   /**
2    * Copyright 2005-2016 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 }