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