View Javadoc

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