View Javadoc

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