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