View Javadoc

1   /**
2    * Copyright 2005-2014 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.kns.lookup;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.ojb.broker.query.Criteria;
20  import org.kuali.rice.core.api.search.Range;
21  import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
22  import org.kuali.rice.core.api.CoreApiServiceLocator;
23  import org.kuali.rice.core.api.config.property.ConfigurationService;
24  import org.kuali.rice.core.api.datetime.DateTimeService;
25  import org.kuali.rice.core.framework.persistence.platform.DatabasePlatform;
26  import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
27  import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
28  import org.kuali.rice.kns.service.KNSServiceLocator;
29  import org.kuali.rice.kns.util.KNSGlobalVariables;
30  import org.kuali.rice.kns.web.comparator.NullValueComparator;
31  import org.kuali.rice.kns.web.struts.form.KualiForm;
32  import org.kuali.rice.kns.web.struts.form.LookupForm;
33  import org.kuali.rice.kns.web.ui.Field;
34  import org.kuali.rice.kns.web.ui.ResultRow;
35  import org.kuali.rice.krad.bo.BusinessObject;
36  import org.kuali.rice.krad.bo.DataObjectRelationship;
37  import org.kuali.rice.krad.datadictionary.RelationshipDefinition;
38  import org.kuali.rice.krad.datadictionary.control.ControlDefinition;
39  import org.kuali.rice.krad.exception.ClassNotPersistableException;
40  import org.kuali.rice.krad.lookup.SelectiveReferenceRefresher;
41  import org.kuali.rice.krad.service.DataDictionaryService;
42  import org.kuali.rice.krad.service.KRADServiceLocator;
43  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
44  import org.kuali.rice.krad.service.PersistenceStructureService;
45  import org.kuali.rice.krad.util.KRADConstants;
46  import org.kuali.rice.krad.util.ObjectUtils;
47  
48  import java.util.ArrayList;
49  import java.util.Collection;
50  import java.util.Comparator;
51  import java.util.HashMap;
52  import java.util.HashSet;
53  import java.util.Iterator;
54  import java.util.List;
55  import java.util.Map;
56  import java.util.Set;
57  import java.util.StringTokenizer;
58  
59  /**
60   * Utility class for Lookup related utilities and helper methods.
61   */
62  public class LookupUtils {
63      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LookupUtils.class);
64  
65      public LookupUtils() {
66          // default constructor for Spring to call to start up initialization process
67      }
68  
69      /**
70       * Removes fields identified in the data dictionary as hidden from the lookup field values.
71       * (This will remove Universal User ID and Person name from search requests when a user ID is entered.)
72       *
73       * @param fieldValues
74       */
75      public static void removeHiddenCriteriaFields(Class businessObjectClass, Map fieldValues) {
76          List<String> lookupFieldAttributeList =
77                  getBusinessObjectMetaDataService().getLookupableFieldNames(businessObjectClass);
78          if (lookupFieldAttributeList != null) {
79              for (Iterator iter = lookupFieldAttributeList.iterator(); iter.hasNext(); ) {
80                  String attributeName = (String) iter.next();
81                  if (fieldValues.containsKey(attributeName)) {
82                      ControlDefinition controlDef = getDataDictionaryService()
83                              .getAttributeControlDefinition(businessObjectClass, attributeName);
84                      if (controlDef != null && controlDef.isHidden()) {
85                          fieldValues.remove(attributeName);
86                      }
87                  }
88              }
89          }
90      }
91  
92      /**
93       * Parses and returns the lookup result set limit, checking first for the limit
94       * for the BO being looked up, and then the global application limit if there isn't a limit
95       * specific to this BO.
96       *
97       * @param businessObjectClass BO class to search on / get limit for.  If the passed in type is not of type
98       * {@link org.kuali.rice.krad.bo.BusinessObject}, then the application-wide default limit is used.
99       * @return result set limit (or null if there isn't one)
100      */
101     public static Integer getSearchResultsLimit(Class businessObjectClass) {
102         Integer limit = null;
103         if (BusinessObject.class.isAssignableFrom(businessObjectClass)) {
104             limit = getBusinessObjectSearchResultsLimit(businessObjectClass);
105         }
106         if (limit == null) {
107             limit = getApplicationSearchResultsLimit();
108         }
109         return limit;
110     }
111 
112     /**
113      *
114      */
115     public static Integer getApplicationSearchResultsLimit() {
116         String limitString = CoreFrameworkServiceLocator.getParameterService()
117                 .getParameterValueAsString(KRADConstants.KNS_NAMESPACE,
118                         KRADConstants.DetailTypes.LOOKUP_PARM_DETAIL_TYPE,
119                         KRADConstants.SystemGroupParameterNames.LOOKUP_RESULTS_LIMIT);
120         if (limitString != null) {
121             return Integer.valueOf(limitString);
122         }
123         return null;
124     }
125 
126     /**
127      * Parses and returns the lookup result set limit for the passed in BO (if one exists)
128      *
129      * @param businessObjectClass
130      * @return result set limit for this BO (or null if the BO doesn't have a limit)
131      */
132     public static Integer getBusinessObjectSearchResultsLimit(Class businessObjectClass) {
133 		if (!(isMultipleValueLookup())) {
134 			return getBusinessObjectDictionaryService().getLookupResultSetLimit(businessObjectClass);
135 		} else {
136 	        return getBusinessObjectDictionaryService().getMultipleValueLookupResultSetLimit(businessObjectClass);
137 		}
138     }
139 
140 	private static boolean isMultipleValueLookup() {
141 		KualiForm kualiForm = KNSGlobalVariables.getKualiForm();
142 		if (kualiForm instanceof LookupForm) {
143 			LookupForm lookupForm = (LookupForm) kualiForm;
144 			return lookupForm.isMultipleValues();
145 		} else {
146 			return false;
147 		}
148 	} 
149 	
150     /**
151      * This method applies the search results limit to the search criteria for this BO
152      *
153      * @param businessObjectClass BO class to search on / get limit for
154      * @param criteria search criteria
155      * @param platform database platform
156      */
157     public static void applySearchResultsLimit(Class businessObjectClass, Criteria criteria,
158             DatabasePlatform platform) {
159         Integer limit = getSearchResultsLimit(businessObjectClass);
160         if (limit != null) {
161             platform.applyLimit(limit, criteria);
162         }
163     }
164 
165     /**
166      * Applies the search results limit to the search criteria for this BO (JPA)
167      *
168      * @param businessObjectClass BO class to search on / get limit for
169      * @param criteria search criteria
170      */
171     public static void applySearchResultsLimit(Class businessObjectClass,
172             org.kuali.rice.core.framework.persistence.jpa.criteria.Criteria criteria) {
173         Integer limit = getSearchResultsLimit(businessObjectClass);
174         if (limit != null) {
175             criteria.setSearchLimit(limit);
176         }
177     }
178 
179     /**
180      * This method the maximum rows per page in a multiple value lookup
181      *
182      * @see org.kuali.KRADConstants.SystemGroupParameterNames#MULTIPLE_VALUE_LOOKUP_RESULTS_PER_PAGE
183      * @return
184      */
185     public static Integer getApplicationMaximumSearchResulsPerPageForMultipleValueLookups() {
186         String limitString = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KRADConstants.KNS_NAMESPACE, KRADConstants.DetailTypes.LOOKUP_PARM_DETAIL_TYPE, KRADConstants.SystemGroupParameterNames.MULTIPLE_VALUE_LOOKUP_RESULTS_PER_PAGE);
187         if (limitString != null) {
188             return Integer.valueOf(limitString);
189         }
190         return null;
191     }
192 
193     /**
194      * This makes a , delimited String list of fields separated by a , into a List of target --> lookup readOnlyFieldsList.
195      *
196      * @param readOnlyFields
197      * @return the List representation of the readOnlyFields  String provided.
198      */
199     public static List<String> translateReadOnlyFieldsToList(String readOnlyFieldsString) {
200         List<String> readOnlyFieldsList = new ArrayList<String>();
201         if (StringUtils.isNotEmpty(readOnlyFieldsString)) {
202             if (readOnlyFieldsString.indexOf(",") > 0) {
203                 StringTokenizer token = new StringTokenizer(readOnlyFieldsString, ",");
204                 while (token.hasMoreTokens()) {
205                     String element = token.nextToken();
206                     readOnlyFieldsList.add(element);
207                 }
208             }
209             else {
210                 readOnlyFieldsList.add(readOnlyFieldsString);
211             }
212         }
213       return readOnlyFieldsList;
214     }
215 
216     /**
217      * This translates a , delimited list of pairs separated by a : into a Map of target --> lookup field conversions.
218      *
219      * @param conversionFields
220      * @return the Map representation of the fieldConversions String provided.
221      */
222     public static Map<String, String> translateFieldConversions(String fieldConversionsString) {
223         Map<String, String> fieldConversionsMap = new HashMap();
224         if (StringUtils.isNotEmpty(fieldConversionsString)) {
225             if (fieldConversionsString.indexOf(",") > 0) {
226                 StringTokenizer token = new StringTokenizer(fieldConversionsString, ",");
227                 while (token.hasMoreTokens()) {
228                     String element = token.nextToken();
229                     fieldConversionsMap.put(element.substring(0, element.indexOf(":")), element.substring(element.indexOf(":") + 1));
230                 }
231             }
232             else {
233                 fieldConversionsMap.put(fieldConversionsString.substring(0, fieldConversionsString.indexOf(":")), fieldConversionsString.substring(fieldConversionsString.indexOf(":") + 1));
234             }
235         }
236         return fieldConversionsMap;
237     }
238 
239     @Deprecated
240     public static Field setFieldQuickfinder(BusinessObject businessObject,
241             String attributeName, Field field, List displayedFieldNames) {
242         return setFieldQuickfinder( businessObject, (String)null, false, 0, attributeName, field, displayedFieldNames );
243     }
244 
245     @Deprecated
246     public static Field setFieldQuickfinder(BusinessObject businessObject,
247             String attributeName, Field field, List displayedFieldNames, SelectiveReferenceRefresher srr) {
248         return setFieldQuickfinder( businessObject, (String)null, false, 0, attributeName, field, displayedFieldNames, srr );
249     }
250 
251     /**
252      * Sets a fields quickfinder class and field conversions for an attribute.
253      */
254     @Deprecated
255     public static Field setFieldQuickfinder(BusinessObject businessObject, String collectionName, boolean addLine, int index,
256             String attributeName, Field field, List displayedFieldNames, SelectiveReferenceRefresher srr) {
257         field = setFieldQuickfinder(businessObject, collectionName, addLine, index, attributeName, field, displayedFieldNames);
258         if (srr != null) {
259             String collectionPrefix = "";
260             if ( collectionName != null ) {
261                 if (addLine) {
262                     collectionPrefix = KRADConstants.MAINTENANCE_ADD_PREFIX + collectionName + ".";
263                 }
264                 else {
265                     collectionPrefix = collectionName + "[" + index + "].";
266                 }
267             }
268             field.setReferencesToRefresh(convertReferencesToSelectCollectionToString(
269                     srr.getAffectedReferencesFromLookup(businessObject, attributeName, collectionPrefix)));
270         }
271         return field;
272     }
273 
274     /**
275      * Sets a fields quickfinder class and field conversions for an attribute.
276      */
277     @Deprecated
278     public static Field setFieldQuickfinder(BusinessObject businessObject, String collectionName, boolean addLine, int index,
279                                             String attributeName, Field field, List displayedFieldNames) {
280         boolean noLookup = false;
281         if (businessObject == null) {
282             return field;
283         }
284 
285         Boolean noLookupField = getBusinessObjectDictionaryService().noLookupFieldLookup(businessObject.getClass(), attributeName);
286         if (noLookupField != null && noLookupField) {
287             noLookup = true;
288         }
289 
290          return setFieldQuickfinder(businessObject, collectionName, addLine, index, attributeName, field, displayedFieldNames, noLookup);
291 
292     }
293 
294     @Deprecated
295     public static Field setFieldQuickfinder(BusinessObject businessObject, String collectionName, boolean addLine, int index, String attributeName, Field field, List displayedFieldNames, boolean noLookupField)
296     {
297          if (businessObject == null) {
298             return field;
299         }
300 
301         if (noLookupField) {
302             return field;
303         }
304         DataObjectRelationship relationship = null;
305         if ( LOG.isDebugEnabled() ) {
306             LOG.debug( "setFieldQuickfinder("+businessObject.getClass().getName()+","+attributeName+","+field+","+displayedFieldNames+")" );
307         }
308 
309         relationship = getBusinessObjectMetaDataService().getBusinessObjectRelationship(businessObject, businessObject.getClass(), attributeName, "", false);
310 
311         String collectionPrefix = "";
312         if ( collectionName != null ) {
313             if (addLine) {
314                 collectionPrefix = KRADConstants.MAINTENANCE_ADD_PREFIX + collectionName + ".";
315             }
316             else {
317                 collectionPrefix = collectionName + "[" + index + "].";
318             }
319         }
320 
321         if (relationship == null) {
322             Class c = ObjectUtils.getPropertyType(businessObject, attributeName, getPersistenceStructureService());
323 
324             if(c!=null) {
325                 if (attributeName.contains(".")) {
326                     attributeName = StringUtils.substringBeforeLast( attributeName, "." );
327                 }
328 
329                 RelationshipDefinition ddReference = getBusinessObjectMetaDataService().getBusinessObjectRelationshipDefinition(businessObject, attributeName);
330                 relationship = getBusinessObjectMetaDataService().getBusinessObjectRelationship(ddReference, businessObject, businessObject.getClass(), attributeName, "", false);
331                 if(relationship!=null) {
332                     field.setQuickFinderClassNameImpl(relationship.getRelatedClass().getName());
333                     field.setFieldConversions(generateFieldConversions( businessObject, collectionPrefix, relationship, field.getPropertyPrefix(), displayedFieldNames, null));
334                     field.setLookupParameters(generateLookupParameters( businessObject, collectionPrefix, relationship, field.getPropertyPrefix(), displayedFieldNames, null));
335                     field.setBaseLookupUrl(LookupUtils.getBaseLookupUrl(false));
336                     field.setImageSrc(getBusinessObjectDictionaryService().getSearchIconOverride(businessObject.getClass()));
337                 }
338             }
339 
340             return field;
341         }
342         if (ObjectUtils.isNestedAttribute(attributeName)) {
343             //first determine the prefix and the attribute we are referring to
344             String nestedAttributePrefix = StringUtils.substringBeforeLast(attributeName, ".");
345 
346             field.setQuickFinderClassNameImpl(relationship.getRelatedClass().getName());
347             field.setFieldConversions( generateFieldConversions( businessObject, collectionPrefix, relationship, field.getPropertyPrefix(), displayedFieldNames, nestedAttributePrefix ) );
348             field.setLookupParameters( generateLookupParameters( businessObject, collectionPrefix, relationship, field.getPropertyPrefix(), displayedFieldNames, nestedAttributePrefix ) );
349             field.setBaseLookupUrl(LookupUtils.getBaseLookupUrl(false));
350         } else {
351             field.setQuickFinderClassNameImpl(relationship.getRelatedClass().getName());
352             field.setFieldConversions( generateFieldConversions( businessObject, collectionPrefix, relationship, field.getPropertyPrefix(), displayedFieldNames, null ) );
353             field.setLookupParameters( generateLookupParameters( businessObject, collectionPrefix, relationship, field.getPropertyPrefix(), displayedFieldNames, null ) );
354             field.setBaseLookupUrl(LookupUtils.getBaseLookupUrl(false));
355         }
356         field.setImageSrc(getBusinessObjectDictionaryService().getSearchIconOverride(businessObject.getClass()));
357 
358         return field;
359     }
360 
361     private static String BASE_LOOKUP_ACTION_URL = null;
362     private static String BASE_MULTIPLE_VALUE_LOOKUP_ACTION_URL = null;
363     private static String BASE_INQUIRY_ACTION_URL = null;
364 
365     /**
366      * @see org.kuali.rice.krad.uif.util.LookupInquiryUtils#getBaseLookupUrl()
367      */
368     @Deprecated
369     public static String getBaseLookupUrl(boolean isMultipleValue) {
370         ConfigurationService kualiConfigurationService = KRADServiceLocator.getKualiConfigurationService();
371     	if ( isMultipleValue ) {
372     		if ( BASE_MULTIPLE_VALUE_LOOKUP_ACTION_URL == null ) {
373     			String lookupUrl = kualiConfigurationService.getPropertyValueAsString(KRADConstants.APPLICATION_URL_KEY);
374     			if (!lookupUrl.endsWith("/")) {
375     				lookupUrl = lookupUrl + "/";
376     			}
377 				lookupUrl += "kr/" + KRADConstants.MULTIPLE_VALUE_LOOKUP_ACTION;
378 				BASE_MULTIPLE_VALUE_LOOKUP_ACTION_URL = lookupUrl;
379     		}
380     		return BASE_MULTIPLE_VALUE_LOOKUP_ACTION_URL;
381     	} else {
382     		if ( BASE_LOOKUP_ACTION_URL == null ) {
383     			String lookupUrl = kualiConfigurationService.getPropertyValueAsString(KRADConstants.APPLICATION_URL_KEY);
384     			if (!lookupUrl.endsWith("/")) {
385     				lookupUrl = lookupUrl + "/";
386     			}
387 				lookupUrl += "kr/" + KRADConstants.LOOKUP_ACTION;
388 				BASE_LOOKUP_ACTION_URL = lookupUrl;
389     		}
390     		return BASE_LOOKUP_ACTION_URL;
391     	}
392     }
393 
394     @Deprecated
395     public static String getBaseInquiryUrl() {
396     	if ( BASE_INQUIRY_ACTION_URL == null ) {
397 	    	StringBuffer inquiryUrl = new StringBuffer( 
398 	    			KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(
399                             KRADConstants.APPLICATION_URL_KEY) );
400 			if (inquiryUrl.charAt(inquiryUrl.length()-1) != '/' ) {
401 				inquiryUrl.append( '/' );
402 			}
403 			inquiryUrl.append("kr/");
404 			inquiryUrl.append( KRADConstants.INQUIRY_ACTION );
405 			BASE_INQUIRY_ACTION_URL = inquiryUrl.toString();
406     	}
407     	return BASE_INQUIRY_ACTION_URL;
408     }
409 
410     public static String transformLookupUrlToMultiple(String lookupUrl) {
411     	return lookupUrl.replace("kr/" + KRADConstants.LOOKUP_ACTION, "kr/" + KRADConstants.MULTIPLE_VALUE_LOOKUP_ACTION);
412     }
413 
414     /**
415      * Sets whether a field should have direct inquiries enabled.  The direct inquiry is the functionality on a page such that if the primary key for
416      * a quickfinder is filled in and the direct inquiry button is pressed, then a new window will popup showing an inquiry page without going through
417      * the lookup first.
418      *
419      * For this method to work properly, it must be called after setFieldQuickfinder
420      * //TODO: chb: that should not be the case -- the relationship object the two rely upon should be established outside of the lookup/quickfinder code
421      *
422      *
423      * @param field
424      */
425     private static void setFieldDirectInquiry(Field field) {
426         if (StringUtils.isNotBlank(field.getFieldConversions())) {
427             boolean directInquiriesEnabled = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(
428                     KRADConstants.KNS_NAMESPACE, KRADConstants.DetailTypes.ALL_DETAIL_TYPE, KRADConstants.SystemGroupParameterNames.ENABLE_DIRECT_INQUIRIES_IND);
429             if (directInquiriesEnabled) {
430                 if (StringUtils.isNotBlank(field.getFieldConversions())) {
431                     String fieldConversions = field.getFieldConversions();
432                     String newInquiryParameters = KRADConstants.EMPTY_STRING;
433                     String[] conversions = StringUtils.split(fieldConversions, KRADConstants.FIELD_CONVERSIONS_SEPARATOR);
434 
435                     for (int l = 0; l < conversions.length; l++) {
436                         String conversion = conversions[l];
437                         //String[] conversionPair = StringUtils.split(conversion, KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR);
438                         String[] conversionPair = StringUtils.split(conversion, KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR, 2);
439                         String conversionFrom = conversionPair[0];
440                         String conversionTo = conversionPair[1];
441                         newInquiryParameters += (conversionTo + KRADConstants.FIELD_CONVERSION_PAIR_SEPARATOR + conversionFrom);
442 
443                         if (l < conversions.length - 1) {
444                             newInquiryParameters += KRADConstants.FIELD_CONVERSIONS_SEPARATOR;
445                         }
446                     }
447 
448                     field.setInquiryParameters(newInquiryParameters);
449                 }
450             }
451             field.setFieldDirectInquiryEnabled(directInquiriesEnabled);
452         }
453         else {
454             field.setFieldDirectInquiryEnabled(false);
455         }
456     }
457 
458     /**
459      *
460      * @param field
461      * @return the altered Field object
462      */
463     public static Field setFieldDirectInquiry(BusinessObject businessObject, String attributeName, Field field)
464     {
465 		if (businessObject == null)
466 		{
467             return field;
468         }
469 
470         Boolean noDirectInquiry = getBusinessObjectDictionaryService().noDirectInquiryFieldLookup(businessObject.getClass(), attributeName);
471         //check if noDirectInquiry is present and true, but if it's not set in existing data dictionary definitions, don't create a direct inquiry
472         if (noDirectInquiry != null && noDirectInquiry.booleanValue() || noDirectInquiry == null) {
473             return field;
474         }
475 
476         setFieldDirectInquiry(field);
477 
478         return field;
479     }
480 
481     private static Map<Class,Map<String,Map>> referencesForForeignKey = new HashMap<Class, Map<String,Map>>();
482 
483     @Deprecated
484     public static Map getPrimitiveReference(BusinessObject businessObject, String attributeName) {
485         Map chosenReferenceByKeySize = new HashMap();
486         Map chosenReferenceByFieldName = new HashMap();
487 
488         Map referenceClasses = null;
489 
490         try {
491             // add special caching of these relationships since the Spring caching is so expensive
492             Map<String,Map> propMap = referencesForForeignKey.get(businessObject.getClass());
493             if ( propMap == null ) {
494                 propMap = new HashMap<String, Map>();
495                 referencesForForeignKey.put(businessObject.getClass(), propMap);
496             }
497             if ( propMap.containsKey(attributeName) ) {
498                 referenceClasses = propMap.get( attributeName );
499             } else {
500             	//KFSMI-709: Make Inquiry Framework use BusinessObjectMetadataService instead of just PersistenceStructureService
501             	referenceClasses = getBusinessObjectMetaDataService().getReferencesForForeignKey(businessObject, attributeName);
502             	if(referenceClasses==null || referenceClasses.isEmpty()) {
503             	    if ( getPersistenceStructureService().isPersistable(businessObject.getClass()) ) {
504             	        referenceClasses = getPersistenceStructureService().getReferencesForForeignKey(businessObject.getClass(), attributeName);
505             	    }
506             	}
507                 propMap.put(attributeName, referenceClasses);
508             }
509         } catch ( ClassNotPersistableException ex ) {
510             // do nothing, there is no quickfinder
511             Map<String,Map> propMap = referencesForForeignKey.get(businessObject.getClass());
512             propMap.put(attributeName, null);
513         }
514 
515         // if field is not fk to any reference class, return field object w no quickfinder
516         if (referenceClasses == null || referenceClasses.isEmpty()) {
517             return chosenReferenceByKeySize;
518         }
519 
520         /*
521          * if field is fk to more than one reference, take the class with the least # of pk fields, this should give the correct
522          * grain for the attribute
523          */
524         int minKeys = Integer.MAX_VALUE;
525         for (Iterator iter = referenceClasses.keySet().iterator(); iter.hasNext();) {
526             String attr = (String) iter.next();
527             Class clazz = (Class) referenceClasses.get(attr);
528             List pkNames = getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(clazz);
529 
530             // Compare based on key size.
531             if (pkNames.size() < minKeys) {
532                 minKeys = pkNames.size();
533                 chosenReferenceByKeySize.clear();
534                 chosenReferenceByKeySize.put(attr, clazz);
535             }
536 
537             // Compare based on field name.
538             if (attributeName.startsWith(attr)) {
539                 chosenReferenceByFieldName.clear();
540                 chosenReferenceByFieldName.put(attr, clazz);
541             }
542         }
543 
544         // If a compatible key was found based on field names, prefer it, otherwise use choice by key size.
545         return chosenReferenceByFieldName.isEmpty() ? chosenReferenceByKeySize : chosenReferenceByFieldName;
546     }
547 
548     /**
549      *
550      * This method walks through the nested attribute and finds the last business object in the chain and returns it (excluding the
551      * last parameter which is the actual attribute)
552      *
553      * @param attributeName
554      * @return
555      */
556     public static BusinessObject getNestedBusinessObject(BusinessObject bo, String attributeName) {
557         String[] nestedAttributes = StringUtils.split(attributeName, ".");
558 
559         BusinessObject childBO = null;
560         String attributeRefName = "";
561         Class clazz = null;
562         if (nestedAttributes.length > 1) {
563             String attributeStringSoFar = "";
564             for (int i = 0; i < nestedAttributes.length - 1; i++) {
565                 // we need to build a string of the attribute names depending on which iteration we're in.
566                 // so if the original attributeName string we're using is "a.b.c.d.e", then first iteration would use
567                 // "a", 2nd "a.b", 3rd "a.b.c", etc.
568                 if (i != 0) {
569                     attributeStringSoFar = attributeStringSoFar + ".";
570                 }
571                 attributeStringSoFar = attributeStringSoFar + nestedAttributes[i];
572 
573                 clazz = ObjectUtils.getPropertyType( bo, attributeStringSoFar, getPersistenceStructureService() );
574 
575                 if (clazz != null && BusinessObject.class.isAssignableFrom(clazz)) {
576                     try {
577                     	childBO = (BusinessObject) ObjectUtils.createNewObjectFromClass(clazz);
578                     }
579                     catch (Exception e) {
580                         return null;
581                     }
582                 }
583             }
584         }
585         return childBO;
586     }
587 
588     public static Class getNestedReferenceClass(BusinessObject businessObject, String attributeName) {
589         BusinessObject bo = getNestedBusinessObject(businessObject, attributeName);
590         return null == bo ? null : bo.getClass();
591     }
592 
593     @Deprecated
594     private static String generateFieldConversions(BusinessObject businessObject, String collectionName, DataObjectRelationship relationship, String propertyPrefix, List displayedFieldNames, String nestedObjectPrefix) {
595         String fieldConversions = "";
596 
597         if ( LOG.isDebugEnabled() ) {
598             LOG.debug( "generateFieldConversions(" + businessObject.getClass().getName() + "," + collectionName + ",\n" + relationship + "\n," + propertyPrefix + "," + displayedFieldNames + "," + nestedObjectPrefix + ")" );
599         }
600 
601         // get the references for the given property
602         for ( Map.Entry<String,String> entry : relationship.getParentToChildReferences().entrySet() ) {
603             String fromField = entry.getValue();
604             String toField = entry.getKey();
605 
606             // find the displayed to field mapping
607             if (!displayedFieldNames.contains(toField)) {
608                 toField = translateToDisplayedField(businessObject.getClass(), toField, displayedFieldNames);
609             }
610 
611             if (StringUtils.isNotBlank(fieldConversions)) {
612                 fieldConversions += ",";
613             }
614 
615             if ( StringUtils.isNotEmpty( propertyPrefix ) ) {
616                 toField = propertyPrefix + "." + toField;
617             }
618 
619             if ( StringUtils.isNotEmpty( collectionName ) ) {
620                 toField = collectionName + toField;
621             }
622 
623             fieldConversions += fromField + ":" + toField;
624         }
625 
626         return fieldConversions;
627     }
628 
629     @Deprecated
630     private static String generateLookupParameters(BusinessObject businessObject, String collectionName, DataObjectRelationship relationship, String propertyPrefix, List displayedFieldNames, String nestedObjectPrefix) {
631 
632         String lookupParameters = "";
633 
634         List displayedQFFieldNames = getBusinessObjectDictionaryService().getLookupFieldNames(relationship.getRelatedClass());
635         for ( Map.Entry<String,String> entry : relationship.getParentToChildReferences().entrySet() ) {
636             String fromField = entry.getKey();
637             String toField = entry.getValue();
638 
639             if ( relationship.getUserVisibleIdentifierKey() == null || relationship.getUserVisibleIdentifierKey().equals( fromField ) ) {
640                 // find the displayed from field mapping
641                 if (!displayedFieldNames.contains(fromField)) {
642                     fromField = translateToDisplayedField(businessObject.getClass(), fromField, displayedFieldNames);
643                 }
644 
645                 // translate to field
646                 if (displayedQFFieldNames != null && !displayedQFFieldNames.contains(toField)) {
647                     toField = translateToDisplayedField(relationship.getRelatedClass(), toField, displayedQFFieldNames);
648                 }
649 
650                 if (StringUtils.isNotBlank(lookupParameters)) {
651                     lookupParameters += ",";
652                 }
653 
654                 if (propertyPrefix != null && !propertyPrefix.equals("")) {
655                     fromField = propertyPrefix + "." + fromField;
656                 }
657 
658                 if ( StringUtils.isNotEmpty( collectionName ) ) {
659                     fromField = collectionName + fromField;
660                 }
661 
662                 lookupParameters += fromField + ":" + toField;
663             }
664         }
665 
666         return lookupParameters;
667     }
668 
669     @Deprecated
670     private static String translateToDisplayedField(Class businessObjectClass, String fieldName, List displayedFieldNames) {        
671         if ( getPersistenceStructureService().isPersistable(businessObjectClass) ) {
672             Map nestedFkMap = getPersistenceStructureService().getNestedForeignKeyMap(businessObjectClass);
673 
674             // translate to primitive fk if nested
675             /*
676              * if (ObjectUtils.isNestedAttribute(fieldName) && nestedFkMap.containsKey(fieldName)) { fieldName = (String)
677              * nestedFkMap.get(fieldName); }
678              */
679 
680             if (!displayedFieldNames.contains(fieldName)) {
681                 for (Iterator iterator = displayedFieldNames.iterator(); iterator.hasNext();) {
682                     String dispField = (String) iterator.next();
683 
684                     if (nestedFkMap.containsKey(dispField) && nestedFkMap.get(dispField).equals(fieldName)) {
685                         fieldName = dispField;
686                     }
687                 }
688             }
689         }
690 
691         return fieldName;
692     }
693 
694     public static String convertReferencesToSelectCollectionToString(Collection<String> referencesToRefresh) {
695         StringBuilder buf = new StringBuilder();
696         for (String reference : referencesToRefresh) {
697             buf.append(reference).append(KRADConstants.REFERENCES_TO_REFRESH_SEPARATOR);
698         }
699         if (!referencesToRefresh.isEmpty()) {
700             // we appended one too many separators, remove it
701             buf.delete(buf.length() - KRADConstants.REFERENCES_TO_REFRESH_SEPARATOR.length(), buf.length());
702         }
703         return buf.toString();
704     }
705 
706     public static String convertSetOfObjectIdsToString(Set<String> objectIds) {
707         if (objectIds.isEmpty()) {
708             return "";
709         }
710         StringBuilder buf = new StringBuilder();
711         for (String objectId : objectIds) {
712             if (objectId.contains(KRADConstants.MULTIPLE_VALUE_LOOKUP_OBJ_IDS_SEPARATOR)) {
713                 throw new RuntimeException("object ID " + objectId + " contains the selected obj ID separator");
714             }
715             buf.append(objectId).append(KRADConstants.MULTIPLE_VALUE_LOOKUP_OBJ_IDS_SEPARATOR);
716         }
717         // added one extra separator, remove it
718         buf.delete(buf.length() - KRADConstants.MULTIPLE_VALUE_LOOKUP_OBJ_IDS_SEPARATOR.length(), buf.length());
719 
720         return buf.toString();
721     }
722 
723     public static Set<String> convertStringOfObjectIdsToSet(String objectIdsString) {
724         Set<String> set = new HashSet<String>();
725 
726         if (StringUtils.isNotBlank(objectIdsString)) {
727             String[] objectIds = StringUtils.splitByWholeSeparator(objectIdsString, KRADConstants.MULTIPLE_VALUE_LOOKUP_OBJ_IDS_SEPARATOR);
728             for (String objectId : objectIds) {
729                 set.add(objectId);
730             }
731         }
732         return set;
733     }
734 
735     /**
736      * Given a list of results from a lookup, determines the best comparator to use on the String values of each of these columns
737      *
738      * This method exists because each cell (represented by the Column object) lists the comparator that should be used within it based on the property value class,
739      * so we gotta go thru the whole list and determine the best comparator to use
740      *
741      * @param resultsTable
742      * @param column
743      * @return
744      */
745     public static Comparator findBestValueComparatorForColumn(List<ResultRow> resultTable, int column) {
746         // BIG HACK
747         Comparator comp = NullValueComparator.getInstance();
748         for (ResultRow row : resultTable) {
749             Comparator tempComp = row.getColumns().get(column).getValueComparator();
750             if (tempComp != null && !NullValueComparator.class.equals(tempComp.getClass())) {
751                 return tempComp;
752             }
753         }
754         return comp;
755     }
756     /**
757      * Changes ranged search fields like from/to dates into the range operators the lookupable dao expects
758      * ("..",">" etc) this method modifies the passed in map and returns a list containing only the modified fields
759      *
760      * This method does not handle document searchable attributes.  This is handled in a second pass by the docsearch-specific
761      * DocumentSearchCriteriaTranslator
762      */
763     public static Map<String, String> preProcessRangeFields(Map<String, String> lookupFormFields) {
764         Map<String, String> fieldsToUpdate = new HashMap<String, String>();
765         Set<String> fieldsForLookup = lookupFormFields.keySet();
766         for (String propName : fieldsForLookup) {
767             if (propName.startsWith(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX)) {
768                 String rangedLowerBoundValue = lookupFormFields.get(propName);
769                 String rangedFieldName = StringUtils.remove(propName, KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX);
770                 String rangedValue = lookupFormFields.get(rangedFieldName);
771 
772                 Range range = new Range();
773                 // defaults for general lookup/search
774                 range.setLowerBoundInclusive(true);
775                 range.setUpperBoundInclusive(true);
776                 range.setLowerBoundValue(rangedLowerBoundValue);
777                 range.setUpperBoundValue(rangedValue);
778 
779                  String expr = range.toString();
780                 if (StringUtils.isEmpty(expr)) {
781                     expr = rangedValue;
782                 }
783 
784                 fieldsToUpdate.put(rangedFieldName, expr);
785             }
786         }
787         //update lookup values from found ranged values to update
788         Set<String> keysToUpdate = fieldsToUpdate.keySet();
789         for (String updateKey : keysToUpdate) {
790             lookupFormFields.put(updateKey, fieldsToUpdate.get(updateKey));
791         }
792         return fieldsToUpdate;
793     }
794 
795     /**
796      * Given 3 sets of object IDs: the set of selected object IDs before rendering the current page,
797      * the set of object IDs rendered on the page, and the set of object IDs selected on the page, computes
798      * the total set of selected object IDs.
799      *
800      * Instead of storing it in a set, returns it in a map with the selected object ID as both the key and value
801      * @param previouslySelectedObjectIds
802      * @param displayedObjectIds
803      * @param selectedObjectIds
804      * @return
805      */
806     public static Map<String, String> generateCompositeSelectedObjectIds(Set<String> previouslySelectedObjectIds, Set<String> displayedObjectIds, Set<String> selectedObjectIds) {
807         Map<String, String> tempMap = new HashMap<String, String>();
808         // Equivalent to the set operation:
809         // (P - D) union C, where - is the set difference operator
810         // P is the list of object IDs previously passed in, D is the set of displayed object IDs, and C is the set of checked obj IDs
811         // since HTML does not pass a value for non-selected dcheckboxes
812 
813         // first build a map w/ all the previouslySelectedObjectIds as keys
814         for (String previouslySelectedObjectId : previouslySelectedObjectIds) {
815             tempMap.put(previouslySelectedObjectId, previouslySelectedObjectId);
816         }
817         // then remove all the displayed elements (any selected displayed elements will be added back in the next loop)
818         for (String displayedObjectId : displayedObjectIds) {
819             tempMap.remove(displayedObjectId);
820         }
821         // put back the selected IDs
822         for (String selectedObjectId : selectedObjectIds) {
823             tempMap.put(selectedObjectId, selectedObjectId);
824         }
825         return tempMap;
826     }
827 
828     public static DataDictionaryService getDataDictionaryService() {
829         return KRADServiceLocatorWeb.getDataDictionaryService();
830     }
831 
832     public static PersistenceStructureService getPersistenceStructureService() {
833         return KRADServiceLocator.getPersistenceStructureService();
834     }
835 
836     public static BusinessObjectDictionaryService getBusinessObjectDictionaryService() {
837         return KNSServiceLocator.getBusinessObjectDictionaryService();
838     }
839 
840     public static BusinessObjectMetaDataService getBusinessObjectMetaDataService() {
841         return KNSServiceLocator.getBusinessObjectMetaDataService();
842     }
843 
844     public static DateTimeService getDateTimeService() {
845         return CoreApiServiceLocator.getDateTimeService();
846     }
847 }