001    /**
002     * Copyright 2005-2014 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.kns.lookup;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.apache.ojb.broker.query.Criteria;
020    import org.kuali.rice.core.api.CoreApiServiceLocator;
021    import org.kuali.rice.core.api.config.property.ConfigurationService;
022    import org.kuali.rice.core.api.datetime.DateTimeService;
023    import org.kuali.rice.core.api.search.Range;
024    import org.kuali.rice.core.framework.persistence.platform.DatabasePlatform;
025    import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
026    import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
027    import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
028    import org.kuali.rice.kns.service.KNSServiceLocator;
029    import org.kuali.rice.kns.util.KNSGlobalVariables;
030    import org.kuali.rice.kns.web.comparator.NullValueComparator;
031    import org.kuali.rice.kns.web.struts.form.KualiForm;
032    import org.kuali.rice.kns.web.struts.form.LookupForm;
033    import org.kuali.rice.kns.web.ui.Field;
034    import org.kuali.rice.kns.web.ui.ResultRow;
035    import org.kuali.rice.krad.bo.BusinessObject;
036    import org.kuali.rice.krad.bo.DataObjectRelationship;
037    import org.kuali.rice.krad.datadictionary.RelationshipDefinition;
038    import org.kuali.rice.krad.datadictionary.control.ControlDefinition;
039    import org.kuali.rice.krad.exception.ClassNotPersistableException;
040    import org.kuali.rice.krad.service.DataDictionaryService;
041    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
042    import org.kuali.rice.krad.service.PersistenceStructureService;
043    import org.kuali.rice.krad.util.KRADConstants;
044    import org.kuali.rice.krad.util.ObjectUtils;
045    
046    import java.util.ArrayList;
047    import java.util.Collection;
048    import java.util.Comparator;
049    import java.util.HashMap;
050    import java.util.HashSet;
051    import java.util.Iterator;
052    import java.util.List;
053    import java.util.Map;
054    import java.util.Set;
055    import java.util.StringTokenizer;
056    
057    /**
058     * Utility class for Lookup related utilities and helper methods.
059     *
060     * @deprecated Only used in KNS classes, use KRAD.
061     */
062    @Deprecated
063    public class LookupUtils {
064        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LookupUtils.class);
065    
066        public LookupUtils() {
067            // default constructor for Spring to call to start up initialization process
068        }
069    
070        /**
071         * Removes fields identified in the data dictionary as hidden from the lookup field values.
072         * (This will remove Universal User ID and Person name from search requests when a user ID is entered.)
073         *
074         * @param fieldValues
075         */
076        public static void removeHiddenCriteriaFields(Class businessObjectClass, Map fieldValues) {
077            List<String> lookupFieldAttributeList =
078                    getBusinessObjectMetaDataService().getLookupableFieldNames(businessObjectClass);
079            if (lookupFieldAttributeList != null) {
080                for (Iterator iter = lookupFieldAttributeList.iterator(); iter.hasNext(); ) {
081                    String attributeName = (String) iter.next();
082                    if (fieldValues.containsKey(attributeName)) {
083                        ControlDefinition controlDef = getDataDictionaryService()
084                                .getAttributeControlDefinition(businessObjectClass, attributeName);
085                        if (controlDef != null && controlDef.isHidden()) {
086                            fieldValues.remove(attributeName);
087                        }
088                    }
089                }
090            }
091        }
092    
093        /**
094         * Parses and returns the lookup result set limit, checking first for the limit
095         * for the BO being looked up, and then the global application limit if there isn't a limit
096         * specific to this BO.
097         *
098         * @param businessObjectClass BO class to search on / get limit for.  If the passed in type is not of type
099         * {@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(BusinessObject 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(BusinessObject 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(BusinessObject 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(BusinessObject 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(BusinessObject 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    }