001    /**
002     * Copyright 2005-2012 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     */
016    package org.kuali.rice.krad.service.impl;
017    
018    import java.beans.PropertyDescriptor;
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.HashMap;
022    import java.util.Iterator;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.Set;
026    
027    import org.apache.commons.beanutils.PropertyUtils;
028    import org.apache.commons.lang.StringUtils;
029    import org.kuali.rice.core.framework.persistence.jpa.metadata.EntityDescriptor;
030    import org.kuali.rice.core.framework.persistence.jpa.metadata.JoinColumnDescriptor;
031    import org.kuali.rice.core.framework.persistence.jpa.metadata.MetadataManager;
032    import org.kuali.rice.core.framework.persistence.jpa.metadata.ObjectDescriptor;
033    import org.kuali.rice.krad.bo.DataObjectRelationship;
034    import org.kuali.rice.krad.bo.PersistableBusinessObject;
035    import org.kuali.rice.krad.exception.ObjectNotABusinessObjectRuntimeException;
036    import org.kuali.rice.krad.exception.ReferenceAttributeDoesntExistException;
037    import org.kuali.rice.krad.exception.ReferenceAttributeNotAnOjbReferenceException;
038    import org.kuali.rice.krad.service.PersistenceStructureService;
039    import org.kuali.rice.krad.util.ForeignKeyFieldsPopulationState;
040    
041    public class PersistenceStructureServiceJpaImpl extends PersistenceServiceImplBase implements PersistenceStructureService {
042    
043            /**
044             * 
045             * special case when the attributeClass passed in doesnt match the class of
046             * the reference-descriptor as defined in ojb-repository. Currently the only
047             * case of this happening is ObjectCode vs. ObjectCodeCurrent.
048             * 
049             * NOTE: This method makes no real sense and is a product of a hack
050             * introduced by KFS for an unknown reason. If you find yourself using this
051             * map stop and go do something else.
052             * 
053             * @param from
054             *            the class in the code
055             * @param to
056             *            the class in the repository
057             */
058            public static Map<Class, Class> referenceConversionMap = new HashMap<Class, Class>();
059    
060            /**
061             * @see org.kuali.rice.krad.service.PersistenceService#isPersistable(java.lang.Class)
062             */
063            
064            public boolean isPersistable(Class clazz) {
065                    boolean isPersistable = false;
066    
067                    if (MetadataManager.getEntityDescriptor(clazz) != null) {
068                            isPersistable = true;
069                    }
070    
071                    return isPersistable;
072            }
073    
074            /**
075             * @see org.kuali.rice.krad.service.PersistenceService#getPrimaryKeys(java.lang.Class)
076             */
077            
078            public List getPrimaryKeys(Class clazz) {
079                    List pkList = new ArrayList();
080    
081                    EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(clazz);
082                    for (org.kuali.rice.core.framework.persistence.jpa.metadata.FieldDescriptor field : descriptor.getPrimaryKeys()) {
083                            pkList.add(field.getName());
084                    }
085    
086                    return pkList;
087            }
088    
089            /**
090             * @see org.kuali.rice.krad.service.PersistenceMetadataExplorerService#listFieldNames(java.lang.Class)
091             */
092            
093            public List listFieldNames(Class clazz) {
094                    List fieldNames = new ArrayList();
095    
096                    EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(clazz);
097                    for (org.kuali.rice.core.framework.persistence.jpa.metadata.FieldDescriptor field : descriptor.getFields()) {
098                            fieldNames.add(field.getName());
099                    }
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