View Javadoc

1   /**
2    * Copyright 2005-2011 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.service.impl;
17  
18  import java.beans.PropertyDescriptor;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.HashMap;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.apache.commons.beanutils.PropertyUtils;
28  import org.apache.commons.lang.StringUtils;
29  import org.kuali.rice.core.framework.persistence.jpa.metadata.EntityDescriptor;
30  import org.kuali.rice.core.framework.persistence.jpa.metadata.JoinColumnDescriptor;
31  import org.kuali.rice.core.framework.persistence.jpa.metadata.MetadataManager;
32  import org.kuali.rice.core.framework.persistence.jpa.metadata.ObjectDescriptor;
33  import org.kuali.rice.krad.bo.DataObjectRelationship;
34  import org.kuali.rice.krad.bo.PersistableBusinessObject;
35  import org.kuali.rice.krad.exception.ObjectNotABusinessObjectRuntimeException;
36  import org.kuali.rice.krad.exception.ReferenceAttributeDoesntExistException;
37  import org.kuali.rice.krad.exception.ReferenceAttributeNotAnOjbReferenceException;
38  import org.kuali.rice.krad.service.PersistenceStructureService;
39  import org.kuali.rice.krad.util.ForeignKeyFieldsPopulationState;
40  
41  public class PersistenceStructureServiceJpaImpl extends PersistenceServiceImplBase implements PersistenceStructureService {
42  
43  	/**
44  	 * 
45  	 * special case when the attributeClass passed in doesnt match the class of
46  	 * the reference-descriptor as defined in ojb-repository. Currently the only
47  	 * case of this happening is ObjectCode vs. ObjectCodeCurrent.
48  	 * 
49  	 * NOTE: This method makes no real sense and is a product of a hack
50  	 * introduced by KFS for an unknown reason. If you find yourself using this
51  	 * map stop and go do something else.
52  	 * 
53  	 * @param from
54  	 *            the class in the code
55  	 * @param to
56  	 *            the class in the repository
57  	 */
58  	public static Map<Class, Class> referenceConversionMap = new HashMap<Class, Class>();
59  
60  	/**
61  	 * @see org.kuali.rice.krad.service.PersistenceService#isPersistable(java.lang.Class)
62  	 */
63  	
64  	public boolean isPersistable(Class clazz) {
65  		boolean isPersistable = false;
66  
67  		if (MetadataManager.getEntityDescriptor(clazz) != null) {
68  			isPersistable = true;
69  		}
70  
71  		return isPersistable;
72  	}
73  
74  	/**
75  	 * @see org.kuali.rice.krad.service.PersistenceService#getPrimaryKeys(java.lang.Class)
76  	 */
77  	
78  	public List getPrimaryKeys(Class clazz) {
79  		List pkList = new ArrayList();
80  
81  		EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(clazz);
82  		for (org.kuali.rice.core.framework.persistence.jpa.metadata.FieldDescriptor field : descriptor.getPrimaryKeys()) {
83  			pkList.add(field.getName());
84  		}
85  
86  		return pkList;
87  	}
88  
89  	/**
90  	 * @see org.kuali.rice.krad.service.PersistenceMetadataExplorerService#listFieldNames(java.lang.Class)
91  	 */
92  	
93  	public List listFieldNames(Class clazz) {
94  		List fieldNames = new ArrayList();
95  
96  		EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(clazz);
97  		for (org.kuali.rice.core.framework.persistence.jpa.metadata.FieldDescriptor field : descriptor.getFields()) {
98  			fieldNames.add(field.getName());
99  		}
100 
101 		return fieldNames;
102 	}
103 
104 	/**
105 	 * @see org.kuali.rice.krad.service.PersistenceMetadataService#clearPrimaryKeyFields(java.lang.Object)
106 	 */
107 	// Unit tests only
108 	// TODO: Write JPA Version
109 	public Object clearPrimaryKeyFields(Object persistableObject) {
110 		/*
111 		 * if (persistableObject == null) { throw new
112 		 * IllegalArgumentException("invalid (null) persistableObject"); }
113 		 * 
114 		 * String className = null; String fieldName = null; try { className =
115 		 * persistableObject.getClass().getName(); List fields =
116 		 * listPrimaryKeyFieldNames(persistableObject.getClass()); for (Iterator
117 		 * i = fields.iterator(); i.hasNext();) { fieldName = (String) i.next();
118 		 * 
119 		 * PropertyUtils.setProperty(persistableObject, fieldName, null); }
120 		 * 
121 		 * if (persistableObject instanceof PersistableBusinessObject) {
122 		 * ((PersistableBusinessObject) persistableObject).setObjectId(null); } }
123 		 * catch (NoSuchMethodException e) { throw new
124 		 * IntrospectionException("no setter for property '" + className + "." +
125 		 * fieldName + "'", e); } catch (IllegalAccessException e) { throw new
126 		 * IntrospectionException("problem accessing property '" + className +
127 		 * "." + fieldName + "'", e); } catch (InvocationTargetException e) {
128 		 * throw new IntrospectionException("problem invoking getter for
129 		 * property '" + className + "." + fieldName + "'", e); }
130 		 */
131 		return persistableObject;
132 	}
133 
134 	/**
135 	 * @see org.kuali.rice.krad.service.PersistenceMetadataExplorerService#listPersistableSubclasses(java.lang.Class)
136 	 */
137 	
138 	// Unit tests only
139 	// TODO: Write JPA Version
140 	public List listPersistableSubclasses(Class superclazz) {
141 		List persistableSubclasses = new ArrayList();
142 		/*
143 		 * if (superclazz == null) { throw new IllegalArgumentException("invalid
144 		 * (null) uberclass"); }
145 		 * 
146 		 * Map allDescriptors = getDescriptorRepository().getDescriptorTable();
147 		 * for (Iterator i = allDescriptors.entrySet().iterator(); i.hasNext();) {
148 		 * Map.Entry e = (Map.Entry) i.next();
149 		 * 
150 		 * Class persistableClass = ((ClassDescriptor)
151 		 * e.getValue()).getClassOfObject(); if
152 		 * (!superclazz.equals(persistableClass) &&
153 		 * superclazz.isAssignableFrom(persistableClass)) {
154 		 * persistableSubclasses.add(persistableClass); } }
155 		 */
156 		return persistableSubclasses;
157 	}
158 
159 	/**
160 	 * @see org.kuali.rice.krad.service.PersistenceService#getRelationshipMetadata(java.lang.Class,
161 	 *      java.lang.String)
162 	 */
163 	
164 	public Map<String, DataObjectRelationship> getRelationshipMetadata(Class persistableClass, String attributeName, String attributePrefix) {
165 		if (persistableClass == null) {
166 			throw new IllegalArgumentException("invalid (null) persistableClass");
167 		}
168 		if (StringUtils.isBlank(attributeName)) {
169 			throw new IllegalArgumentException("invalid (blank) attributeName");
170 		}
171 
172 		Map<String, DataObjectRelationship> relationships = new HashMap<String, DataObjectRelationship>();
173 
174 		EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(persistableClass);
175 		for (ObjectDescriptor objectDescriptor : descriptor.getObjectRelationships()) {
176 			List<String> fks = objectDescriptor.getForeignKeyFields();
177 			if (fks.contains(attributeName) || objectDescriptor.getAttributeName().equals(attributeName)) {
178 				Map<String, String> fkToPkRefs = getForeignKeysForReference(persistableClass, objectDescriptor.getAttributeName());
179 				DataObjectRelationship
180                         rel = new DataObjectRelationship(persistableClass, objectDescriptor.getAttributeName(), objectDescriptor.getTargetEntity());
181 				for (Map.Entry<String, String> ref : fkToPkRefs.entrySet()) {
182 					if (StringUtils.isBlank(attributePrefix)) {
183 						rel.getParentToChildReferences().put(ref.getKey(), ref.getValue());
184 					} else {
185 						rel.getParentToChildReferences().put(attributePrefix + "." + ref.getKey(), ref.getValue());
186 					}
187 				}
188 				relationships.put(objectDescriptor.getAttributeName(), rel);
189 			}
190 		}
191 
192 		return relationships;
193 	}
194 
195 	
196 	// Unit tests only
197 	public Map<String, DataObjectRelationship> getRelationshipMetadata(Class persistableClass, String attributeName) {
198 		return getRelationshipMetadata(persistableClass, attributeName, null);
199 	}
200 
201 	/**
202 	 * @see org.kuali.rice.krad.service.PersistenceService#getForeignKeyFieldName(java.lang.Object,
203 	 *      java.lang.String, java.lang.String)
204 	 */
205 	
206 	public String getForeignKeyFieldName(Class persistableObjectClass, String attributeName, String pkName) {
207 		String fkName = null;
208 
209 		EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(persistableObjectClass);
210 		ObjectDescriptor objectDescriptor = descriptor.getObjectDescriptorByName(attributeName);
211 		if (objectDescriptor == null) {
212 			throw new RuntimeException("Attribute name " + attributeName + " is not a valid reference to class " + persistableObjectClass.getName());
213 		}
214 		List<org.kuali.rice.core.framework.persistence.jpa.metadata.FieldDescriptor> matches = new ArrayList<org.kuali.rice.core.framework.persistence.jpa.metadata.FieldDescriptor>();
215 		for (org.kuali.rice.core.framework.persistence.jpa.metadata.FieldDescriptor field : descriptor.getFields()) {
216 			String column = field.getColumn();
217 			for (JoinColumnDescriptor join : objectDescriptor.getJoinColumnDescriptors()) {
218 				if (column != null && column.equals(join.getName())) {
219 					matches.add(field);
220 				}
221 			}
222 		}
223 
224 		if (matches.size() == 1) {
225 			fkName = matches.get(0).getName();
226 		} else {
227 			throw new RuntimeException("Implement me!");
228 		}
229 
230 		return fkName;
231 	}
232 
233 	/**
234 	 * @see org.kuali.rice.krad.service.PersistenceService#getReferencesForForeignKey(java.lang.Class,
235 	 *      java.lang.String)
236 	 */
237 	
238 	public Map getReferencesForForeignKey(Class persistableObjectClass, String attributeName) {
239 		Map referenceClasses = new HashMap();
240 
241 		if (PersistableBusinessObject.class.isAssignableFrom(persistableObjectClass)) {
242 			EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(persistableObjectClass);
243 			for (ObjectDescriptor objectDescriptor : descriptor.getObjectRelationships()) {
244 				List<String> refFkNames = objectDescriptor.getForeignKeyFields();
245 				for (String fk : refFkNames) {
246 					if (fk.equals(attributeName)) {
247 						referenceClasses.put(objectDescriptor.getAttributeName(), objectDescriptor.getTargetEntity());
248 					}
249 				}
250 			}
251 		}
252 
253 		return referenceClasses;
254 	}
255 
256 	/**
257 	 * @see org.kuali.rice.krad.service.PersistenceService#getForeignKeysForReference(java.lang.Class,
258 	 *      java.lang.String) The Map structure is: Key(String fkFieldName) =>
259 	 *      Value(String pkFieldName) NOTE that this implementation depends on
260 	 *      the ordering of foreign-key elements in the ojb-repository matching
261 	 *      the ordering of primary-key declarations of the class on the other
262 	 *      side of the relationship. This is done because: 1. The current
263 	 *      version of OJB requires you to declare all of these things in the
264 	 *      correct (and matching) order in the ojb-repository file for it to
265 	 *      work at all. 2. There is no other way to match a given foreign-key
266 	 *      reference to its corresponding primary-key on the opposing side of
267 	 *      the relationship. Yes, this is a crummy way to do it, but OJB doesnt
268 	 *      provide explicit matches of foreign-keys to primary keys, and always
269 	 *      assumes that foreign-keys map to primary keys on the other object,
270 	 *      and never to a set of candidate keys, or any other column.
271 	 */
272 	
273 	public Map getForeignKeysForReference(Class clazz, String attributeName) {
274 
275 		// yelp if nulls were passed in
276 		if (clazz == null) {
277 			throw new IllegalArgumentException("The Class passed in for the clazz argument was null.");
278 		}
279 		if (attributeName == null) {
280 			throw new IllegalArgumentException("The String passed in for the attributeName argument was null.");
281 		}
282 
283 		// get the class of the attribute name
284 		Class attributeClass = getBusinessObjectAttributeClass(clazz, attributeName);
285 		if (attributeClass == null) {
286 			throw new ReferenceAttributeDoesntExistException("Requested attribute: '" + attributeName + "' does not exist on class: '" + clazz.getName() + "'.");
287 		}
288 
289 		// make sure the class of the attribute descends from BusinessObject,
290 		// otherwise throw an exception
291 		if (!PersistableBusinessObject.class.isAssignableFrom(attributeClass)) {
292 			throw new ObjectNotABusinessObjectRuntimeException("Attribute requested (" + attributeName + ") is of class: '" + attributeClass.getName() + "' and is not a descendent of BusinessObject.  Only descendents of BusinessObject can be used.");
293 		}
294 
295 		return determineFkMap(clazz, attributeName, attributeClass);
296 	}
297 
298 	private Map determineFkMap(Class clazz, String attributeName, Class attributeClass) {
299 		Map fkMap = new HashMap();
300 		EntityDescriptor entityDescriptor = MetadataManager.getEntityDescriptor(clazz);
301 		ObjectDescriptor objectDescriptor = entityDescriptor.getObjectDescriptorByName(attributeName);
302 		if (objectDescriptor == null) {
303 			throw new ReferenceAttributeNotAnOjbReferenceException("Attribute requested (" + attributeName + ") is not defined in JPA annotations for class: '" + clazz.getName() + "'");
304 		}
305 
306 		// special case when the attributeClass passed in doesnt match the
307 		// class of the reference-descriptor as defined in ojb-repository.
308 		// Currently
309 		// the only case of this happening is ObjectCode vs.
310 		// ObjectCodeCurrent.
311 		if (!attributeClass.equals(objectDescriptor.getTargetEntity())) {
312 			if (referenceConversionMap.containsKey(attributeClass)) {
313 				attributeClass = referenceConversionMap.get(attributeClass);
314 			} else {
315 				throw new RuntimeException("The Class of the Java member [" + attributeClass.getName() + "] '" + attributeName + "' does not match the class of the reference [" + objectDescriptor.getTargetEntity().getName() + "]. " + "This is an unhandled special case for which special code needs to be written in this class.");
316 			}
317 		}
318 
319 		// get the list of the foreign-keys for this reference-descriptor
320 		// (OJB)
321 		List<String> fkFields = objectDescriptor.getForeignKeyFields();
322 		Iterator<String> fkIterator = fkFields.iterator();
323 
324 		// get the list of the corresponding pk fields on the other side of
325 		// the relationship
326 		List pkFields = getPrimaryKeys(attributeClass);
327 		Iterator pkIterator = pkFields.iterator();
328 
329 		// make sure the size of the pkIterator is the same as the
330 		// size of the fkIterator, otherwise this whole thing is borked
331 		if (pkFields.size() != fkFields.size()) {
332 			throw new RuntimeException("KualiPersistenceStructureService Error: The number of " + "foreign keys doesnt match the number of primary keys.");
333 		}
334 
335 		// walk through the list of the foreign keys, get their types
336 		while (fkIterator.hasNext()) {
337 			// if there is a next FK but not a next PK, then we've got a big
338 			// problem,
339 			// and cannot continue
340 			if (!pkIterator.hasNext()) {
341 				throw new RuntimeException("The number of foriegn keys dont match the number of primary keys for the reference '" + attributeName + "', on BO of type '" + clazz.getName() + "'.  " + "This should never happen under normal circumstances.");
342 			}
343 
344 			// get the field name of the fk & pk field
345 			String fkFieldName = (String) fkIterator.next();
346 			String pkFieldName = (String) pkIterator.next();
347 
348 			// add the fieldName and fieldType to the map
349 			fkMap.put(fkFieldName, pkFieldName);
350 		}
351 		return fkMap;
352 	}
353 
354 	
355 	public Map<String, String> getInverseForeignKeysForCollection(Class boClass, String collectionName) {
356 		// yelp if nulls were passed in
357 		if (boClass == null) {
358 			throw new IllegalArgumentException("The Class passed in for the boClass argument was null.");
359 		}
360 		if (collectionName == null) {
361 			throw new IllegalArgumentException("The String passed in for the attributeName argument was null.");
362 		}
363 
364 		PropertyDescriptor propertyDescriptor = null;
365 
366 		// make an instance of the class passed
367 		Object classInstance;
368 		try {
369 			classInstance = boClass.newInstance();
370 		} catch (Exception e) {
371 			throw new RuntimeException(e);
372 		}
373 
374 		// make sure the attribute exists at all, throw exception if not
375 		try {
376 			propertyDescriptor = PropertyUtils.getPropertyDescriptor(classInstance, collectionName);
377 		} catch (Exception e) {
378 			throw new RuntimeException(e);
379 		}
380 		if (propertyDescriptor == null) {
381 			throw new ReferenceAttributeDoesntExistException("Requested attribute: '" + collectionName + "' does not exist " + "on class: '" + boClass.getName() + "'. GFK");
382 		}
383 
384 		// get the class of the attribute name
385 		Class attributeClass = propertyDescriptor.getPropertyType();
386 
387 		// make sure the class of the attribute descends from BusinessObject,
388 		// otherwise throw an exception
389 		if (!Collection.class.isAssignableFrom(attributeClass)) {
390 			throw new ObjectNotABusinessObjectRuntimeException("Attribute requested (" + collectionName + ") is of class: " + "'" + attributeClass.getName() + "' and is not a " + "descendent of Collection");
391 		}
392 
393 		EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(boClass);
394 		org.kuali.rice.core.framework.persistence.jpa.metadata.CollectionDescriptor cd = descriptor.getCollectionDescriptorByName(collectionName);
395 		List<String> childPrimaryKeys = cd.getForeignKeyFields();
396 
397 		List parentForeignKeys = getPrimaryKeys(boClass);
398 
399 		if (parentForeignKeys.size() != childPrimaryKeys.size()) {
400 			throw new RuntimeException("The number of keys in the class descriptor and the inverse foreign key mapping for the collection descriptors do not match.");
401 		}
402 
403 		Map<String, String> fkToPkMap = new HashMap<String, String>();
404 		Iterator pFKIter = parentForeignKeys.iterator();
405 		Iterator cPKIterator = childPrimaryKeys.iterator();
406 
407 		while (pFKIter.hasNext()) {
408 			String parentForeignKey = (String) pFKIter.next();
409 			String childPrimaryKey = (String) cPKIterator.next();
410 
411 			fkToPkMap.put(parentForeignKey, childPrimaryKey);
412 		}
413 		return fkToPkMap;
414 	}
415 
416 	/**
417 	 * @see org.kuali.rice.krad.service.PersistenceService#getNestedForeignKeyMap(java.lang.Class)
418 	 */
419 	
420 	public Map getNestedForeignKeyMap(Class persistableObjectClass) {
421 		Map fkMap = new HashMap();
422 
423 		// Rice JPA MetadataManager
424 		// Note: Not sure how to test this method, and JPA and OJB ordering of
425 		// PK/FK is not the same as well. This should be tested more thoroughly.
426 		EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(persistableObjectClass);
427 		for (ObjectDescriptor objectReferenceDescriptor : descriptor.getObjectRelationships()) {
428 			EntityDescriptor referenceDescriptor = MetadataManager.getEntityDescriptor(objectReferenceDescriptor.getTargetEntity());
429 			List<String> fkFields = objectReferenceDescriptor.getForeignKeyFields();
430 			Set<org.kuali.rice.core.framework.persistence.jpa.metadata.FieldDescriptor> pkFields = referenceDescriptor.getPrimaryKeys();
431 			int i = 0;
432 			for (org.kuali.rice.core.framework.persistence.jpa.metadata.FieldDescriptor fd : pkFields) {
433 				fkMap.put(objectReferenceDescriptor.getAttributeName() + "." + fd.getName(), fkFields.get(i));
434 				i++;
435 			}
436 		}
437 
438 		return fkMap;
439 	}
440 
441 	/**
442 	 * @see org.kuali.rice.krad.service.PersistenceMetadataService#hasPrimaryKeyFieldValues(java.lang.Object)
443 	 */
444 	public boolean hasPrimaryKeyFieldValues(Object persistableObject) {
445 		Map keyFields = getPrimaryKeyFieldValues(persistableObject);
446 
447 		boolean emptyField = false;
448 		for (Iterator i = keyFields.entrySet().iterator(); !emptyField && i.hasNext();) {
449 			Map.Entry e = (Map.Entry) i.next();
450 
451 			Object fieldValue = e.getValue();
452 			if (fieldValue == null) {
453 				emptyField = true;
454 			} else if (fieldValue instanceof String) {
455 				if (StringUtils.isEmpty((String) fieldValue)) {
456 					emptyField = true;
457 				} else {
458 					emptyField = false;
459 				}
460 			}
461 		}
462 
463 		return !emptyField;
464 	}
465 
466 	/**
467 	 * @see org.kuali.rice.krad.service.PersistenceService#getForeignKeyFieldsPopulationState(org.kuali.rice.krad.bo.BusinessObject,
468 	 *      java.lang.String)
469 	 */
470 	public ForeignKeyFieldsPopulationState getForeignKeyFieldsPopulationState(PersistableBusinessObject bo, String referenceName) {
471 
472 		boolean allFieldsPopulated = true;
473 		boolean anyFieldsPopulated = false;
474 		List<String> unpopulatedFields = new ArrayList<String>();
475 
476 		// yelp if nulls were passed in
477 		if (bo == null) {
478 			throw new IllegalArgumentException("The Class passed in for the BusinessObject argument was null.");
479 		}
480 		if (StringUtils.isBlank(referenceName)) {
481 			throw new IllegalArgumentException("The String passed in for the referenceName argument was null or empty.");
482 		}
483 
484 		PropertyDescriptor propertyDescriptor = null;
485 
486 		// make sure the attribute exists at all, throw exception if not
487 		try {
488 			propertyDescriptor = PropertyUtils.getPropertyDescriptor(bo, referenceName);
489 		} catch (Exception e) {
490 			throw new RuntimeException(e);
491 		}
492 		if (propertyDescriptor == null) {
493 			throw new ReferenceAttributeDoesntExistException("Requested attribute: '" + referenceName + "' does not exist " + "on class: '" + bo.getClass().getName() + "'.");
494 		}
495 
496 		// get the class of the attribute name
497 		Class referenceClass = propertyDescriptor.getPropertyType();
498 
499 		// make sure the class of the attribute descends from BusinessObject,
500 		// otherwise throw an exception
501 		if (!PersistableBusinessObject.class.isAssignableFrom(referenceClass)) {
502 			throw new ObjectNotABusinessObjectRuntimeException("Attribute requested (" + referenceName + ") is of class: " + "'" + referenceClass.getName() + "' and is not a " + "descendent of BusinessObject.  Only descendents of BusinessObject " + "can be used.");
503 		}
504 
505 		EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(bo.getClass());
506 		ObjectDescriptor objectDescriptor = descriptor.getObjectDescriptorByName(referenceName);
507 
508 		if (objectDescriptor == null) {
509 			throw new ReferenceAttributeNotAnOjbReferenceException("Attribute requested (" + referenceName + ") is not listed " + "in OJB as a reference-descriptor for class: '" + bo.getClass().getName() + "'");
510 		}
511 
512 		List<String> fkFields = objectDescriptor.getForeignKeyFields();
513 		Iterator fkIterator = fkFields.iterator();
514 
515 		// walk through the list of the foreign keys, get their types
516 		while (fkIterator.hasNext()) {
517 
518 			// get the field name of the fk & pk field
519 			String fkFieldName = (String) fkIterator.next();
520 
521 			// get the value for the fk field
522 			Object fkFieldValue = null;
523 			try {
524 				fkFieldValue = PropertyUtils.getSimpleProperty(bo, fkFieldName);
525 			}
526 
527 			// abort if the value is not retrievable
528 			catch (Exception e) {
529 				throw new RuntimeException(e);
530 			}
531 
532 			// test the value
533 			if (fkFieldValue == null) {
534 				allFieldsPopulated = false;
535 				unpopulatedFields.add(fkFieldName);
536 			} else if (fkFieldValue instanceof String) {
537 				if (StringUtils.isBlank((String) fkFieldValue)) {
538 					allFieldsPopulated = false;
539 					unpopulatedFields.add(fkFieldName);
540 				} else {
541 					anyFieldsPopulated = true;
542 				}
543 			} else {
544 				anyFieldsPopulated = true;
545 			}
546 		}
547 
548 		// sanity check. if the flag for all fields populated is set, then
549 		// there should be nothing in the unpopulatedFields list
550 		if (allFieldsPopulated) {
551 			if (!unpopulatedFields.isEmpty()) {
552 				throw new RuntimeException("The flag is set that indicates all fields are populated, but there " + "are fields present in the unpopulatedFields list.  This should never happen, and indicates " + "that the logic in this method is broken.");
553 			}
554 		}
555 
556 		return new ForeignKeyFieldsPopulationState(allFieldsPopulated, anyFieldsPopulated, unpopulatedFields);
557 	}
558 
559 	/**
560 	 * @see org.kuali.rice.krad.service.PersistenceStructureService#listReferenceObjectFieldNames(java.lang.Class)
561 	 */
562 	
563 	public Map<String, Class> listReferenceObjectFields(Class boClass) {
564 		// validate parameter
565 		if (boClass == null) {
566 			throw new IllegalArgumentException("Class specified in the parameter was null.");
567 		}
568 		if (!PersistableBusinessObject.class.isAssignableFrom(boClass)) {
569 			throw new IllegalArgumentException("Class specified [" + boClass.getName() + "] must be a class that " + "inherits from BusinessObject.");
570 		}
571 
572 		EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(boClass);
573 		Map<String, Class> references = new HashMap();
574 		for (org.kuali.rice.core.framework.persistence.jpa.metadata.ObjectDescriptor od : descriptor.getObjectRelationships()) {
575 			references.put(od.getAttributeName(), od.getTargetEntity());
576 		}
577 
578 		return references;
579 	}
580 
581 	
582 	public Map<String, Class> listCollectionObjectTypes(Class boClass) {
583 		if (boClass == null) {
584 			throw new IllegalArgumentException("Class specified in the parameter was null.");
585 		}
586 
587 		Map<String, Class> references = new HashMap();
588 
589 		EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(boClass);
590 		if (descriptor == null) {
591 			return references;
592 		}
593 
594 		for (org.kuali.rice.core.framework.persistence.jpa.metadata.CollectionDescriptor cd : descriptor.getCollectionRelationships()) {
595 			references.put(cd.getAttributeName(), cd.getTargetEntity());
596 		}
597 
598 		return references;
599 	}
600 
601 	public Map<String, Class> listCollectionObjectTypes(PersistableBusinessObject bo) {
602 		// validate parameter
603 		if (bo == null) {
604 			throw new IllegalArgumentException("BO specified in the parameter was null.");
605 		}
606 		if (!(bo instanceof PersistableBusinessObject)) {
607 			throw new IllegalArgumentException("BO specified [" + bo.getClass().getName() + "] must be a class that " + "inherits from BusinessObject.");
608 		}
609 
610 		return listCollectionObjectTypes(bo.getClass());
611 	}
612 
613 	/**
614 	 * @see org.kuali.rice.krad.service.PersistenceStructureService#listReferenceObjectFieldNames(org.kuali.rice.krad.bo.BusinessObject)
615 	 */
616 	public Map<String, Class> listReferenceObjectFields(PersistableBusinessObject bo) {
617 		// validate parameter
618 		if (bo == null) {
619 			throw new IllegalArgumentException("BO specified in the parameter was null.");
620 		}
621 		if (!(bo instanceof PersistableBusinessObject)) {
622 			throw new IllegalArgumentException("BO specified [" + bo.getClass().getName() + "] must be a class that " + "inherits from BusinessObject.");
623 		}
624 
625 		return listReferenceObjectFields(bo.getClass());
626 	}
627 
628 	
629 	public boolean isReferenceUpdatable(Class boClass, String referenceName) {
630 		EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(boClass);
631 		return descriptor.getObjectDescriptorByName(referenceName).isUpdateable();
632 //		for (CascadeType ct : descriptor.getObjectDescriptorByName(referenceName).getCascade()) {
633 //			if (ct.equals(CascadeType.ALL) || ct.equals(CascadeType.MERGE) || ct.equals(CascadeType.PERSIST)) {
634 //				return true;
635 //			}
636 //		}
637 //		return false;
638 	}
639 
640 	
641 	public boolean isCollectionUpdatable(Class boClass, String collectionName) {
642 		EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(boClass);
643 		return descriptor.getCollectionDescriptorByName(collectionName).isUpdateable();
644 //		for (CascadeType ct : descriptor.getCollectionDescriptorByName(collectionName).getCascade()) {
645 //			if (ct.equals(CascadeType.ALL) || ct.equals(CascadeType.MERGE) || ct.equals(CascadeType.PERSIST)) {
646 //				return true;
647 //			}
648 //		}
649 //		return false;
650 	}
651 
652 	
653 	public boolean hasCollection(Class boClass, String collectionName) {
654 		EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(boClass);
655 		return descriptor.getCollectionDescriptorByName(collectionName) != null;
656 	}
657 
658 	
659 	public boolean hasReference(Class boClass, String referenceName) {
660 		EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(boClass);
661 		return descriptor.getObjectDescriptorByName(referenceName) != null;
662 	}
663 
664 	/**
665 	 * @see org.kuali.rice.krad.service.PersistenceStructureService#getTableName(java.lang.Class)
666 	 */
667 	
668 	public String getTableName(Class<? extends PersistableBusinessObject> boClass) {
669 		EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(boClass);
670 		return descriptor.getTable();
671 	}
672 }
673