View Javadoc

1   /**
2    * Copyright 2005-2013 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.provider.util;
17  
18  import java.util.Collection;
19  import java.util.HashSet;
20  import java.util.List;
21  import java.util.Set;
22  
23  import org.apache.commons.lang.ObjectUtils;
24  import org.apache.commons.lang.StringUtils;
25  import org.kuali.rice.krad.data.DataObjectService;
26  import org.kuali.rice.krad.data.DataObjectUtils;
27  import org.kuali.rice.krad.data.DataObjectWrapper;
28  import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship;
29  import org.kuali.rice.krad.data.metadata.DataObjectCollection;
30  import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
31  import org.kuali.rice.krad.data.metadata.DataObjectRelationship;
32  import org.kuali.rice.krad.data.metadata.MetadataRepository;
33  
34  /**
35   * Links parent-child object references
36   */
37  public class ReferenceLinker {
38  	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ReferenceLinker.class);
39  
40      private DataObjectService dataObjectService;
41  
42      public ReferenceLinker(DataObjectService dataObjectService) {
43          this.dataObjectService = dataObjectService;
44      }
45  
46      protected DataObjectService getDataObjectService() {
47          return dataObjectService;
48      }
49  
50      protected MetadataRepository getMetadataRepository() {
51          return getDataObjectService().getMetadataRepository();
52      }
53  
54      /**
55       * For each reference object to the parent persistableObject, sets the key
56       * values for that object. First, if the reference object already has a
57       * value for the key, the value is left unchanged. Otherwise, for
58       * non-anonymous keys, the value is taken from the parent object. For
59       * anonymous keys, all other persistableObjects are checked until a value
60       * for the key is found.
61       */
62      public void linkObjects(Object persistableObject) {
63          linkObjectsWithCircularReferenceCheck(persistableObject, new HashSet<Object>());
64      }
65  
66      protected void linkObjectsWithCircularReferenceCheck(Object persistableObject, Set<Object> referenceSet) {
67          if (referenceSet.contains(persistableObject) || DataObjectUtils.isNull(persistableObject)) {
68              return;
69          }
70  		if (LOG.isDebugEnabled()) {
71  			LOG.debug("Attempting to link reference objects on " + persistableObject);
72  		}
73          referenceSet.add(persistableObject);
74          DataObjectMetadata metadata = getMetadataRepository().getMetadata(persistableObject.getClass());
75  
76          if (metadata == null) {
77              LOG.warn("Unable to find metadata for "
78                      + persistableObject.getClass()
79                      + " when linking references, skipping");
80              return;
81          }
82  
83  		linkRelationships(metadata, persistableObject, referenceSet);
84  		linkCollections(metadata, persistableObject, referenceSet);
85  	}
86  
87  	protected void linkRelationships(DataObjectMetadata metadata, Object persistableObject, Set<Object> referenceSet) {
88          // iterate through all object references for the persistableObject
89          List<DataObjectRelationship> objectReferences = metadata.getRelationships();
90  		if (LOG.isDebugEnabled()) {
91  			LOG.debug("Obtained relationships for linking: " + objectReferences);
92  		}
93  		DataObjectWrapper<?> parentWrap = getDataObjectService().wrap(persistableObject);
94          for (DataObjectRelationship referenceDescriptor : objectReferences) {
95              // get the actual reference object
96              String fieldName = referenceDescriptor.getName();
97  			Object childObject = parentWrap.getPropertyValue(fieldName);
98  			boolean updatableRelationship = referenceDescriptor.isSavedWithParent();
99  			if (childObject == null) {
100 				if (LOG.isDebugEnabled()) {
101 					LOG.debug("Referenced object for field " + fieldName + " is null, skipping");
102 				}
103 				continue;
104 			}
105 
106             // recursively link object
107 			linkObjectsWithCircularReferenceCheck(childObject, referenceSet);
108 
109             // iterate through the keys for the reference object and set
110             // value
111 			List<DataObjectAttributeRelationship> refAttrs = referenceDescriptor.getAttributeRelationships();
112             DataObjectMetadata refCld = getMetadataRepository().getMetadata(referenceDescriptor.getRelatedType());
113             if (refCld == null) {
114                 LOG.warn("No metadata found in repository for referenced object: " + referenceDescriptor);
115                 continue;
116             }
117 			// List<String> refPkNames = refCld.getPrimaryKeyAttributeNames();
118 
119 			if (LOG.isDebugEnabled()) {
120 				LOG.debug("Linking Referenced fields with parent's FK fields:" + "\n***Refs: " + refAttrs);
121 			}
122 
123 			// Two cases: Updatable Reference (owned child object) or non-updatable (reference data)
124 			// In the former case, we always want to push our keys down into the child, since that
125 			// is what will maintain the relationship.
126 			// In the latter: We assume that the parent object's key fields are correct, only
127 			// setting them from the embedded object *IF* they are null.
128 			// (Since we can't effectively tell which one is the master.)
129 
130 			// Go through all the attribute relationships to copy the key fields as appropriate
131 			DataObjectWrapper<?> childWrap = getDataObjectService().wrap(childObject);
132 			if (updatableRelationship) {
133 				linkUpdatableChild(parentWrap, childWrap, referenceDescriptor.getName(), refAttrs);
134 			} else { // non-updatable (reference-only) relationship
135 				linkNonUpdatableChild(parentWrap, childWrap, referenceDescriptor.getName(), refAttrs);
136 			}
137 		}
138 	}
139 
140 	/**
141 	 * Attempt to ensure that, for an updatable reference object that the FK fields and the reference object remain
142 	 * consistent.
143 	 * 
144 	 * <ol>
145 	 * <li>If the referenced key on the child object is not set and the matching key on the parent is set, set the key
146 	 * field on the child object.</li>
147 	 * <li></li>
148 	 * </ol>
149 	 * 
150 	 * @param parentWrap
151 	 * @param childWrap
152 	 * @param refAttrs
153 	 */
154 	protected void linkUpdatableChild(DataObjectWrapper<?> parentWrap, DataObjectWrapper<?> childWrap,
155 			String childObjectPropertyName, List<DataObjectAttributeRelationship> refAttrs) {
156 		DataObjectMetadata referenceMetadata = childWrap.getMetadata();
157 		List<String> childPkAttributes = referenceMetadata.getPrimaryKeyAttributeNames();
158 		List<String> parentPkAttributes = parentWrap.getMetadata().getPrimaryKeyAttributeNames();
159 		for (DataObjectAttributeRelationship attrRel : refAttrs) {
160 			Object parentPropertyValue = parentWrap.getPropertyValue(attrRel.getParentAttributeName());
161 			Object childPropertyValue = childWrap.getPropertyValueNullSafe(attrRel.getChildAttributeName());
162 
163 			// if fk is set in main object, take value from there
164 			if (parentPropertyValue != null && StringUtils.isNotBlank(parentPropertyValue.toString())) {
165 				// if the child's value is a PK field then we don't want to set it
166 				// *unless* it's null, which we assume is an invalid situation
167 				// and indicates that it has not been set yet
168 				if (childPkAttributes.contains(attrRel.getChildAttributeName()) && childPropertyValue != null
169 						&& StringUtils.isNotBlank(childPropertyValue.toString())) {
170 					if (LOG.isDebugEnabled()) {
171 						LOG.debug("Relationship is to PK value on updatable child object - it may not be changed.  Skipping: "
172 								+ childWrap.getWrappedClass().getName() + "." + attrRel.getChildAttributeName());
173 					}
174 					continue;
175 				}
176 				if (LOG.isDebugEnabled()) {
177 					LOG.debug("Parent Object Of Updateable Child has FK value set (" + attrRel.getParentAttributeName()
178 							+ "="
179 							+ parentPropertyValue + "): using that");
180 				}
181 				childWrap.setPropertyValue(attrRel.getChildAttributeName(), parentPropertyValue);
182 			} else {
183 				// The key field on the parent is blank, and so can not link to a child object
184 				// Blank out the child reference object.
185 				// parentWrap.setPropertyValue(childObjectPropertyName, null);
186 
187 				// The FK field on the parent is blank,
188 				// but the child has key values - so set the parent so they link properly
189 				if (childPropertyValue != null && StringUtils.isNotBlank(childPropertyValue.toString())) {
190 					if (parentPkAttributes.contains(attrRel.getParentAttributeName())) {
191 						if (LOG.isDebugEnabled()) {
192 							LOG.debug("Relationship is to PK value on parent object - it may not be changed.  Skipping: "
193 									+ parentWrap.getWrappedClass().getName() + "." + attrRel.getParentAttributeName());
194 						}
195 						continue;
196 					}
197 					if (LOG.isDebugEnabled()) {
198 						LOG.debug("Updatable Child Object has FK value set (" + attrRel.getChildAttributeName() + "="
199 								+ childPropertyValue + "): using that");
200 					}
201 					parentWrap.setPropertyValue(attrRel.getParentAttributeName(), childPropertyValue);
202 				}
203 			}
204 		}
205 	}
206 
207 	protected void linkNonUpdatableChild(DataObjectWrapper<?> parentWrap, DataObjectWrapper<?> childWrap,
208 			String childObjectPropertyName, List<DataObjectAttributeRelationship> refAttrs) {
209 		for (DataObjectAttributeRelationship attrRel : refAttrs) {
210 			Object parentPropertyValue = parentWrap.getPropertyValueNullSafe(attrRel.getParentAttributeName());
211 			Object childPropertyValue = childWrap.getPropertyValueNullSafe(attrRel.getChildAttributeName());
212 			// if (parentPropertyValue != null && StringUtils.isNotBlank(parentPropertyValue.toString())) {
213 			// // Skip this property, it has already been set on the parent object
214 			// continue;
215 			// }
216 			if (ObjectUtils.notEqual(parentPropertyValue, childPropertyValue)) {
217 				parentWrap.setPropertyValue(childObjectPropertyName, null);
218 				break;
219 				// we have nothing else to do - one of the parent properties
220 				// was blank (or mismatched) so we can quit
221 			}
222 
223 			// The key field on the parent is blank, and so can not link to a child object
224 			// Blank out the child reference object.
225 
226 
227 			// Object childPropertyValue = childWrap.getPropertyValueNullSafe(attrRel.getChildAttributeName());
228 			// // don't bother setting parent if it's not set itself
229 			// if (childPropertyValue == null || StringUtils.isBlank(childPropertyValue.toString())) {
230 			// continue;
231 			// }
232 			// if (LOG.isDebugEnabled()) {
233 			// LOG.debug("Non-Updatable Child Object has FK value set (" + attrRel.getChildAttributeName() + "="
234 			// + childPropertyValue + "): using that");
235 			// }
236 			// parentWrap.setPropertyValue(attrRel.getParentAttributeName(), childPropertyValue);
237 		}
238 	}
239 
240 	protected void linkCollections(DataObjectMetadata metadata, Object persistableObject, Set<Object> referenceSet) {
241 		List<DataObjectCollection> collections = metadata.getCollections();
242 		if (LOG.isDebugEnabled()) {
243 			LOG.debug("Obtained collections for linking: " + collections);
244         }
245 
246 		for (DataObjectCollection collectionMetadata : collections) {
247 			// We only process collections if they are being saved with the parent
248 			if (!collectionMetadata.isSavedWithParent()) {
249 				continue;
250 			}
251 			// get the actual reference object
252 			String fieldName = collectionMetadata.getName();
253 			DataObjectWrapper<?> parentObjectWrapper = getDataObjectService().wrap(persistableObject);
254 			Collection<?> collection = (Collection<?>) parentObjectWrapper.getPropertyValue(fieldName);
255 			if (collection == null) {
256 				if (LOG.isDebugEnabled()) {
257 					LOG.debug("Referenced collection for field " + fieldName + " is null, skipping");
258 				}
259 				continue;
260 			} else if (referenceSet.contains(collection)) {
261 				if (LOG.isDebugEnabled()) {
262 					LOG.debug("We've previously linked the object assigned to " + fieldName + ", skipping");
263 				}
264 				continue;
265 			}
266 			List<DataObjectAttributeRelationship> collectionAttributeRelationships = collectionMetadata
267 					.getAttributeRelationships();
268 
269 			// Need to iterate through the collection, setting FK values as needed and telling each child object to link
270 			// itself
271 			for (Object collectionItem : collection) {
272 				// recursively link object
273 				linkObjectsWithCircularReferenceCheck(collectionItem, referenceSet);
274 
275 				DataObjectWrapper<Object> collItemWrapper = getDataObjectService().wrap(collectionItem);
276 				// Now - go through the keys relating the parent object to each child and set them so that they are
277 				// linked properly
278 				// This will also reset them to the parent's values in case they were changed.
279 				// If this updates the PK fields of the collection objects, then the user is doing
280 				// something they shouldn't (swapping Collection items between parent data objects)
281 				// And it will blow up with an JPA exception anyway.
282 				for (DataObjectAttributeRelationship rel : collectionAttributeRelationships) {
283                     if(rel.getChildAttributeName() != null && rel.getParentAttributeName() != null){
284 					    collItemWrapper.setPropertyValue(rel.getChildAttributeName(),
285 							parentObjectWrapper.getPropertyValueNullSafe(rel.getParentAttributeName()));
286                     }
287 				}
288 			}
289 		}
290     }
291 
292 }