View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kns.lookup;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
20  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
21  import org.kuali.rice.core.api.CoreApiServiceLocator;
22  import org.kuali.rice.core.api.config.property.ConfigurationService;
23  import org.kuali.rice.core.api.encryption.EncryptionService;
24  import org.kuali.rice.core.api.search.SearchOperator;
25  import org.kuali.rice.core.api.util.RiceKeyConstants;
26  import org.kuali.rice.core.api.util.cache.CopiedObject;
27  import org.kuali.rice.core.api.util.type.TypeUtils;
28  import org.kuali.rice.core.web.format.DateFormatter;
29  import org.kuali.rice.core.web.format.Formatter;
30  import org.kuali.rice.kim.api.identity.Person;
31  import org.kuali.rice.kns.document.authorization.BusinessObjectRestrictions;
32  import org.kuali.rice.kns.document.authorization.FieldRestriction;
33  import org.kuali.rice.kns.inquiry.Inquirable;
34  import org.kuali.rice.kns.service.BusinessObjectAuthorizationService;
35  import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
36  import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
37  import org.kuali.rice.kns.service.KNSServiceLocator;
38  import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
39  import org.kuali.rice.kns.util.FieldUtils;
40  import org.kuali.rice.kns.web.comparator.CellComparatorHelper;
41  import org.kuali.rice.kns.web.struts.form.LookupForm;
42  import org.kuali.rice.kns.web.struts.form.MultipleValueLookupForm;
43  import org.kuali.rice.kns.web.ui.Column;
44  import org.kuali.rice.kns.web.ui.Field;
45  import org.kuali.rice.kns.web.ui.ResultRow;
46  import org.kuali.rice.kns.web.ui.Row;
47  import org.kuali.rice.krad.bo.BusinessObject;
48  import org.kuali.rice.krad.bo.PersistableBusinessObject;
49  import org.kuali.rice.krad.datadictionary.AttributeSecurity;
50  import org.kuali.rice.krad.datadictionary.mask.MaskFormatter;
51  import org.kuali.rice.krad.exception.ValidationException;
52  import org.kuali.rice.krad.service.BusinessObjectService;
53  import org.kuali.rice.krad.service.DataDictionaryService;
54  import org.kuali.rice.krad.service.KRADServiceLocator;
55  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
56  import org.kuali.rice.krad.service.LookupService;
57  import org.kuali.rice.krad.service.PersistenceStructureService;
58  import org.kuali.rice.krad.service.SequenceAccessorService;
59  import org.kuali.rice.krad.util.ExternalizableBusinessObjectUtils;
60  import org.kuali.rice.krad.util.GlobalVariables;
61  import org.kuali.rice.krad.util.KRADConstants;
62  import org.kuali.rice.krad.util.ObjectUtils;
63  import org.kuali.rice.krad.util.UrlFactory;
64  
65  import java.security.GeneralSecurityException;
66  import java.sql.Date;
67  import java.util.ArrayList;
68  import java.util.Collection;
69  import java.util.HashMap;
70  import java.util.HashSet;
71  import java.util.Iterator;
72  import java.util.List;
73  import java.util.Map;
74  import java.util.Properties;
75  import java.util.Set;
76  
77  /**
78   * This class declares many of the common spring injected properties, the get/set-ers for them,
79   * and some common util methods that require the injected services
80   */
81  public abstract class AbstractLookupableHelperServiceImpl implements LookupableHelperService {
82  
83      protected static final String TITLE_RETURN_URL_PREPENDTEXT_PROPERTY = "title.return.url.value.prependtext";
84      protected static final String TITLE_ACTION_URL_PREPENDTEXT_PROPERTY = "title.action.url.value.prependtext";
85      protected static final String ACTION_URLS_CHILDREN_SEPARATOR = " | ";
86      protected static final String ACTION_URLS_CHILDREN_STARTER = " [";
87      protected static final String ACTION_URLS_CHILDREN_END = "]";
88      protected static final String ACTION_URLS_SEPARATOR = "  ";
89      protected static final String ACTION_URLS_EMPTY = " ";
90  
91      protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AbstractLookupableHelperServiceImpl.class);
92  
93      protected Class businessObjectClass;
94      protected Map<String, String[]> parameters;
95      protected BusinessObjectDictionaryService businessObjectDictionaryService;
96      protected BusinessObjectMetaDataService businessObjectMetaDataService;
97      protected DataDictionaryService dataDictionaryService;
98      protected PersistenceStructureService persistenceStructureService;
99      protected EncryptionService encryptionService;
100     protected List<String> readOnlyFieldsList;
101     protected String backLocation;
102     protected String docFormKey;
103     protected Map fieldConversions;
104     protected LookupService lookupService;
105     protected List<Row> rows;
106     protected String referencesToRefresh;
107     protected SequenceAccessorService sequenceAccessorService;
108     protected BusinessObjectService businessObjectService;
109     protected LookupResultsService lookupResultsService;
110     protected String docNum;
111     protected ConfigurationService configurationService;
112     protected ParameterService parameterService;
113     protected BusinessObjectAuthorizationService businessObjectAuthorizationService;
114 
115     /**
116      * @return the docNum
117      */
118     public String getDocNum() {
119         return this.docNum;
120     }
121 
122     /**
123      * @param docNum the docNum to set
124      */
125     public void setDocNum(String docNum) {
126         this.docNum = docNum;
127     }
128 
129     public AbstractLookupableHelperServiceImpl() {
130         rows = null;
131     }
132 
133     /**
134      * This implementation always returns false.
135      *
136      * @see LookupableHelperService#checkForAdditionalFields(java.util.Map)
137      */
138     public boolean checkForAdditionalFields(Map<String, String> fieldValues) {
139         return false;
140     }
141 
142     /**
143      * @see LookupableHelperService#getBusinessObjectClass()
144      */
145     public Class getBusinessObjectClass() {
146         return businessObjectClass;
147     }
148 
149     /**
150      * @see LookupableHelperService#setBusinessObjectClass(java.lang.Class)
151      */
152     public void setBusinessObjectClass(Class businessObjectClass) {
153         this.businessObjectClass = businessObjectClass;
154         setRows();
155     }
156 
157     /**
158      * @see LookupableHelperService#getParameters()
159      */
160     public Map<String, String[]> getParameters() {
161         return parameters;
162     }
163 
164     /**
165      * @see LookupableHelperService#setParameters(java.util.Map)
166      */
167     public void setParameters(Map<String, String[]> parameters) {
168         this.parameters = parameters;
169     }
170 
171     /**
172      * Gets the dataDictionaryService attribute.
173      *
174      * @return Returns the dataDictionaryService.
175      */
176     public DataDictionaryService getDataDictionaryService() {
177         return dataDictionaryService != null ? dataDictionaryService : KRADServiceLocatorWeb.getDataDictionaryService();
178     }
179 
180     /**
181      * Sets the dataDictionaryService attribute value.
182      *
183      * @param dataDictionaryService The dataDictionaryService to set.
184      */
185     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
186         this.dataDictionaryService = dataDictionaryService;
187     }
188 
189     /**
190      * Gets the businessObjectDictionaryService attribute.
191      *
192      * @return Returns the businessObjectDictionaryService.
193      */
194     public BusinessObjectDictionaryService getBusinessObjectDictionaryService() {
195         return businessObjectDictionaryService != null ? businessObjectDictionaryService : KNSServiceLocator
196                 .getBusinessObjectDictionaryService();
197     }
198 
199     /**
200      * Sets the businessObjectDictionaryService attribute value.
201      *
202      * @param businessObjectDictionaryService
203      *         The businessObjectDictionaryService to set.
204      */
205     public void setBusinessObjectDictionaryService(BusinessObjectDictionaryService businessObjectDictionaryService) {
206         this.businessObjectDictionaryService = businessObjectDictionaryService;
207     }
208 
209     /**
210      * Gets the businessObjectMetaDataService attribute.
211      *
212      * @return Returns the businessObjectMetaDataService.
213      */
214     public BusinessObjectMetaDataService getBusinessObjectMetaDataService() {
215         return businessObjectMetaDataService != null ? businessObjectMetaDataService : KNSServiceLocator
216                 .getBusinessObjectMetaDataService();
217     }
218 
219     /**
220      * Sets the businessObjectMetaDataService attribute value.
221      *
222      * @param businessObjectMetaDataService The businessObjectMetaDataService to set.
223      */
224     public void setBusinessObjectMetaDataService(BusinessObjectMetaDataService businessObjectMetaDataService) {
225         this.businessObjectMetaDataService = businessObjectMetaDataService;
226     }
227 
228     /**
229      * Gets the persistenceStructureService attribute.
230      *
231      * @return Returns the persistenceStructureService.
232      */
233     protected PersistenceStructureService getPersistenceStructureService() {
234         return persistenceStructureService != null ? persistenceStructureService : KRADServiceLocator
235                 .getPersistenceStructureService();
236     }
237 
238     /**
239      * Sets the persistenceStructureService attribute value.
240      *
241      * @param persistenceStructureService The persistenceStructureService to set.
242      */
243     public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
244         this.persistenceStructureService = persistenceStructureService;
245     }
246 
247     /**
248      * Gets the encryptionService attribute.
249      *
250      * @return Returns the encryptionService.
251      */
252     protected EncryptionService getEncryptionService() {
253         return encryptionService != null ? encryptionService : CoreApiServiceLocator.getEncryptionService();
254     }
255 
256     /**
257      * Sets the encryptionService attribute value.
258      *
259      * @param encryptionService The encryptionService to set.
260      */
261     public void setEncryptionService(EncryptionService encryptionService) {
262         this.encryptionService = encryptionService;
263     }
264 
265     protected MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
266 
267     public MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() {
268         if (maintenanceDocumentDictionaryService == null) {
269             maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService();
270         }
271         return maintenanceDocumentDictionaryService;
272     }
273 
274 
275     public BusinessObjectAuthorizationService getBusinessObjectAuthorizationService() {
276         if (businessObjectAuthorizationService == null) {
277             businessObjectAuthorizationService = KNSServiceLocator.getBusinessObjectAuthorizationService();
278         }
279         return businessObjectAuthorizationService;
280     }
281 
282     protected Inquirable kualiInquirable;
283 
284     public Inquirable getKualiInquirable() {
285         if (kualiInquirable == null) {
286             kualiInquirable = KNSServiceLocator.getKualiInquirable();
287         }
288         return kualiInquirable;
289     }
290 
291     public void setMaintenanceDocumentDictionaryService(MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService) {
292         this.maintenanceDocumentDictionaryService = maintenanceDocumentDictionaryService;
293     }
294 
295     public void setKualiInquirable(Inquirable kualiInquirable) {
296         this.kualiInquirable = kualiInquirable;
297     }
298 
299 
300     public ConfigurationService getKualiConfigurationService() {
301         if (configurationService == null) {
302             configurationService = KRADServiceLocator.getKualiConfigurationService();
303         }
304         return configurationService;
305     }
306 
307     public void setParameterService(ConfigurationService configurationService) {
308         this.configurationService = configurationService;
309     }
310 
311 
312     public ParameterService getParameterService() {
313         if (parameterService == null) {
314             parameterService = CoreFrameworkServiceLocator.getParameterService();
315         }
316         return parameterService;
317     }
318 
319     public void setParameterService(ParameterService parameterService) {
320         this.parameterService = parameterService;
321     }
322 
323     /**
324      * Determines if underlying lookup bo has associated maintenance document that allows new or copy maintenance actions.
325      *
326      * @return true if bo has maint doc that allows new or copy actions
327      */
328     public boolean allowsMaintenanceNewOrCopyAction() {
329         boolean allowsNewOrCopy = false;
330 
331         String maintDocTypeName = getMaintenanceDocumentTypeName();
332         Class boClass = this.getBusinessObjectClass();
333 
334         if (StringUtils.isNotBlank(maintDocTypeName)) {
335             allowsNewOrCopy = getBusinessObjectAuthorizationService().canCreate(boClass, GlobalVariables.getUserSession().getPerson(), maintDocTypeName);
336         }
337         return allowsNewOrCopy;
338     }
339 
340     protected boolean allowsMaintenanceEditAction(BusinessObject businessObject) {
341         boolean allowsEdit = false;
342 
343         String maintDocTypeName = getMaintenanceDocumentTypeName();
344 
345         if (StringUtils.isNotBlank(maintDocTypeName)) {
346             allowsEdit = getBusinessObjectAuthorizationService().canMaintain(businessObject, GlobalVariables.getUserSession().getPerson(), maintDocTypeName);
347         }
348         return allowsEdit;
349     }
350 
351 
352     /**
353      * Build a maintenance url.
354      *
355      * @param bo           - business object representing the record for maint.
356      * @param methodToCall - maintenance action
357      * @return
358      */
359     final public String getMaintenanceUrl(BusinessObject businessObject, HtmlData htmlData, List pkNames, BusinessObjectRestrictions businessObjectRestrictions) {
360         htmlData.setTitle(getActionUrlTitleText(businessObject, htmlData.getDisplayText(), pkNames, businessObjectRestrictions));
361         return htmlData.constructCompleteHtmlTag();
362     }
363 
364     /**
365      * This method is called by performLookup method to generate action urls.
366      * It calls the method getCustomActionUrls to get html data, calls getMaintenanceUrl to get the actual html tag,
367      * and returns a formatted/concatenated string of action urls.
368      *
369      * @see LookupableHelperService#getActionUrls(org.kuali.rice.krad.bo.BusinessObject)
370      */
371     final public String getActionUrls(BusinessObject businessObject, List pkNames, BusinessObjectRestrictions businessObjectRestrictions) {
372         StringBuffer actions = new StringBuffer();
373         List<HtmlData> htmlDataList = getCustomActionUrls(businessObject, pkNames);
374         for (HtmlData htmlData : htmlDataList) {
375             actions.append(getMaintenanceUrl(businessObject, htmlData, pkNames, businessObjectRestrictions));
376             if (htmlData.getChildUrlDataList() != null) {
377                 if (htmlData.getChildUrlDataList().size() > 0) {
378                     actions.append(ACTION_URLS_CHILDREN_STARTER);
379                     for (HtmlData childURLData : htmlData.getChildUrlDataList()) {
380                         actions.append(getMaintenanceUrl(businessObject, childURLData, pkNames, businessObjectRestrictions));
381                         actions.append(ACTION_URLS_CHILDREN_SEPARATOR);
382                     }
383                     if (actions.toString().endsWith(ACTION_URLS_CHILDREN_SEPARATOR))
384                         actions.delete(actions.length() - ACTION_URLS_CHILDREN_SEPARATOR.length(), actions.length());
385                     actions.append(ACTION_URLS_CHILDREN_END);
386                 }
387             }
388             actions.append(ACTION_URLS_SEPARATOR);
389         }
390         if (actions.toString().endsWith(ACTION_URLS_SEPARATOR))
391             actions.delete(actions.length() - ACTION_URLS_SEPARATOR.length(), actions.length());
392         return actions.toString();
393     }
394 
395     /**
396      * Child classes should override this method if they want to return some other action urls.
397      *
398      * @returns This default implementation returns links to edit and copy maintenance action for
399      * the current maintenance record if the business object class has an associated maintenance document.
400      * Also checks value of allowsNewOrCopy in maintenance document xml before rendering the copy link.
401      * @see LookupableHelperService#getCustomActionUrls(org.kuali.rice.krad.bo.BusinessObject, java.util.List, java.util.List pkNames)
402      */
403     public List<HtmlData> getCustomActionUrls(BusinessObject businessObject, List pkNames) {
404         List<HtmlData> htmlDataList = new ArrayList<HtmlData>();
405         if (allowsMaintenanceEditAction(businessObject)) {
406             htmlDataList.add(getUrlData(businessObject, KRADConstants.MAINTENANCE_EDIT_METHOD_TO_CALL, pkNames));
407         }
408         if (allowsMaintenanceNewOrCopyAction()) {
409             htmlDataList.add(getUrlData(businessObject, KRADConstants.MAINTENANCE_COPY_METHOD_TO_CALL, pkNames));
410         }
411         if (allowsMaintenanceDeleteAction(businessObject)) {
412             htmlDataList.add(getUrlData(businessObject, KRADConstants.MAINTENANCE_DELETE_METHOD_TO_CALL, pkNames));
413         }
414         return htmlDataList;
415     }
416 
417     /**
418      * This method ...
419      * for KULRice 3070
420      *
421      * @return
422      */
423     protected boolean allowsMaintenanceDeleteAction(BusinessObject businessObject) {
424 
425         boolean allowsMaintain = false;
426         boolean allowsDelete = false;
427 
428         String maintDocTypeName = getMaintenanceDocumentTypeName();
429 
430         if (StringUtils.isNotBlank(maintDocTypeName)) {
431             allowsMaintain = getBusinessObjectAuthorizationService().canMaintain(businessObject, GlobalVariables.getUserSession().getPerson(), maintDocTypeName);
432         }
433 
434         allowsDelete = KNSServiceLocator.getMaintenanceDocumentDictionaryService().getAllowsRecordDeletion(businessObjectClass);
435 
436         return allowsDelete && allowsMaintain;
437     }
438 
439     /**
440      * This method constructs an AnchorHtmlData.
441      * This method can be overriden by child classes if they want to construct the html data in a different way.
442      * Foe example, if they want different type of html tag, like input/image.
443      *
444      * @param businessObject
445      * @param methodToCall
446      * @param displayText
447      * @param pkNames
448      * @return
449      */
450     protected HtmlData.AnchorHtmlData getUrlData(BusinessObject businessObject, String methodToCall, String displayText, List pkNames) {
451 
452         String href = getActionUrlHref(businessObject, methodToCall, pkNames);
453         //String title = StringUtils.isBlank(href)?"":getActionUrlTitleText(businessObject, displayText, pkNames);
454         HtmlData.AnchorHtmlData anchorHtmlData = new HtmlData.AnchorHtmlData(href, methodToCall, displayText);
455         return anchorHtmlData;
456     }
457 
458     /**
459      * This method calls its overloaded method with displayText as methodToCall
460      *
461      * @param businessObject
462      * @param methodToCall
463      * @param pkNames
464      * @return
465      */
466     protected HtmlData.AnchorHtmlData getUrlData(BusinessObject businessObject, String methodToCall, List pkNames) {
467         return getUrlData(businessObject, methodToCall, methodToCall, pkNames);
468     }
469 
470     /**
471      * A utility method that returns an empty list of action urls.
472      *
473      * @return
474      */
475     protected List<HtmlData> getEmptyActionUrls() {
476         return new ArrayList<HtmlData>();
477     }
478 
479     protected HtmlData getEmptyAnchorHtmlData() {
480         return new HtmlData.AnchorHtmlData();
481     }
482 
483     /**
484      * This method generates and returns href for the given parameters.
485      * This method can be overridden by child classes if they have to generate href differently.
486      * For example, refer to IntendedIncumbentLookupableHelperServiceImpl
487      *
488      * @param businessObject
489      * @param methodToCall
490      * @param pkNames
491      * @return
492      */
493     protected String getActionUrlHref(BusinessObject businessObject, String methodToCall, List pkNames) {
494         Properties parameters = new Properties();
495         parameters.put(KRADConstants.DISPATCH_REQUEST_PARAMETER, methodToCall);
496         // TODO: why is this not using the businessObject parmeter's class?
497         parameters.put(KRADConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE, businessObject.getClass().getName());
498         parameters.putAll(getParametersFromPrimaryKey(businessObject, pkNames));
499         if (StringUtils.isNotBlank(getReturnLocation())) {
500             parameters.put(KRADConstants.RETURN_LOCATION_PARAMETER, getReturnLocation());
501         }
502         return UrlFactory.parameterizeUrl(KRADConstants.MAINTENANCE_ACTION, parameters);
503     }
504 
505     protected Properties getParametersFromPrimaryKey(BusinessObject businessObject, List pkNames) {
506         Properties parameters = new Properties();
507         for (Iterator iter = pkNames.iterator(); iter.hasNext();) {
508             String fieldNm = (String) iter.next();
509 
510             Object fieldVal = ObjectUtils.getPropertyValue(businessObject, fieldNm);
511             if (fieldVal == null) {
512                 fieldVal = KRADConstants.EMPTY_STRING;
513             }
514             if (fieldVal instanceof java.sql.Date) {
515                 String formattedString = "";
516                 if (Formatter.findFormatter(fieldVal.getClass()) != null) {
517                     Formatter formatter = Formatter.getFormatter(fieldVal.getClass());
518                     formattedString = (String) formatter.format(fieldVal);
519                     fieldVal = formattedString;
520                 }
521             }
522 
523             // Encrypt value if it is a secure field
524             if (getBusinessObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, fieldNm)) {
525                 try {
526                     fieldVal = getEncryptionService().encrypt(fieldVal) + EncryptionService.ENCRYPTION_POST_PREFIX;
527                 } catch (GeneralSecurityException e) {
528                     LOG.error("Exception while trying to encrypted value for inquiry framework.", e);
529                     throw new RuntimeException(e);
530                 }
531 
532             }
533 
534             parameters.put(fieldNm, fieldVal.toString());
535         }
536         return parameters;
537     }
538 
539     /**
540      * This method generates and returns title text for action urls.
541      * Child classes can override this if they want to generate the title text differently.
542      * For example, refer to BatchJobStatusLookupableHelperServiceImpl
543      *
544      * @param businessObject
545      * @param displayText
546      * @param pkNames
547      * @return
548      */
549     protected String getActionUrlTitleText(BusinessObject businessObject, String displayText, List pkNames, BusinessObjectRestrictions businessObjectRestrictions) {
550         String prependTitleText = displayText + " "
551                 + getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(getBusinessObjectClass().getName()).getObjectLabel()
552                 + " "
553                 + this.getKualiConfigurationService().getPropertyValueAsString(TITLE_ACTION_URL_PREPENDTEXT_PROPERTY);
554         return HtmlData.getTitleText(prependTitleText, businessObject, pkNames, businessObjectRestrictions);
555     }
556 
557     /**
558      * Returns the maintenance document type associated with the business object class or null if one does not
559      * exist.
560      *
561      * @return String representing the maintenance document type name
562      */
563     protected String getMaintenanceDocumentTypeName() {
564         MaintenanceDocumentDictionaryService dd = getMaintenanceDocumentDictionaryService();
565         String maintDocTypeName = dd.getDocumentTypeName(getBusinessObjectClass());
566         return maintDocTypeName;
567     }
568 
569     /**
570      * Gets the readOnlyFieldsList attribute.
571      *
572      * @return Returns the readOnlyFieldsList.
573      */
574     public List<String> getReadOnlyFieldsList() {
575         return readOnlyFieldsList;
576     }
577 
578 
579     /**
580      * Sets the readOnlyFieldsList attribute value.
581      *
582      * @param readOnlyFieldsList The readOnlyFieldsList to set.
583      */
584     public void setReadOnlyFieldsList(List<String> readOnlyFieldsList) {
585         this.readOnlyFieldsList = readOnlyFieldsList;
586     }
587 
588     protected HashMap<String, Boolean> noLookupResultFieldInquiryCache = new HashMap<String, Boolean>();
589     protected HashMap<Class, Class> inquirableClassCache = new HashMap<Class, Class>();
590     protected HashMap<String, Boolean> forceLookupResultFieldInquiryCache = new HashMap<String, Boolean>();
591 
592     /**
593      * Returns the inquiry url for a field if one exist.
594      *
595      * @param bo           the business object instance to build the urls for
596      * @param propertyName the property which links to an inquirable
597      * @return String url to inquiry
598      */
599     public HtmlData getInquiryUrl(BusinessObject bo, String propertyName) {
600         HtmlData inquiryUrl = new HtmlData.AnchorHtmlData();
601 
602         String cacheKey = bo.getClass().getName() + "." + propertyName;
603         Boolean noLookupResultFieldInquiry = noLookupResultFieldInquiryCache.get(cacheKey);
604         if (noLookupResultFieldInquiry == null) {
605             noLookupResultFieldInquiry = getBusinessObjectDictionaryService().noLookupResultFieldInquiry(bo.getClass(), propertyName);
606             if (noLookupResultFieldInquiry == null) {
607                 noLookupResultFieldInquiry = Boolean.TRUE;
608             }
609             noLookupResultFieldInquiryCache.put(cacheKey, noLookupResultFieldInquiry);
610         }
611         if (!noLookupResultFieldInquiry) {
612 
613             Class<Inquirable> inquirableClass = inquirableClassCache.get(bo.getClass());
614             if (!inquirableClassCache.containsKey(bo.getClass())) {
615                 inquirableClass = getBusinessObjectDictionaryService().getInquirableClass(bo.getClass());
616                 inquirableClassCache.put(bo.getClass(), inquirableClass);
617             }
618             Inquirable inq = null;
619             try {
620                 if (inquirableClass != null) {
621                     inq = inquirableClass.newInstance();
622                 } else {
623                     inq = getKualiInquirable();
624                     if (LOG.isDebugEnabled()) {
625                         LOG.debug("Default Inquirable Class: " + inq.getClass());
626                     }
627                 }
628                 Boolean forceLookupResultFieldInquiry = forceLookupResultFieldInquiryCache.get(cacheKey);
629                 if (forceLookupResultFieldInquiry == null) {
630                     forceLookupResultFieldInquiry = getBusinessObjectDictionaryService().forceLookupResultFieldInquiry(bo.getClass(), propertyName);
631                     if (forceLookupResultFieldInquiry == null) {
632                         forceLookupResultFieldInquiry = Boolean.FALSE;
633                     }
634                     forceLookupResultFieldInquiryCache.put(cacheKey, forceLookupResultFieldInquiry);
635                 }
636                 inquiryUrl = inq.getInquiryUrl(bo, propertyName, forceLookupResultFieldInquiry);
637             } catch (Exception ex) {
638                 LOG.error("unable to create inquirable to get inquiry URL", ex);
639             }
640         }
641 
642         return inquiryUrl;
643     }
644 
645     protected CopiedObject<ArrayList<Column>> resultColumns = null;
646 
647     /**
648      * Constructs the list of columns for the search results. All properties for the column objects come from the DataDictionary.
649      */
650     public List<Column> getColumns() {
651         if (resultColumns == null) {
652             ArrayList<Column> columns = new ArrayList<Column>();
653             for (String attributeName : getBusinessObjectDictionaryService().getLookupResultFieldNames(getBusinessObjectClass())) {
654                 Column column = new Column();
655                 column.setPropertyName(attributeName);
656                 String columnTitle = getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), attributeName);
657                 Boolean useShortLabel = getBusinessObjectDictionaryService().getLookupResultFieldUseShortLabel(businessObjectClass, attributeName);
658                 if (useShortLabel != null && useShortLabel) {
659                     columnTitle = getDataDictionaryService().getAttributeShortLabel(getBusinessObjectClass(), attributeName);
660                 }
661                 if (StringUtils.isBlank(columnTitle)) {
662                     columnTitle = getDataDictionaryService().getCollectionLabel(getBusinessObjectClass(), attributeName);
663                 }
664                 column.setColumnTitle(columnTitle);
665                 column.setMaxLength(getColumnMaxLength(attributeName));
666 
667                 if (!businessObjectClass.isInterface()) {
668                     try {
669                         column.setFormatter(ObjectUtils.getFormatterWithDataDictionary(getBusinessObjectClass()
670                                 .newInstance(), attributeName));
671                     } catch (InstantiationException e) {
672                         LOG.info("Unable to get new instance of business object class: " + businessObjectClass.getName(), e);
673                         // just swallow exception and leave formatter blank
674                     } catch (IllegalAccessException e) {
675                         LOG.info("Unable to get new instance of business object class: " + businessObjectClass.getName(), e);
676                         // just swallow exception and leave formatter blank
677                     }
678                 }
679 
680                 String alternateDisplayPropertyName = getBusinessObjectDictionaryService()
681                         .getLookupFieldAlternateDisplayAttributeName(getBusinessObjectClass(), attributeName);
682                 if (StringUtils.isNotBlank(alternateDisplayPropertyName)) {
683                     column.setAlternateDisplayPropertyName(alternateDisplayPropertyName);
684                 }
685 
686                 String additionalDisplayPropertyName = getBusinessObjectDictionaryService()
687                         .getLookupFieldAdditionalDisplayAttributeName(getBusinessObjectClass(), attributeName);
688                 if (StringUtils.isNotBlank(additionalDisplayPropertyName)) {
689                     column.setAdditionalDisplayPropertyName(additionalDisplayPropertyName);
690                 } else {
691                     boolean translateCodes = getBusinessObjectDictionaryService().tranlateCodesInLookup(getBusinessObjectClass());
692                     if (translateCodes) {
693                         FieldUtils.setAdditionalDisplayPropertyForCodes(getBusinessObjectClass(), attributeName, column);
694                     }
695                 }
696 
697                 column.setTotal(getBusinessObjectDictionaryService().getLookupResultFieldTotal(getBusinessObjectClass(), attributeName));
698 
699                 columns.add(column);
700             }
701             resultColumns = ObjectUtils.deepCopyForCaching(columns);
702             return columns;
703         }
704         return resultColumns.getContent();
705     }
706 
707     protected static Integer RESULTS_DEFAULT_MAX_COLUMN_LENGTH = null;
708 
709     protected int getColumnMaxLength(String attributeName) {
710         Integer fieldDefinedMaxLength = getBusinessObjectDictionaryService().getLookupResultFieldMaxLength(getBusinessObjectClass(), attributeName);
711         if (fieldDefinedMaxLength == null) {
712             if (RESULTS_DEFAULT_MAX_COLUMN_LENGTH == null) {
713                 try {
714                     RESULTS_DEFAULT_MAX_COLUMN_LENGTH = Integer.valueOf(getParameterService().getParameterValueAsString(
715                             KRADConstants.KNS_NAMESPACE, KRADConstants.DetailTypes.LOOKUP_PARM_DETAIL_TYPE, KRADConstants.RESULTS_DEFAULT_MAX_COLUMN_LENGTH));
716                 } catch (NumberFormatException ex) {
717                     LOG.error("Lookup field max length parameter not found and unable to parse default set in system parameters (RESULTS_DEFAULT_MAX_COLUMN_LENGTH).");
718                 }
719             }
720             return RESULTS_DEFAULT_MAX_COLUMN_LENGTH.intValue();
721         }
722         return fieldDefinedMaxLength.intValue();
723     }
724 
725     /**
726      * @return Returns the backLocation.
727      */
728     public String getBackLocation() {
729         return backLocation;
730     }
731 
732     /**
733      * @param backLocation The backLocation to set.
734      */
735     public void setBackLocation(String backLocation) {
736         this.backLocation = backLocation;
737     }
738 
739     /**
740      * @see LookupableHelperService#getReturnLocation()
741      */
742     public String getReturnLocation() {
743         return backLocation;
744     }
745 
746     /**
747      * This method is for lookupable implementations
748      *
749      * @see LookupableHelperService#getReturnUrl(org.kuali.rice.krad.bo.BusinessObject, java.util.Map, java.lang.String, java.util.List)
750      */
751     final public HtmlData getReturnUrl(BusinessObject businessObject, Map fieldConversions, String lookupImpl, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) {
752         String href = getReturnHref(businessObject, fieldConversions, lookupImpl, returnKeys);
753         String returnUrlAnchorLabel =
754                 this.getKualiConfigurationService().getPropertyValueAsString(TITLE_RETURN_URL_PREPENDTEXT_PROPERTY);
755         HtmlData.AnchorHtmlData anchor = new HtmlData.AnchorHtmlData(href, HtmlData.getTitleText(returnUrlAnchorLabel, businessObject, returnKeys, businessObjectRestrictions));
756         anchor.setDisplayText(returnUrlAnchorLabel);
757         return anchor;
758     }
759 
760     /**
761      * This method is for lookupable implementations
762      *
763      * @param businessObject
764      * @param fieldConversions
765      * @param lookupImpl
766      * @param returnKeys
767      * @return
768      */
769     final protected String getReturnHref(BusinessObject businessObject, Map fieldConversions, String lookupImpl, List returnKeys) {
770         if (StringUtils.isNotBlank(backLocation)) {
771             return UrlFactory.parameterizeUrl(backLocation, getParameters(
772                     businessObject, fieldConversions, lookupImpl, returnKeys));
773         }
774         return "";
775     }
776 
777     /**
778      * @see LookupableHelperService#getReturnUrl(org.kuali.core.bo.BusinessObject, java.util.Map, java.lang.String)
779      */
780     public HtmlData getReturnUrl(BusinessObject businessObject, LookupForm lookupForm, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) {
781         Properties parameters = getParameters(
782                 businessObject, lookupForm.getFieldConversions(), lookupForm.getLookupableImplServiceName(), returnKeys);
783         if (StringUtils.isEmpty(lookupForm.getHtmlDataType()) || HtmlData.ANCHOR_HTML_DATA_TYPE.equals(lookupForm.getHtmlDataType()))
784             return getReturnAnchorHtmlData(businessObject, parameters, lookupForm, returnKeys, businessObjectRestrictions);
785         else
786             return getReturnInputHtmlData(businessObject, parameters, lookupForm, returnKeys, businessObjectRestrictions);
787     }
788 
789     protected HtmlData getReturnInputHtmlData(BusinessObject businessObject, Properties parameters, LookupForm lookupForm, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) {
790         String returnUrlAnchorLabel =
791                 this.getKualiConfigurationService().getPropertyValueAsString(TITLE_RETURN_URL_PREPENDTEXT_PROPERTY);
792         String name = KRADConstants.MULTIPLE_VALUE_LOOKUP_SELECTED_OBJ_ID_PARAM_PREFIX + lookupForm.getLookupObjectId();
793         HtmlData.InputHtmlData input = new HtmlData.InputHtmlData(name, HtmlData.InputHtmlData.CHECKBOX_INPUT_TYPE);
794         input.setTitle(HtmlData.getTitleText(returnUrlAnchorLabel, businessObject, returnKeys, businessObjectRestrictions));
795         if (((MultipleValueLookupForm) lookupForm).getCompositeObjectIdMap() == null ||
796                 ((MultipleValueLookupForm) lookupForm).getCompositeObjectIdMap().get(
797                         ((PersistableBusinessObject) businessObject).getObjectId()) == null) {
798             input.setChecked("");
799         } else {
800             input.setChecked(HtmlData.InputHtmlData.CHECKBOX_CHECKED_VALUE);
801         }
802         input.setValue(HtmlData.InputHtmlData.CHECKBOX_CHECKED_VALUE);
803         return input;
804     }
805 
806     protected HtmlData getReturnAnchorHtmlData(BusinessObject businessObject, Properties parameters, LookupForm lookupForm, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) {
807         String returnUrlAnchorLabel =
808                 this.getKualiConfigurationService().getPropertyValueAsString(TITLE_RETURN_URL_PREPENDTEXT_PROPERTY);
809         HtmlData.AnchorHtmlData anchor = new HtmlData.AnchorHtmlData(
810                 getReturnHref(parameters, lookupForm, returnKeys),
811                 HtmlData.getTitleText(returnUrlAnchorLabel, businessObject, returnKeys, businessObjectRestrictions));
812         anchor.setDisplayText(returnUrlAnchorLabel);
813         return anchor;
814     }
815 
816     protected String getReturnHref(Properties parameters, LookupForm lookupForm, List returnKeys) {
817         if (StringUtils.isNotBlank(backLocation)) {
818             String href = UrlFactory.parameterizeUrl(backLocation, parameters);
819             return addToReturnHref(href, lookupForm);
820         }
821         return "";
822     }
823 
824     protected String addToReturnHref(String href, LookupForm lookupForm) {
825         String lookupAnchor = "";
826         if (StringUtils.isNotEmpty(lookupForm.getAnchor())) {
827             lookupAnchor = lookupForm.getAnchor();
828         }
829         href += "&anchor=" + lookupAnchor + "&docNum=" + (StringUtils.isEmpty(getDocNum()) ? "" : getDocNum());
830         return href;
831     }
832 
833     protected Properties getParameters(BusinessObject bo, Map<String, String> fieldConversions, String lookupImpl, List returnKeys) {
834         Properties parameters = new Properties();
835         parameters.put(KRADConstants.DISPATCH_REQUEST_PARAMETER, KRADConstants.RETURN_METHOD_TO_CALL);
836         if (getDocFormKey() != null) {
837             parameters.put(KRADConstants.DOC_FORM_KEY, getDocFormKey());
838         }
839         if (lookupImpl != null) {
840             parameters.put(KRADConstants.REFRESH_CALLER, lookupImpl);
841         }
842         if (getDocNum() != null) {
843             parameters.put(KRADConstants.DOC_NUM, getDocNum());
844         }
845 
846         if (getReferencesToRefresh() != null) {
847             parameters.put(KRADConstants.REFERENCES_TO_REFRESH, getReferencesToRefresh());
848         }
849 
850         Iterator returnKeysIt = getReturnKeys().iterator();
851         while (returnKeysIt.hasNext()) {
852             String fieldNm = (String) returnKeysIt.next();
853 
854             Object fieldVal = ObjectUtils.getPropertyValue(bo, fieldNm);
855             if (fieldVal == null) {
856                 fieldVal = KRADConstants.EMPTY_STRING;
857             }
858 
859             if (getBusinessObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, fieldNm)) {
860                 try {
861                     fieldVal = getEncryptionService().encrypt(fieldVal) + EncryptionService.ENCRYPTION_POST_PREFIX;
862                 } catch (GeneralSecurityException e) {
863                     LOG.error("Exception while trying to encrypted value for inquiry framework.", e);
864                     throw new RuntimeException(e);
865                 }
866 
867             }
868 
869             //need to format date in url
870             if (fieldVal instanceof Date) {
871                 DateFormatter dateFormatter = new DateFormatter();
872                 fieldVal = dateFormatter.format(fieldVal);
873             }
874 
875             if (fieldConversions.containsKey(fieldNm)) {
876                 fieldNm = (String) fieldConversions.get(fieldNm);
877             }
878 
879             parameters.put(fieldNm, fieldVal.toString());
880         }
881 
882         return parameters;
883     }
884 
885     /**
886      * @return a List of the names of fields which are marked in data dictionary as return fields.
887      */
888     public List<String> getReturnKeys() {
889         List<String> returnKeys;
890         if (fieldConversions != null && !fieldConversions.isEmpty()) {
891             returnKeys = new ArrayList<String>(fieldConversions.keySet());
892         } else {
893             returnKeys = getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(getBusinessObjectClass());
894         }
895 
896         return returnKeys;
897     }
898 
899     /**
900      * Gets the docFormKey attribute.
901      *
902      * @return Returns the docFormKey.
903      */
904     public String getDocFormKey() {
905         return docFormKey;
906     }
907 
908     /**
909      * Sets the docFormKey attribute value.
910      *
911      * @param docFormKey The docFormKey to set.
912      */
913     public void setDocFormKey(String docFormKey) {
914         this.docFormKey = docFormKey;
915     }
916 
917     /**
918      * @see org.kuali.core.lookup.LookupableHelperService#setFieldConversions(java.util.Map)
919      */
920     public void setFieldConversions(Map fieldConversions) {
921         this.fieldConversions = fieldConversions;
922     }
923 
924     /**
925      * Gets the lookupService attribute.
926      *
927      * @return Returns the lookupService.
928      */
929     protected LookupService getLookupService() {
930         return lookupService != null ? lookupService : KRADServiceLocatorWeb.getLookupService();
931     }
932 
933     /**
934      * Sets the lookupService attribute value.
935      *
936      * @param lookupService The lookupService to set.
937      */
938     public void setLookupService(LookupService lookupService) {
939         this.lookupService = lookupService;
940     }
941 
942     /**
943      * Uses the DD to determine which is the default sort order.
944      *
945      * @return property names that will be used to sort on by default
946      */
947     public List<String> getDefaultSortColumns() {
948         return getBusinessObjectDictionaryService().getLookupDefaultSortFieldNames(getBusinessObjectClass());
949     }
950 
951     /**
952      * Checks that any required search fields have value.
953      *
954      * @see org.kuali.core.lookup.LookupableHelperService#validateSearchParameters(java.util.Map)
955      */
956     public void validateSearchParameters(Map<String, String> fieldValues) {
957         List<String> lookupFieldAttributeList = null;
958         if (getBusinessObjectMetaDataService().isLookupable(getBusinessObjectClass())) {
959             lookupFieldAttributeList = getBusinessObjectMetaDataService().getLookupableFieldNames(getBusinessObjectClass());
960         }
961         if (lookupFieldAttributeList == null) {
962             throw new RuntimeException("Lookup not defined for business object " + getBusinessObjectClass());
963         }
964         for (Iterator iter = lookupFieldAttributeList.iterator(); iter.hasNext();) {
965             String attributeName = (String) iter.next();
966             if (fieldValues.containsKey(attributeName)) {
967                 // get label of attribute for message
968                 String attributeLabel = getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), attributeName);
969 
970                 String attributeValue = (String) fieldValues.get(attributeName);
971 
972                 // check for required if field does not have value
973                 if (StringUtils.isBlank(attributeValue)) {
974                     if ((getBusinessObjectDictionaryService().getLookupAttributeRequired(getBusinessObjectClass(), attributeName)).booleanValue()) {
975                         GlobalVariables.getMessageMap().putError(attributeName, RiceKeyConstants.ERROR_REQUIRED, attributeLabel);
976                     }
977                 }
978                 validateSearchParameterWildcardAndOperators(attributeName, attributeValue);
979             }
980         }
981 
982         if (GlobalVariables.getMessageMap().hasErrors()) {
983             throw new ValidationException("errors in search criteria");
984         }
985     }
986 
987     protected void validateSearchParameterWildcardAndOperators(String attributeName, String attributeValue) {
988         if (StringUtils.isBlank(attributeValue))
989             return;
990 
991         // make sure a wildcard/operator is in the value
992         boolean found = false;
993         for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) {
994             String queryCharacter = op.op();
995 
996             if (attributeValue.contains(queryCharacter)) {
997                 found = true;
998             }
999         }
1000         if (!found)
1001             return;
1002 
1003         String attributeLabel = getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), attributeName);
1004         if (getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(businessObjectClass, attributeName)) {
1005             BusinessObject example = null;
1006             try {
1007                 example = (BusinessObject) businessObjectClass.newInstance();
1008             } catch (Exception e) {
1009                 LOG.error("Exception caught instantiating " + businessObjectClass.getName(), e);
1010                 throw new RuntimeException("Cannot instantiate " + businessObjectClass.getName(), e);
1011             }
1012 
1013             Class propertyType = ObjectUtils.getPropertyType(example, attributeName, getPersistenceStructureService());
1014             if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) || TypeUtils.isTemporalClass(propertyType)) {
1015                 GlobalVariables.getMessageMap().putError(attributeName, RiceKeyConstants.ERROR_WILDCARDS_AND_OPERATORS_NOT_ALLOWED_ON_FIELD, attributeLabel);
1016             }
1017             if (TypeUtils.isStringClass(propertyType)) {
1018                 GlobalVariables.getMessageMap().putInfo(attributeName, RiceKeyConstants.INFO_WILDCARDS_AND_OPERATORS_TREATED_LITERALLY, attributeLabel);
1019             }
1020         } else {
1021             if (getBusinessObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, attributeName)) {
1022                 if (!attributeValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) {
1023                     // encrypted values usually come from the DB, so we don't need to filter for wildcards
1024 
1025                     // wildcards are not allowed on restricted fields, because they are typically encrypted, and wildcard searches cannot be performed without
1026                     // decrypting every row, which is currently not supported by KNS
1027 
1028                     GlobalVariables.getMessageMap().putError(attributeName, RiceKeyConstants.ERROR_SECURE_FIELD, attributeLabel);
1029                 }
1030             }
1031         }
1032     }
1033 
1034     /**
1035      * Constructs the list of rows for the search fields. All properties for the field objects come
1036      * from the DataDictionary. To be called by setBusinessObject
1037      */
1038     protected void setRows() {
1039         List<String> lookupFieldAttributeList = null;
1040         if (getBusinessObjectMetaDataService().isLookupable(getBusinessObjectClass())) {
1041             lookupFieldAttributeList = getBusinessObjectMetaDataService().getLookupableFieldNames(
1042                     getBusinessObjectClass());
1043         }
1044         if (lookupFieldAttributeList == null) {
1045             throw new RuntimeException("Lookup not defined for business object " + getBusinessObjectClass());
1046         }
1047 
1048         // construct field object for each search attribute
1049         List fields = new ArrayList();
1050         try {
1051             fields = FieldUtils.createAndPopulateFieldsForLookup(lookupFieldAttributeList, getReadOnlyFieldsList(),
1052                     getBusinessObjectClass());
1053         } catch (InstantiationException e) {
1054             throw new RuntimeException("Unable to create instance of business object class" + e.getMessage());
1055         } catch (IllegalAccessException e) {
1056             throw new RuntimeException("Unable to create instance of business object class" + e.getMessage());
1057         }
1058 
1059         int numCols = getBusinessObjectDictionaryService().getLookupNumberOfColumns(this.getBusinessObjectClass());
1060 
1061         this.rows = FieldUtils.wrapFields(fields, numCols);
1062     }
1063 
1064     public List<Row> getRows() {
1065         return rows;
1066     }
1067 
1068     public abstract List<? extends BusinessObject> getSearchResults(Map<String, String> fieldValues);
1069 
1070     /**
1071      * This implementation of this method throws an UnsupportedOperationException, since not every implementation
1072      * may actually want to use this operation.  Subclasses desiring other behaviors
1073      * will need to override this.
1074      *
1075      * @see org.kuali.core.lookup.LookupableHelperService#getSearchResultsUnbounded(java.util.Map)
1076      */
1077     public List<? extends BusinessObject> getSearchResultsUnbounded(Map<String, String> fieldValues) {
1078         throw new UnsupportedOperationException("Lookupable helper services do not always support getSearchResultsUnbounded");
1079     }
1080 
1081     /**
1082      * Performs the lookup and returns a collection of lookup items
1083      *
1084      * @param lookupForm
1085      * @param resultTable
1086      * @param bounded
1087      * @return
1088      */
1089     public Collection<? extends BusinessObject> performLookup(LookupForm lookupForm, Collection<ResultRow> resultTable, boolean bounded) {
1090         Map lookupFormFields = lookupForm.getFieldsForLookup();
1091 
1092         setBackLocation((String) lookupForm.getFieldsForLookup().get(KRADConstants.BACK_LOCATION));
1093         setDocFormKey((String) lookupForm.getFieldsForLookup().get(KRADConstants.DOC_FORM_KEY));
1094         Collection<? extends BusinessObject> displayList;
1095 
1096         LookupUtils.preProcessRangeFields(lookupFormFields);
1097 
1098         Map fieldsForLookup = new HashMap(lookupForm.getFieldsForLookup());
1099         // call search method to get results
1100         if (bounded) {
1101             displayList = getSearchResults(lookupForm.getFieldsForLookup());
1102         } else {
1103             displayList = getSearchResultsUnbounded(lookupForm.getFieldsForLookup());
1104         }
1105 
1106         boolean hasReturnableRow = false;
1107 
1108         List returnKeys = getReturnKeys();
1109         List pkNames = getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(getBusinessObjectClass());
1110         Person user = GlobalVariables.getUserSession().getPerson();
1111 
1112         // iterate through result list and wrap rows with return url and action
1113         // urls
1114         for (Iterator iter = displayList.iterator(); iter.hasNext();) {
1115             BusinessObject element = (BusinessObject) iter.next();
1116             BusinessObject baseElement = element;
1117             //if ebo, then use base BO to get lookupId and find restrictions
1118             if (ExternalizableBusinessObjectUtils.isExternalizableBusinessObject(element.getClass())) {
1119                 try {
1120                     baseElement = (BusinessObject)this.getBusinessObjectClass().newInstance();
1121                 } catch (InstantiationException e) {
1122                     e.printStackTrace();
1123                 } catch (IllegalAccessException e) {
1124                     e.printStackTrace();
1125                 }
1126             }
1127 
1128             final String lookupId = KNSServiceLocator.getLookupResultsService().getLookupId(baseElement);
1129             if (lookupId != null) {
1130                 lookupForm.setLookupObjectId(lookupId);
1131             }
1132 
1133             BusinessObjectRestrictions businessObjectRestrictions = getBusinessObjectAuthorizationService()
1134                     .getLookupResultRestrictions(element, user);
1135 
1136             HtmlData returnUrl = getReturnUrl(element, lookupForm, returnKeys, businessObjectRestrictions);
1137             String actionUrls = getActionUrls(element, pkNames, businessObjectRestrictions);
1138             // Fix for JIRA - KFSMI-2417
1139             if ("".equals(actionUrls)) {
1140                 actionUrls = ACTION_URLS_EMPTY;
1141             }
1142 
1143             List<Column> columns = getColumns();
1144             for (Iterator iterator = columns.iterator(); iterator.hasNext();) {
1145                 Column col = (Column) iterator.next();
1146 
1147                 String propValue = ObjectUtils.getFormattedPropertyValue(element, col.getPropertyName(), col.getFormatter());
1148                 Class propClass = getPropertyClass(element, col.getPropertyName());
1149 
1150                 col.setComparator(CellComparatorHelper.getAppropriateComparatorForPropertyClass(propClass));
1151                 col.setValueComparator(CellComparatorHelper.getAppropriateValueComparatorForPropertyClass(propClass));
1152 
1153                 String propValueBeforePotientalMasking = propValue;
1154                 propValue = maskValueIfNecessary(element.getClass(), col.getPropertyName(), propValue,
1155                         businessObjectRestrictions);
1156                 col.setPropertyValue(propValue);
1157 
1158                 // if property value is masked, don't display additional or alternate properties, or allow totals
1159                 if (StringUtils.equals(propValueBeforePotientalMasking, propValue)) {
1160                     if (StringUtils.isNotBlank(col.getAlternateDisplayPropertyName())) {
1161                         String alternatePropertyValue = ObjectUtils.getFormattedPropertyValue(element, col
1162                                 .getAlternateDisplayPropertyName(), null);
1163                         col.setPropertyValue(alternatePropertyValue);
1164                     }
1165 
1166                     if (StringUtils.isNotBlank(col.getAdditionalDisplayPropertyName())) {
1167                         String additionalPropertyValue = ObjectUtils.getFormattedPropertyValue(element, col
1168                                 .getAdditionalDisplayPropertyName(), null);
1169                         col.setPropertyValue(col.getPropertyValue() + " *-* " + additionalPropertyValue);
1170                     }
1171                 } else {
1172                     col.setTotal(false);
1173                 }
1174 
1175                 if (col.isTotal()) {
1176                     Object unformattedPropValue = ObjectUtils.getPropertyValue(element, col.getPropertyName());
1177                     col.setUnformattedPropertyValue(unformattedPropValue);
1178                 }
1179 
1180                 if (StringUtils.isNotBlank(propValue)) {
1181                     col.setColumnAnchor(getInquiryUrl(element, col.getPropertyName()));
1182                 }
1183             }
1184 
1185             ResultRow row = new ResultRow(columns, returnUrl.constructCompleteHtmlTag(), actionUrls);
1186             row.setRowId(returnUrl.getName());
1187             row.setReturnUrlHtmlData(returnUrl);
1188 
1189             // because of concerns of the BO being cached in session on the
1190             // ResultRow,
1191             // let's only attach it when needed (currently in the case of
1192             // export)
1193             if (getBusinessObjectDictionaryService().isExportable(getBusinessObjectClass())) {
1194                 row.setBusinessObject(element);
1195             }
1196 
1197             if (lookupId != null) {
1198                 row.setObjectId(lookupId);
1199             }
1200 
1201             boolean rowReturnable = isResultReturnable(element);
1202             row.setRowReturnable(rowReturnable);
1203             if (rowReturnable) {
1204                 hasReturnableRow = true;
1205             }
1206             resultTable.add(row);
1207         }
1208 
1209         lookupForm.setHasReturnableRow(hasReturnableRow);
1210 
1211         return displayList;
1212     }
1213 
1214     /**
1215      * Gets the Class for the property in the given BusinessObject instance, if
1216      * property is not accessible then runtime exception is thrown
1217      *
1218      * @param element      BusinessObject instance that contains property
1219      * @param propertyName Name of property in BusinessObject to get class for
1220      * @return Type for property as Class
1221      */
1222     protected Class getPropertyClass(BusinessObject element, String propertyName) {
1223         Class propClass = null;
1224 
1225         try {
1226             propClass = ObjectUtils.getPropertyType(element, propertyName, getPersistenceStructureService());
1227 
1228         } catch (Exception e) {
1229             throw new RuntimeException("Cannot access PropertyType for property " + "'" + propertyName + "' "
1230                     + " on an instance of '" + element.getClass().getName() + "'.", e);
1231         }
1232 
1233         return propClass;
1234     }
1235 
1236 
1237 
1238     protected String maskValueIfNecessary(Class businessObjectClass, String propertyName, String propertyValue, BusinessObjectRestrictions businessObjectRestrictions) {
1239         String maskedPropertyValue = propertyValue;
1240         if (businessObjectRestrictions != null) {
1241             FieldRestriction fieldRestriction = businessObjectRestrictions.getFieldRestriction(propertyName);
1242             if (fieldRestriction != null && (fieldRestriction.isMasked() || fieldRestriction.isPartiallyMasked())) {
1243                 maskedPropertyValue = fieldRestriction.getMaskFormatter().maskValue(propertyValue);
1244             }
1245         }
1246         return maskedPropertyValue;
1247     }
1248 
1249 
1250     protected void setReferencesToRefresh(String referencesToRefresh) {
1251         this.referencesToRefresh = referencesToRefresh;
1252     }
1253 
1254     public String getReferencesToRefresh() {
1255         return referencesToRefresh;
1256     }
1257 
1258     protected SequenceAccessorService getSequenceAccessorService() {
1259         return sequenceAccessorService != null ? sequenceAccessorService : KRADServiceLocator
1260                 .getSequenceAccessorService();
1261     }
1262 
1263     public void setSequenceAccessorService(SequenceAccessorService sequenceAccessorService) {
1264         this.sequenceAccessorService = sequenceAccessorService;
1265     }
1266 
1267     public BusinessObjectService getBusinessObjectService() {
1268         return businessObjectService != null ? businessObjectService : KRADServiceLocator.getBusinessObjectService();
1269     }
1270 
1271     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
1272         this.businessObjectService = businessObjectService;
1273     }
1274 
1275     protected LookupResultsService getLookupResultsService() {
1276         return lookupResultsService != null ? lookupResultsService : KNSServiceLocator.getLookupResultsService();
1277     }
1278 
1279     public void setLookupResultsService(LookupResultsService lookupResultsService) {
1280         this.lookupResultsService = lookupResultsService;
1281     }
1282 
1283     /**
1284      * @return false always, subclasses should override to do something smarter
1285      * @see org.kuali.core.lookup.LookupableHelperService#isSearchUsingOnlyPrimaryKeyValues()
1286      */
1287     public boolean isSearchUsingOnlyPrimaryKeyValues() {
1288         // by default, this implementation returns false, as lookups may not necessarily support this
1289         return false;
1290     }
1291 
1292     /**
1293      * Returns "N/A"
1294      *
1295      * @return "N/A"
1296      * @see org.kuali.core.lookup.LookupableHelperService#getPrimaryKeyFieldLabels()
1297      */
1298     public String getPrimaryKeyFieldLabels() {
1299         return KRADConstants.NOT_AVAILABLE_STRING;
1300     }
1301 
1302     /**
1303      * @see org.kuali.core.lookup.LookupableHelperService#isResultReturnable(org.kuali.core.bo.BusinessObject)
1304      */
1305     public boolean isResultReturnable(BusinessObject object) {
1306         return true;
1307     }
1308 
1309     /**
1310      * This method does the logic for the clear action.
1311      *
1312      * @see LookupableHelperService#performClear()
1313      */
1314     public void performClear(LookupForm lookupForm) {
1315         for (Iterator iter = this.getRows().iterator(); iter.hasNext();) {
1316             Row row = (Row) iter.next();
1317             for (Iterator iterator = row.getFields().iterator(); iterator.hasNext();) {
1318                 Field field = (Field) iterator.next();
1319                 if (field.isSecure()) {
1320                     field.setSecure(false);
1321                     field.setDisplayMaskValue(null);
1322                     field.setEncryptedValue(null);
1323                 }
1324 
1325                 if (!field.getFieldType().equals(Field.RADIO)) {
1326                     field.setPropertyValue(field.getDefaultValue());
1327                 }
1328             }
1329         }
1330     }
1331 
1332     /**
1333      * @see LookupableHelperService#shouldDisplayHeaderNonMaintActions()
1334      */
1335     public boolean shouldDisplayHeaderNonMaintActions() {
1336         return true;
1337     }
1338 
1339     /**
1340      * @see LookupableHelperService#shouldDisplayLookupCriteria()
1341      */
1342     public boolean shouldDisplayLookupCriteria() {
1343         return true;
1344     }
1345 
1346     /**
1347      * @see LookupableHelperService#getSupplementalMenuBar()
1348      */
1349     public String getSupplementalMenuBar() {
1350         return new String();
1351     }
1352 
1353     /**
1354      * @see LookupableHelperService#getTitle()
1355      */
1356     public String getTitle() {
1357         return getBusinessObjectDictionaryService().getLookupTitle(getBusinessObjectClass());
1358     }
1359 
1360     /**
1361      * @see LookupableHelperService#performCustomAction(boolean)
1362      */
1363     public boolean performCustomAction(boolean ignoreErrors) {
1364         return false;
1365     }
1366 
1367     /**
1368      * @see Lookupable#getExtraField()
1369      */
1370     public Field getExtraField() {
1371         return null;
1372     }
1373 
1374     public boolean allowsNewOrCopyAction(String documentTypeName) {
1375         throw new UnsupportedOperationException("Function not supported.");
1376     }
1377 
1378     /**
1379      * Functional requirements state that users are able to perform searches using criteria values that they are not allowed to view.
1380      *
1381      * @see LookupableHelperService#applyFieldAuthorizationsFromNestedLookups(org.kuali.rice.krad.web.ui.Field)
1382      */
1383     public void applyFieldAuthorizationsFromNestedLookups(Field field) {
1384         BusinessObjectAuthorizationService boAuthzService = this.getBusinessObjectAuthorizationService();
1385         if (!Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType())) {
1386             if (field.getPropertyValue() != null && field.getPropertyValue().endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) {
1387                 if (boAuthzService.attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, field.getPropertyName())) {
1388                     AttributeSecurity attributeSecurity = getDataDictionaryService().getAttributeSecurity(businessObjectClass.getName(), field.getPropertyName());
1389                     Person user = GlobalVariables.getUserSession().getPerson();
1390                     String decryptedValue;
1391                     try {
1392                         String cipherText = StringUtils.removeEnd(field.getPropertyValue(), EncryptionService.ENCRYPTION_POST_PREFIX);
1393                         decryptedValue = getEncryptionService().decrypt(cipherText);
1394                     } catch (GeneralSecurityException e) {
1395                         throw new RuntimeException("Error decrypting value for business object " + businessObjectClass + " attribute " + field.getPropertyName(), e);
1396                     }
1397                     if (attributeSecurity.isMask() && !boAuthzService.canFullyUnmaskField(user,
1398                             businessObjectClass, field.getPropertyName(), null)) {
1399                         MaskFormatter maskFormatter = attributeSecurity.getMaskFormatter();
1400                         field.setEncryptedValue(field.getPropertyValue());
1401                         field.setDisplayMaskValue(maskFormatter.maskValue(decryptedValue));
1402                         field.setSecure(true);
1403                     } else if (attributeSecurity.isPartialMask() && !boAuthzService.canPartiallyUnmaskField(user,
1404                             businessObjectClass, field.getPropertyName(), null)) {
1405                         MaskFormatter maskFormatter = attributeSecurity.getPartialMaskFormatter();
1406                         field.setEncryptedValue(field.getPropertyValue());
1407                         field.setDisplayMaskValue(maskFormatter.maskValue(decryptedValue));
1408                         field.setSecure(true);
1409                     } else {
1410                         field.setPropertyValue(org.kuali.rice.krad.lookup.LookupUtils
1411                                 .forceUppercase(businessObjectClass, field.getPropertyName(), decryptedValue));
1412                     }
1413                 } else {
1414                     throw new RuntimeException("Field " + field.getPersonNameAttributeName() + " was encrypted on " + businessObjectClass.getName() +
1415                             " lookup was encrypted when it should not have been encrypted according to the data dictionary.");
1416                 }
1417             }
1418         } else {
1419             if (boAuthzService.attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, field.getPropertyName())) {
1420                 LOG.error("Cannot handle multiple value field types that have field authorizations, please implement custom lookupable helper service");
1421                 throw new RuntimeException("Cannot handle multiple value field types that have field authorizations.");
1422             }
1423         }
1424     }
1425 
1426     /**
1427      * Calls methods that can be overridden by child lookupables to implement conditional logic for setting
1428      * read-only, required, and hidden attributes. Called in the last part of the lookup lifecycle so the
1429      * fields values that will be sent will be correctly reflected in the rows (like after a clear).
1430      *
1431      * @see #getConditionallyReadOnlyPropertyNames()
1432      * @see #getConditionallyRequiredPropertyNames()
1433      * @see #getConditionallyHiddenPropertyNames()
1434      * @see LookupableHelperService#applyConditionalLogicForFieldDisplay()
1435      */
1436     public void applyConditionalLogicForFieldDisplay() {
1437         Set<String> readOnlyFields = getConditionallyReadOnlyPropertyNames();
1438         Set<String> requiredFields = getConditionallyRequiredPropertyNames();
1439         Set<String> hiddenFields = getConditionallyHiddenPropertyNames();
1440 
1441         for (Iterator iter = this.getRows().iterator(); iter.hasNext();) {
1442             Row row = (Row) iter.next();
1443             for (Iterator iterator = row.getFields().iterator(); iterator.hasNext();) {
1444                 Field field = (Field) iterator.next();
1445 
1446                 if (readOnlyFields != null && readOnlyFields.contains(field.getPropertyName())) {
1447                     field.setReadOnly(true);
1448                 }
1449 
1450                 if (requiredFields != null && requiredFields.contains(field.getPropertyName())) {
1451                     field.setFieldRequired(true);
1452                 }
1453 
1454                 if (hiddenFields != null && hiddenFields.contains(field.getPropertyName())) {
1455                     field.setFieldType(Field.HIDDEN);
1456                 }
1457             }
1458         }
1459     }
1460 
1461     /**
1462      * @return Set of property names that should be set as read only based on the current search
1463      *         contents, note request parms containing search field values can be retrieved with
1464      *         {@link #getParameters()}
1465      */
1466     public Set<String> getConditionallyReadOnlyPropertyNames() {
1467         return new HashSet<String>();
1468     }
1469 
1470     /**
1471      * @return Set of property names that should be set as required based on the current search
1472      *         contents, note request parms containing search field values can be retrieved with
1473      *         {@link #getParameters()}
1474      */
1475     public Set<String> getConditionallyRequiredPropertyNames() {
1476         return new HashSet<String>();
1477     }
1478 
1479     /**
1480      * @return Set of property names that should be set as hidden based on the current search
1481      *         contents, note request parms containing search field values can be retrieved with
1482      *         {@link #getParameters()}
1483      */
1484     public Set<String> getConditionallyHiddenPropertyNames() {
1485         return new HashSet<String>();
1486     }
1487 
1488     /**
1489      * Helper method to get the value for a property out of the row-field graph. If property is
1490      * multi-value then the values will be joined by a semi-colon.
1491      *
1492      * @param propertyName - name of property to retrieve value for
1493      * @return current property value as a String
1494      */
1495     protected String getCurrentSearchFieldValue(String propertyName) {
1496         String currentValue = null;
1497 
1498         boolean fieldFound = false;
1499         for (Iterator iter = this.getRows().iterator(); iter.hasNext();) {
1500             Row row = (Row) iter.next();
1501             for (Iterator iterator = row.getFields().iterator(); iterator.hasNext();) {
1502                 Field field = (Field) iterator.next();
1503 
1504                 if (StringUtils.equalsIgnoreCase(propertyName, field.getPropertyName())) {
1505                     if (Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType())) {
1506                         currentValue = StringUtils.join(field.getPropertyValues(), ";");
1507                     } else {
1508                         currentValue = field.getPropertyValue();
1509                     }
1510                     fieldFound = true;
1511                 }
1512 
1513                 if (fieldFound) {
1514                     break;
1515                 }
1516             }
1517 
1518             if (fieldFound) {
1519                 break;
1520             }
1521         }
1522 
1523         return currentValue;
1524     }
1525 }