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}