View Javadoc

1   /**
2    * Copyright 2005-2013 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                     if(CoreApiServiceLocator.getEncryptionService().isEnabled()) {
527                         fieldVal = getEncryptionService().encrypt(fieldVal) + EncryptionService.ENCRYPTION_POST_PREFIX;
528                     }
529                 } catch (GeneralSecurityException e) {
530                     LOG.error("Exception while trying to encrypted value for inquiry framework.", e);
531                     throw new RuntimeException(e);
532                 }
533 
534             }
535 
536             parameters.put(fieldNm, fieldVal.toString());
537         }
538         return parameters;
539     }
540 
541     /**
542      * This method generates and returns title text for action urls.
543      * Child classes can override this if they want to generate the title text differently.
544      * For example, refer to BatchJobStatusLookupableHelperServiceImpl
545      *
546      * @param businessObject
547      * @param displayText
548      * @param pkNames
549      * @return
550      */
551     protected String getActionUrlTitleText(BusinessObject businessObject, String displayText, List pkNames, BusinessObjectRestrictions businessObjectRestrictions) {
552         String prependTitleText = displayText + " "
553                 + getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(getBusinessObjectClass().getName()).getObjectLabel()
554                 + " "
555                 + this.getKualiConfigurationService().getPropertyValueAsString(TITLE_ACTION_URL_PREPENDTEXT_PROPERTY);
556         return HtmlData.getTitleText(prependTitleText, businessObject, pkNames, businessObjectRestrictions);
557     }
558 
559     /**
560      * Returns the maintenance document type associated with the business object class or null if one does not
561      * exist.
562      *
563      * @return String representing the maintenance document type name
564      */
565     protected String getMaintenanceDocumentTypeName() {
566         MaintenanceDocumentDictionaryService dd = getMaintenanceDocumentDictionaryService();
567         String maintDocTypeName = dd.getDocumentTypeName(getBusinessObjectClass());
568         return maintDocTypeName;
569     }
570 
571     /**
572      * Gets the readOnlyFieldsList attribute.
573      *
574      * @return Returns the readOnlyFieldsList.
575      */
576     public List<String> getReadOnlyFieldsList() {
577         return readOnlyFieldsList;
578     }
579 
580 
581     /**
582      * Sets the readOnlyFieldsList attribute value.
583      *
584      * @param readOnlyFieldsList The readOnlyFieldsList to set.
585      */
586     public void setReadOnlyFieldsList(List<String> readOnlyFieldsList) {
587         this.readOnlyFieldsList = readOnlyFieldsList;
588     }
589 
590     protected HashMap<String, Boolean> noLookupResultFieldInquiryCache = new HashMap<String, Boolean>();
591     protected HashMap<Class, Class> inquirableClassCache = new HashMap<Class, Class>();
592     protected HashMap<String, Boolean> forceLookupResultFieldInquiryCache = new HashMap<String, Boolean>();
593 
594     /**
595      * Returns the inquiry url for a field if one exist.
596      *
597      * @param bo           the business object instance to build the urls for
598      * @param propertyName the property which links to an inquirable
599      * @return String url to inquiry
600      */
601     public HtmlData getInquiryUrl(BusinessObject bo, String propertyName) {
602         HtmlData inquiryUrl = new HtmlData.AnchorHtmlData();
603 
604         String cacheKey = bo.getClass().getName() + "." + propertyName;
605         Boolean noLookupResultFieldInquiry = noLookupResultFieldInquiryCache.get(cacheKey);
606         if (noLookupResultFieldInquiry == null) {
607             noLookupResultFieldInquiry = getBusinessObjectDictionaryService().noLookupResultFieldInquiry(bo.getClass(), propertyName);
608             if (noLookupResultFieldInquiry == null) {
609                 noLookupResultFieldInquiry = Boolean.TRUE;
610             }
611             noLookupResultFieldInquiryCache.put(cacheKey, noLookupResultFieldInquiry);
612         }
613         if (!noLookupResultFieldInquiry) {
614 
615             Class<Inquirable> inquirableClass = inquirableClassCache.get(bo.getClass());
616             if (!inquirableClassCache.containsKey(bo.getClass())) {
617                 inquirableClass = getBusinessObjectDictionaryService().getInquirableClass(bo.getClass());
618                 inquirableClassCache.put(bo.getClass(), inquirableClass);
619             }
620             Inquirable inq = null;
621             try {
622                 if (inquirableClass != null) {
623                     inq = inquirableClass.newInstance();
624                 } else {
625                     inq = getKualiInquirable();
626                     if (LOG.isDebugEnabled()) {
627                         LOG.debug("Default Inquirable Class: " + inq.getClass());
628                     }
629                 }
630                 Boolean forceLookupResultFieldInquiry = forceLookupResultFieldInquiryCache.get(cacheKey);
631                 if (forceLookupResultFieldInquiry == null) {
632                     forceLookupResultFieldInquiry = getBusinessObjectDictionaryService().forceLookupResultFieldInquiry(bo.getClass(), propertyName);
633                     if (forceLookupResultFieldInquiry == null) {
634                         forceLookupResultFieldInquiry = Boolean.FALSE;
635                     }
636                     forceLookupResultFieldInquiryCache.put(cacheKey, forceLookupResultFieldInquiry);
637                 }
638                 inquiryUrl = inq.getInquiryUrl(bo, propertyName, forceLookupResultFieldInquiry);
639             } catch (Exception ex) {
640                 LOG.error("unable to create inquirable to get inquiry URL", ex);
641             }
642         }
643 
644         return inquiryUrl;
645     }
646 
647     protected CopiedObject<ArrayList<Column>> resultColumns = null;
648 
649     /**
650      * Constructs the list of columns for the search results. All properties for the column objects come from the DataDictionary.
651      */
652     public List<Column> getColumns() {
653         if (resultColumns == null) {
654             ArrayList<Column> columns = new ArrayList<Column>();
655             for (String attributeName : getBusinessObjectDictionaryService().getLookupResultFieldNames(getBusinessObjectClass())) {
656                 Column column = new Column();
657                 column.setPropertyName(attributeName);
658                 String columnTitle = getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), attributeName);
659                 Boolean useShortLabel = getBusinessObjectDictionaryService().getLookupResultFieldUseShortLabel(businessObjectClass, attributeName);
660                 if (useShortLabel != null && useShortLabel) {
661                     columnTitle = getDataDictionaryService().getAttributeShortLabel(getBusinessObjectClass(), attributeName);
662                 }
663                 if (StringUtils.isBlank(columnTitle)) {
664                     columnTitle = getDataDictionaryService().getCollectionLabel(getBusinessObjectClass(), attributeName);
665                 }
666                 column.setColumnTitle(columnTitle);
667                 column.setMaxLength(getColumnMaxLength(attributeName));
668 
669                 if (!businessObjectClass.isInterface()) {
670                     try {
671                         column.setFormatter(ObjectUtils.getFormatterWithDataDictionary(getBusinessObjectClass()
672                                 .newInstance(), attributeName));
673                     } catch (InstantiationException e) {
674                         LOG.info("Unable to get new instance of business object class: " + businessObjectClass.getName(), e);
675                         // just swallow exception and leave formatter blank
676                     } catch (IllegalAccessException e) {
677                         LOG.info("Unable to get new instance of business object class: " + businessObjectClass.getName(), e);
678                         // just swallow exception and leave formatter blank
679                     }
680                 }
681 
682                 String alternateDisplayPropertyName = getBusinessObjectDictionaryService()
683                         .getLookupFieldAlternateDisplayAttributeName(getBusinessObjectClass(), attributeName);
684                 if (StringUtils.isNotBlank(alternateDisplayPropertyName)) {
685                     column.setAlternateDisplayPropertyName(alternateDisplayPropertyName);
686                 }
687 
688                 String additionalDisplayPropertyName = getBusinessObjectDictionaryService()
689                         .getLookupFieldAdditionalDisplayAttributeName(getBusinessObjectClass(), attributeName);
690                 if (StringUtils.isNotBlank(additionalDisplayPropertyName)) {
691                     column.setAdditionalDisplayPropertyName(additionalDisplayPropertyName);
692                 } else {
693                     boolean translateCodes = getBusinessObjectDictionaryService().tranlateCodesInLookup(getBusinessObjectClass());
694                     if (translateCodes) {
695                         FieldUtils.setAdditionalDisplayPropertyForCodes(getBusinessObjectClass(), attributeName, column);
696                     }
697                 }
698 
699                 column.setTotal(getBusinessObjectDictionaryService().getLookupResultFieldTotal(getBusinessObjectClass(), attributeName));
700 
701                 columns.add(column);
702             }
703             resultColumns = ObjectUtils.deepCopyForCaching(columns);
704             return columns;
705         }
706         return resultColumns.getContent();
707     }
708 
709     protected static Integer RESULTS_DEFAULT_MAX_COLUMN_LENGTH = null;
710 
711     protected int getColumnMaxLength(String attributeName) {
712         Integer fieldDefinedMaxLength = getBusinessObjectDictionaryService().getLookupResultFieldMaxLength(getBusinessObjectClass(), attributeName);
713         if (fieldDefinedMaxLength == null) {
714             if (RESULTS_DEFAULT_MAX_COLUMN_LENGTH == null) {
715                 try {
716                     RESULTS_DEFAULT_MAX_COLUMN_LENGTH = Integer.valueOf(getParameterService().getParameterValueAsString(
717                             KRADConstants.KNS_NAMESPACE, KRADConstants.DetailTypes.LOOKUP_PARM_DETAIL_TYPE, KRADConstants.RESULTS_DEFAULT_MAX_COLUMN_LENGTH));
718                 } catch (NumberFormatException ex) {
719                     LOG.error("Lookup field max length parameter not found and unable to parse default set in system parameters (RESULTS_DEFAULT_MAX_COLUMN_LENGTH).");
720                 }
721             }
722             return RESULTS_DEFAULT_MAX_COLUMN_LENGTH.intValue();
723         }
724         return fieldDefinedMaxLength.intValue();
725     }
726 
727     /**
728      * @return Returns the backLocation.
729      */
730     public String getBackLocation() {
731         return backLocation;
732     }
733 
734     /**
735      * @param backLocation The backLocation to set.
736      */
737     public void setBackLocation(String backLocation) {
738         this.backLocation = backLocation;
739     }
740 
741     /**
742      * @see LookupableHelperService#getReturnLocation()
743      */
744     public String getReturnLocation() {
745         return backLocation;
746     }
747 
748     /**
749      * This method is for lookupable implementations
750      *
751      * @see LookupableHelperService#getReturnUrl(org.kuali.rice.krad.bo.BusinessObject, java.util.Map, java.lang.String, java.util.List)
752      */
753     final public HtmlData getReturnUrl(BusinessObject businessObject, Map fieldConversions, String lookupImpl, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) {
754         String href = getReturnHref(businessObject, fieldConversions, lookupImpl, returnKeys);
755         String returnUrlAnchorLabel =
756                 this.getKualiConfigurationService().getPropertyValueAsString(TITLE_RETURN_URL_PREPENDTEXT_PROPERTY);
757         HtmlData.AnchorHtmlData anchor = new HtmlData.AnchorHtmlData(href, HtmlData.getTitleText(returnUrlAnchorLabel, businessObject, returnKeys, businessObjectRestrictions));
758         anchor.setDisplayText(returnUrlAnchorLabel);
759         return anchor;
760     }
761 
762     /**
763      * This method is for lookupable implementations
764      *
765      * @param businessObject
766      * @param fieldConversions
767      * @param lookupImpl
768      * @param returnKeys
769      * @return
770      */
771     final protected String getReturnHref(BusinessObject businessObject, Map fieldConversions, String lookupImpl, List returnKeys) {
772         if (StringUtils.isNotBlank(backLocation)) {
773             return UrlFactory.parameterizeUrl(backLocation, getParameters(
774                     businessObject, fieldConversions, lookupImpl, returnKeys));
775         }
776         return "";
777     }
778 
779     /**
780      * @see LookupableHelperService#getReturnUrl(org.kuali.core.bo.BusinessObject, java.util.Map, java.lang.String)
781      */
782     public HtmlData getReturnUrl(BusinessObject businessObject, LookupForm lookupForm, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) {
783         Properties parameters = getParameters(
784                 businessObject, lookupForm.getFieldConversions(), lookupForm.getLookupableImplServiceName(), returnKeys);
785         if (StringUtils.isEmpty(lookupForm.getHtmlDataType()) || HtmlData.ANCHOR_HTML_DATA_TYPE.equals(lookupForm.getHtmlDataType()))
786             return getReturnAnchorHtmlData(businessObject, parameters, lookupForm, returnKeys, businessObjectRestrictions);
787         else
788             return getReturnInputHtmlData(businessObject, parameters, lookupForm, returnKeys, businessObjectRestrictions);
789     }
790 
791     protected HtmlData getReturnInputHtmlData(BusinessObject businessObject, Properties parameters, LookupForm lookupForm, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) {
792         String returnUrlAnchorLabel =
793                 this.getKualiConfigurationService().getPropertyValueAsString(TITLE_RETURN_URL_PREPENDTEXT_PROPERTY);
794         String name = KRADConstants.MULTIPLE_VALUE_LOOKUP_SELECTED_OBJ_ID_PARAM_PREFIX + lookupForm.getLookupObjectId();
795         HtmlData.InputHtmlData input = new HtmlData.InputHtmlData(name, HtmlData.InputHtmlData.CHECKBOX_INPUT_TYPE);
796         input.setTitle(HtmlData.getTitleText(returnUrlAnchorLabel, businessObject, returnKeys, businessObjectRestrictions));
797         if (((MultipleValueLookupForm) lookupForm).getCompositeObjectIdMap() == null ||
798                 ((MultipleValueLookupForm) lookupForm).getCompositeObjectIdMap().get(
799                         ((PersistableBusinessObject) businessObject).getObjectId()) == null) {
800             input.setChecked("");
801         } else {
802             input.setChecked(HtmlData.InputHtmlData.CHECKBOX_CHECKED_VALUE);
803         }
804         input.setValue(HtmlData.InputHtmlData.CHECKBOX_CHECKED_VALUE);
805         return input;
806     }
807 
808     protected HtmlData getReturnAnchorHtmlData(BusinessObject businessObject, Properties parameters, LookupForm lookupForm, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) {
809         String returnUrlAnchorLabel =
810                 this.getKualiConfigurationService().getPropertyValueAsString(TITLE_RETURN_URL_PREPENDTEXT_PROPERTY);
811         HtmlData.AnchorHtmlData anchor = new HtmlData.AnchorHtmlData(
812                 getReturnHref(parameters, lookupForm, returnKeys),
813                 HtmlData.getTitleText(returnUrlAnchorLabel, businessObject, returnKeys, businessObjectRestrictions));
814         anchor.setDisplayText(returnUrlAnchorLabel);
815         return anchor;
816     }
817 
818     protected String getReturnHref(Properties parameters, LookupForm lookupForm, List returnKeys) {
819         if (StringUtils.isNotBlank(backLocation)) {
820             String href = UrlFactory.parameterizeUrl(backLocation, parameters);
821             return addToReturnHref(href, lookupForm);
822         }
823         return "";
824     }
825 
826     protected String addToReturnHref(String href, LookupForm lookupForm) {
827         String lookupAnchor = "";
828         if (StringUtils.isNotEmpty(lookupForm.getAnchor())) {
829             lookupAnchor = lookupForm.getAnchor();
830         }
831         href += "&anchor=" + lookupAnchor + "&docNum=" + (StringUtils.isEmpty(getDocNum()) ? "" : getDocNum());
832         return href;
833     }
834 
835     protected Properties getParameters(BusinessObject bo, Map<String, String> fieldConversions, String lookupImpl, List returnKeys) {
836         Properties parameters = new Properties();
837         parameters.put(KRADConstants.DISPATCH_REQUEST_PARAMETER, KRADConstants.RETURN_METHOD_TO_CALL);
838         if (getDocFormKey() != null) {
839             parameters.put(KRADConstants.DOC_FORM_KEY, getDocFormKey());
840         }
841         if (lookupImpl != null) {
842             parameters.put(KRADConstants.REFRESH_CALLER, lookupImpl);
843         }
844         if (getDocNum() != null) {
845             parameters.put(KRADConstants.DOC_NUM, getDocNum());
846         }
847 
848         if (getReferencesToRefresh() != null) {
849             parameters.put(KRADConstants.REFERENCES_TO_REFRESH, getReferencesToRefresh());
850         }
851 
852         Iterator returnKeysIt = getReturnKeys().iterator();
853         while (returnKeysIt.hasNext()) {
854             String fieldNm = (String) returnKeysIt.next();
855 
856             Object fieldVal = ObjectUtils.getPropertyValue(bo, fieldNm);
857             if (fieldVal == null) {
858                 fieldVal = KRADConstants.EMPTY_STRING;
859             }
860 
861             if (getBusinessObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, fieldNm)) {
862                 try {
863                     if(CoreApiServiceLocator.getEncryptionService().isEnabled()) {
864                         fieldVal = getEncryptionService().encrypt(fieldVal) + EncryptionService.ENCRYPTION_POST_PREFIX;
865                     }
866                 } catch (GeneralSecurityException e) {
867                     LOG.error("Exception while trying to encrypted value for inquiry framework.", e);
868                     throw new RuntimeException(e);
869                 }
870 
871             }
872 
873             //need to format date in url
874             if (fieldVal instanceof Date) {
875                 DateFormatter dateFormatter = new DateFormatter();
876                 fieldVal = dateFormatter.format(fieldVal);
877             }
878 
879             if (fieldConversions.containsKey(fieldNm)) {
880                 fieldNm = (String) fieldConversions.get(fieldNm);
881             }
882 
883             parameters.put(fieldNm, fieldVal.toString());
884         }
885 
886         return parameters;
887     }
888 
889     /**
890      * @return a List of the names of fields which are marked in data dictionary as return fields.
891      */
892     public List<String> getReturnKeys() {
893         List<String> returnKeys;
894         if (fieldConversions != null && !fieldConversions.isEmpty()) {
895             returnKeys = new ArrayList<String>(fieldConversions.keySet());
896         } else {
897             returnKeys = getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(getBusinessObjectClass());
898         }
899 
900         return returnKeys;
901     }
902 
903     /**
904      * Gets the docFormKey attribute.
905      *
906      * @return Returns the docFormKey.
907      */
908     public String getDocFormKey() {
909         return docFormKey;
910     }
911 
912     /**
913      * Sets the docFormKey attribute value.
914      *
915      * @param docFormKey The docFormKey to set.
916      */
917     public void setDocFormKey(String docFormKey) {
918         this.docFormKey = docFormKey;
919     }
920 
921     /**
922      * @see LookupableHelperService#setFieldConversions(java.util.Map)
923      */
924     public void setFieldConversions(Map fieldConversions) {
925         this.fieldConversions = fieldConversions;
926     }
927 
928     /**
929      * Gets the lookupService attribute.
930      *
931      * @return Returns the lookupService.
932      */
933     protected LookupService getLookupService() {
934         return lookupService != null ? lookupService : KRADServiceLocatorWeb.getLookupService();
935     }
936 
937     /**
938      * Sets the lookupService attribute value.
939      *
940      * @param lookupService The lookupService to set.
941      */
942     public void setLookupService(LookupService lookupService) {
943         this.lookupService = lookupService;
944     }
945 
946     /**
947      * Uses the DD to determine which is the default sort order.
948      *
949      * @return property names that will be used to sort on by default
950      */
951     public List<String> getDefaultSortColumns() {
952         return getBusinessObjectDictionaryService().getLookupDefaultSortFieldNames(getBusinessObjectClass());
953     }
954 
955     /**
956      * Checks that any required search fields have value.
957      *
958      * @see LookupableHelperService#validateSearchParameters(java.util.Map)
959      */
960     public void validateSearchParameters(Map<String, String> fieldValues) {
961         List<String> lookupFieldAttributeList = null;
962         if (getBusinessObjectMetaDataService().isLookupable(getBusinessObjectClass())) {
963             lookupFieldAttributeList = getBusinessObjectMetaDataService().getLookupableFieldNames(getBusinessObjectClass());
964         }
965         if (lookupFieldAttributeList == null) {
966             throw new RuntimeException("Lookup not defined for business object " + getBusinessObjectClass());
967         }
968         for (Iterator iter = lookupFieldAttributeList.iterator(); iter.hasNext();) {
969             String attributeName = (String) iter.next();
970             if (fieldValues.containsKey(attributeName)) {
971                 // get label of attribute for message
972                 String attributeLabel = getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), attributeName);
973 
974                 String attributeValue = (String) fieldValues.get(attributeName);
975 
976                 // check for required if field does not have value
977                 if (StringUtils.isBlank(attributeValue)) {
978                     if ((getBusinessObjectDictionaryService().getLookupAttributeRequired(getBusinessObjectClass(), attributeName)).booleanValue()) {
979                         GlobalVariables.getMessageMap().putError(attributeName, RiceKeyConstants.ERROR_REQUIRED, attributeLabel);
980                     }
981                 }
982                 validateSearchParameterWildcardAndOperators(attributeName, attributeValue);
983             }
984         }
985 
986         if (GlobalVariables.getMessageMap().hasErrors()) {
987             throw new ValidationException("errors in search criteria");
988         }
989     }
990 
991     protected void validateSearchParameterWildcardAndOperators(String attributeName, String attributeValue) {
992         if (StringUtils.isBlank(attributeValue))
993             return;
994 
995         // make sure a wildcard/operator is in the value
996         boolean found = false;
997         for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) {
998             String queryCharacter = op.op();
999 
1000             if (attributeValue.contains(queryCharacter)) {
1001                 found = true;
1002             }
1003         }
1004         if (!found)
1005             return;
1006 
1007         String attributeLabel = getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), attributeName);
1008         if (getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(businessObjectClass, attributeName)) {
1009             BusinessObject example = null;
1010             try {
1011                 example = (BusinessObject) businessObjectClass.newInstance();
1012             } catch (Exception e) {
1013                 LOG.error("Exception caught instantiating " + businessObjectClass.getName(), e);
1014                 throw new RuntimeException("Cannot instantiate " + businessObjectClass.getName(), e);
1015             }
1016 
1017             Class propertyType = ObjectUtils.getPropertyType(example, attributeName, getPersistenceStructureService());
1018             if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) || TypeUtils.isTemporalClass(propertyType)) {
1019                 GlobalVariables.getMessageMap().putError(attributeName, RiceKeyConstants.ERROR_WILDCARDS_AND_OPERATORS_NOT_ALLOWED_ON_FIELD, attributeLabel);
1020             }
1021             if (TypeUtils.isStringClass(propertyType)) {
1022                 GlobalVariables.getMessageMap().putInfo(attributeName, RiceKeyConstants.INFO_WILDCARDS_AND_OPERATORS_TREATED_LITERALLY, attributeLabel);
1023             }
1024         } else {
1025             if (getBusinessObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, attributeName)) {
1026                 if (!attributeValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) {
1027                     // encrypted values usually come from the DB, so we don't need to filter for wildcards
1028 
1029                     // wildcards are not allowed on restricted fields, because they are typically encrypted, and wildcard searches cannot be performed without
1030                     // decrypting every row, which is currently not supported by KNS
1031 
1032                     GlobalVariables.getMessageMap().putError(attributeName, RiceKeyConstants.ERROR_SECURE_FIELD, attributeLabel);
1033                 }
1034             }
1035         }
1036     }
1037 
1038     /**
1039      * Constructs the list of rows for the search fields. All properties for the field objects come
1040      * from the DataDictionary. To be called by setBusinessObject
1041      */
1042     protected void setRows() {
1043         List<String> lookupFieldAttributeList = null;
1044         if (getBusinessObjectMetaDataService().isLookupable(getBusinessObjectClass())) {
1045             lookupFieldAttributeList = getBusinessObjectMetaDataService().getLookupableFieldNames(
1046                     getBusinessObjectClass());
1047         }
1048         if (lookupFieldAttributeList == null) {
1049             throw new RuntimeException("Lookup not defined for business object " + getBusinessObjectClass());
1050         }
1051 
1052         // construct field object for each search attribute
1053         List fields = new ArrayList();
1054         try {
1055             fields = FieldUtils.createAndPopulateFieldsForLookup(lookupFieldAttributeList, getReadOnlyFieldsList(),
1056                     getBusinessObjectClass());
1057         } catch (InstantiationException e) {
1058             throw new RuntimeException("Unable to create instance of business object class" + e.getMessage());
1059         } catch (IllegalAccessException e) {
1060             throw new RuntimeException("Unable to create instance of business object class" + e.getMessage());
1061         }
1062 
1063         int numCols = getBusinessObjectDictionaryService().getLookupNumberOfColumns(this.getBusinessObjectClass());
1064 
1065         this.rows = FieldUtils.wrapFields(fields, numCols);
1066     }
1067 
1068     public List<Row> getRows() {
1069         return rows;
1070     }
1071 
1072     public abstract List<? extends BusinessObject> getSearchResults(Map<String, String> fieldValues);
1073 
1074     /**
1075      * This implementation of this method throws an UnsupportedOperationException, since not every implementation
1076      * may actually want to use this operation.  Subclasses desiring other behaviors
1077      * will need to override this.
1078      *
1079      * @see LookupableHelperService#getSearchResultsUnbounded(java.util.Map)
1080      */
1081     public List<? extends BusinessObject> getSearchResultsUnbounded(Map<String, String> fieldValues) {
1082         throw new UnsupportedOperationException("Lookupable helper services do not always support getSearchResultsUnbounded");
1083     }
1084 
1085     /**
1086      * Performs the lookup and returns a collection of lookup items
1087      *
1088      * @param lookupForm
1089      * @param resultTable
1090      * @param bounded
1091      * @return
1092      */
1093     public Collection<? extends BusinessObject> performLookup(LookupForm lookupForm, Collection<ResultRow> resultTable, boolean bounded) {
1094         Map lookupFormFields = lookupForm.getFieldsForLookup();
1095 
1096         setBackLocation((String) lookupFormFields.get(KRADConstants.BACK_LOCATION));
1097         setDocFormKey((String) lookupFormFields.get(KRADConstants.DOC_FORM_KEY));
1098         Collection<? extends BusinessObject> displayList;
1099 
1100         LookupUtils.preProcessRangeFields(lookupFormFields);
1101 
1102         // call search method to get results
1103         if (bounded) {
1104             displayList = getSearchResults(lookupFormFields);
1105         } else {
1106             displayList = getSearchResultsUnbounded(lookupFormFields);
1107         }
1108 
1109         boolean hasReturnableRow = false;
1110 
1111         List<String> returnKeys = getReturnKeys();
1112         List<String> pkNames = getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(getBusinessObjectClass());
1113         Person user = GlobalVariables.getUserSession().getPerson();
1114 
1115         // iterate through result list and wrap rows with return url and action
1116         // urls
1117         for (BusinessObject element : displayList) {
1118             BusinessObject baseElement = element;
1119             //if ebo, then use base BO to get lookupId and find restrictions
1120             //we don't need to do this anymore as the BO is required to implement the EBO interface as of this time
1121             //if this needs reimplemented in the future, one should consider what happens/needs to happen
1122             //with the base BO fields (OBJ ID in particular) as they are all null/empty on new instantiation
1123             //which will fail if we try to depend on any values within it.
1124             //KULRICE-7223
1125 //            if (ExternalizableBusinessObjectUtils.isExternalizableBusinessObject(element.getClass())) {
1126 //                try {
1127 //                    baseElement = (BusinessObject)this.getBusinessObjectClass().newInstance();
1128 //                } catch (InstantiationException e) {
1129 //                    e.printStackTrace();
1130 //                } catch (IllegalAccessException e) {
1131 //                    e.printStackTrace();
1132 //                }
1133 //            }
1134 
1135             final String lookupId = KNSServiceLocator.getLookupResultsService().getLookupId(baseElement);
1136             if (lookupId != null) {
1137                 lookupForm.setLookupObjectId(lookupId);
1138             }
1139 
1140             BusinessObjectRestrictions businessObjectRestrictions = getBusinessObjectAuthorizationService()
1141                     .getLookupResultRestrictions(element, user);
1142 
1143             HtmlData returnUrl = getReturnUrl(element, lookupForm, returnKeys, businessObjectRestrictions);
1144             String actionUrls = getActionUrls(element, pkNames, businessObjectRestrictions);
1145             // Fix for JIRA - KFSMI-2417
1146             if ("".equals(actionUrls)) {
1147                 actionUrls = ACTION_URLS_EMPTY;
1148             }
1149 
1150             List<Column> columns = getColumns();
1151             for (Iterator iterator = columns.iterator(); iterator.hasNext();) {
1152                 Column col = (Column) iterator.next();
1153 
1154                 String propValue = ObjectUtils.getFormattedPropertyValue(element, col.getPropertyName(), col.getFormatter());
1155                 Class propClass = getPropertyClass(element, col.getPropertyName());
1156 
1157                 col.setComparator(CellComparatorHelper.getAppropriateComparatorForPropertyClass(propClass));
1158                 col.setValueComparator(CellComparatorHelper.getAppropriateValueComparatorForPropertyClass(propClass));
1159 
1160                 String propValueBeforePotientalMasking = propValue;
1161                 propValue = maskValueIfNecessary(element.getClass(), col.getPropertyName(), propValue,
1162                         businessObjectRestrictions);
1163                 col.setPropertyValue(propValue);
1164 
1165                 // if property value is masked, don't display additional or alternate properties, or allow totals
1166                 if (StringUtils.equals(propValueBeforePotientalMasking, propValue)) {
1167                     if (StringUtils.isNotBlank(col.getAlternateDisplayPropertyName())) {
1168                         String alternatePropertyValue = ObjectUtils.getFormattedPropertyValue(element, col
1169                                 .getAlternateDisplayPropertyName(), null);
1170                         col.setPropertyValue(alternatePropertyValue);
1171                     }
1172 
1173                     if (StringUtils.isNotBlank(col.getAdditionalDisplayPropertyName())) {
1174                         String additionalPropertyValue = ObjectUtils.getFormattedPropertyValue(element, col
1175                                 .getAdditionalDisplayPropertyName(), null);
1176                         col.setPropertyValue(col.getPropertyValue() + " *-* " + additionalPropertyValue);
1177                     }
1178                 } else {
1179                     col.setTotal(false);
1180                 }
1181 
1182                 if (col.isTotal()) {
1183                     Object unformattedPropValue = ObjectUtils.getPropertyValue(element, col.getPropertyName());
1184                     col.setUnformattedPropertyValue(unformattedPropValue);
1185                 }
1186 
1187                 if (StringUtils.isNotBlank(propValue)) {
1188                     col.setColumnAnchor(getInquiryUrl(element, col.getPropertyName()));
1189                 }
1190             }
1191 
1192             ResultRow row = new ResultRow(columns, returnUrl.constructCompleteHtmlTag(), actionUrls);
1193             row.setRowId(returnUrl.getName());
1194             row.setReturnUrlHtmlData(returnUrl);
1195 
1196             // because of concerns of the BO being cached in session on the
1197             // ResultRow,
1198             // let's only attach it when needed (currently in the case of
1199             // export)
1200             if (getBusinessObjectDictionaryService().isExportable(getBusinessObjectClass())) {
1201                 row.setBusinessObject(element);
1202             }
1203 
1204             if (lookupId != null) {
1205                 row.setObjectId(lookupId);
1206             }
1207 
1208             boolean rowReturnable = isResultReturnable(element);
1209             row.setRowReturnable(rowReturnable);
1210             if (rowReturnable) {
1211                 hasReturnableRow = true;
1212             }
1213             resultTable.add(row);
1214         }
1215 
1216         lookupForm.setHasReturnableRow(hasReturnableRow);
1217 
1218         return displayList;
1219     }
1220 
1221     /**
1222      * Gets the Class for the property in the given BusinessObject instance, if
1223      * property is not accessible then runtime exception is thrown
1224      *
1225      * @param element      BusinessObject instance that contains property
1226      * @param propertyName Name of property in BusinessObject to get class for
1227      * @return Type for property as Class
1228      */
1229     protected Class getPropertyClass(BusinessObject element, String propertyName) {
1230         Class propClass = null;
1231 
1232         try {
1233             propClass = ObjectUtils.getPropertyType(element, propertyName, getPersistenceStructureService());
1234 
1235         } catch (Exception e) {
1236             throw new RuntimeException("Cannot access PropertyType for property " + "'" + propertyName + "' "
1237                     + " on an instance of '" + element.getClass().getName() + "'.", e);
1238         }
1239 
1240         return propClass;
1241     }
1242 
1243 
1244 
1245     protected String maskValueIfNecessary(Class businessObjectClass, String propertyName, String propertyValue, BusinessObjectRestrictions businessObjectRestrictions) {
1246         String maskedPropertyValue = propertyValue;
1247         if (businessObjectRestrictions != null) {
1248             FieldRestriction fieldRestriction = businessObjectRestrictions.getFieldRestriction(propertyName);
1249             if (fieldRestriction != null && (fieldRestriction.isMasked() || fieldRestriction.isPartiallyMasked())) {
1250                 maskedPropertyValue = fieldRestriction.getMaskFormatter().maskValue(propertyValue);
1251             }
1252         }
1253         return maskedPropertyValue;
1254     }
1255 
1256 
1257     protected void setReferencesToRefresh(String referencesToRefresh) {
1258         this.referencesToRefresh = referencesToRefresh;
1259     }
1260 
1261     public String getReferencesToRefresh() {
1262         return referencesToRefresh;
1263     }
1264 
1265     protected SequenceAccessorService getSequenceAccessorService() {
1266         return sequenceAccessorService != null ? sequenceAccessorService : KRADServiceLocator
1267                 .getSequenceAccessorService();
1268     }
1269 
1270     public void setSequenceAccessorService(SequenceAccessorService sequenceAccessorService) {
1271         this.sequenceAccessorService = sequenceAccessorService;
1272     }
1273 
1274     public BusinessObjectService getBusinessObjectService() {
1275         return businessObjectService != null ? businessObjectService : KRADServiceLocator.getBusinessObjectService();
1276     }
1277 
1278     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
1279         this.businessObjectService = businessObjectService;
1280     }
1281 
1282     protected LookupResultsService getLookupResultsService() {
1283         return lookupResultsService != null ? lookupResultsService : KNSServiceLocator.getLookupResultsService();
1284     }
1285 
1286     public void setLookupResultsService(LookupResultsService lookupResultsService) {
1287         this.lookupResultsService = lookupResultsService;
1288     }
1289 
1290     /**
1291      * @return false always, subclasses should override to do something smarter
1292      * @see LookupableHelperService#isSearchUsingOnlyPrimaryKeyValues()
1293      */
1294     public boolean isSearchUsingOnlyPrimaryKeyValues() {
1295         // by default, this implementation returns false, as lookups may not necessarily support this
1296         return false;
1297     }
1298 
1299     /**
1300      * Returns "N/A"
1301      *
1302      * @return "N/A"
1303      * @see LookupableHelperService#getPrimaryKeyFieldLabels()
1304      */
1305     public String getPrimaryKeyFieldLabels() {
1306         return KRADConstants.NOT_AVAILABLE_STRING;
1307     }
1308 
1309     /**
1310      * @see LookupableHelperService#isResultReturnable(org.kuali.core.bo.BusinessObject)
1311      */
1312     public boolean isResultReturnable(BusinessObject object) {
1313         return true;
1314     }
1315 
1316     /**
1317      * This method does the logic for the clear action.
1318      *
1319      * @see LookupableHelperService#performClear()
1320      */
1321     public void performClear(LookupForm lookupForm) {
1322         for (Iterator iter = this.getRows().iterator(); iter.hasNext();) {
1323             Row row = (Row) iter.next();
1324             for (Iterator iterator = row.getFields().iterator(); iterator.hasNext();) {
1325                 Field field = (Field) iterator.next();
1326                 if (field.isSecure()) {
1327                     field.setSecure(false);
1328                     field.setDisplayMaskValue(null);
1329                     field.setEncryptedValue(null);
1330                 }
1331 
1332                 if (!field.getFieldType().equals(Field.RADIO)) {
1333                     field.setPropertyValue(field.getDefaultValue());
1334                     if (field.getFieldType().equals(Field.MULTISELECT)) {
1335                         field.setPropertyValues(null);
1336                     }
1337                 }
1338             }
1339         }
1340     }
1341 
1342     /**
1343      * @see LookupableHelperService#shouldDisplayHeaderNonMaintActions()
1344      */
1345     public boolean shouldDisplayHeaderNonMaintActions() {
1346         return true;
1347     }
1348 
1349     /**
1350      * @see LookupableHelperService#shouldDisplayLookupCriteria()
1351      */
1352     public boolean shouldDisplayLookupCriteria() {
1353         return true;
1354     }
1355 
1356     /**
1357      * @see LookupableHelperService#getSupplementalMenuBar()
1358      */
1359     public String getSupplementalMenuBar() {
1360         return new String();
1361     }
1362 
1363     /**
1364      * @see LookupableHelperService#getTitle()
1365      */
1366     public String getTitle() {
1367         return getBusinessObjectDictionaryService().getLookupTitle(getBusinessObjectClass());
1368     }
1369 
1370     /**
1371      * @see LookupableHelperService#performCustomAction(boolean)
1372      */
1373     public boolean performCustomAction(boolean ignoreErrors) {
1374         return false;
1375     }
1376 
1377     /**
1378      * @see Lookupable#getExtraField()
1379      */
1380     public Field getExtraField() {
1381         return null;
1382     }
1383 
1384     public boolean allowsNewOrCopyAction(String documentTypeName) {
1385         throw new UnsupportedOperationException("Function not supported.");
1386     }
1387 
1388     /**
1389      * Functional requirements state that users are able to perform searches using criteria values that they are not allowed to view.
1390      *
1391      * @see LookupableHelperService#applyFieldAuthorizationsFromNestedLookups(org.kuali.rice.krad.web.ui.Field)
1392      */
1393     public void applyFieldAuthorizationsFromNestedLookups(Field field) {
1394         BusinessObjectAuthorizationService boAuthzService = this.getBusinessObjectAuthorizationService();
1395         if (!Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType())) {
1396             if (field.getPropertyValue() != null && field.getPropertyValue().endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) {
1397                 if (boAuthzService.attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, field.getPropertyName())) {
1398                     AttributeSecurity attributeSecurity = getDataDictionaryService().getAttributeSecurity(businessObjectClass.getName(), field.getPropertyName());
1399                     Person user = GlobalVariables.getUserSession().getPerson();
1400                     String decryptedValue = "";
1401                     try {
1402                         String cipherText = StringUtils.removeEnd(field.getPropertyValue(), EncryptionService.ENCRYPTION_POST_PREFIX);
1403                         if(CoreApiServiceLocator.getEncryptionService().isEnabled()) {
1404                             decryptedValue = getEncryptionService().decrypt(cipherText);
1405                         }
1406                     } catch (GeneralSecurityException e) {
1407                         throw new RuntimeException("Error decrypting value for business object " + businessObjectClass + " attribute " + field.getPropertyName(), e);
1408                     }
1409                     if (attributeSecurity.isMask() && !boAuthzService.canFullyUnmaskField(user,
1410                             businessObjectClass, field.getPropertyName(), null)) {
1411                         MaskFormatter maskFormatter = attributeSecurity.getMaskFormatter();
1412                         field.setEncryptedValue(field.getPropertyValue());
1413                         field.setDisplayMaskValue(maskFormatter.maskValue(decryptedValue));
1414                         field.setSecure(true);
1415                     } else if (attributeSecurity.isPartialMask() && !boAuthzService.canPartiallyUnmaskField(user,
1416                             businessObjectClass, field.getPropertyName(), null)) {
1417                         MaskFormatter maskFormatter = attributeSecurity.getPartialMaskFormatter();
1418                         field.setEncryptedValue(field.getPropertyValue());
1419                         field.setDisplayMaskValue(maskFormatter.maskValue(decryptedValue));
1420                         field.setSecure(true);
1421                     } else {
1422                         field.setPropertyValue(org.kuali.rice.krad.lookup.LookupUtils
1423                                 .forceUppercase(businessObjectClass, field.getPropertyName(), decryptedValue));
1424                     }
1425                 } else {
1426                     throw new RuntimeException("Field " + field.getPersonNameAttributeName() + " was encrypted on " + businessObjectClass.getName() +
1427                             " lookup was encrypted when it should not have been encrypted according to the data dictionary.");
1428                 }
1429             }
1430         } else {
1431             if (boAuthzService.attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, field.getPropertyName())) {
1432                 LOG.error("Cannot handle multiple value field types that have field authorizations, please implement custom lookupable helper service");
1433                 throw new RuntimeException("Cannot handle multiple value field types that have field authorizations.");
1434             }
1435         }
1436     }
1437 
1438     /**
1439      * Calls methods that can be overridden by child lookupables to implement conditional logic for setting
1440      * read-only, required, and hidden attributes. Called in the last part of the lookup lifecycle so the
1441      * fields values that will be sent will be correctly reflected in the rows (like after a clear).
1442      *
1443      * @see #getConditionallyReadOnlyPropertyNames()
1444      * @see #getConditionallyRequiredPropertyNames()
1445      * @see #getConditionallyHiddenPropertyNames()
1446      * @see LookupableHelperService#applyConditionalLogicForFieldDisplay()
1447      */
1448     public void applyConditionalLogicForFieldDisplay() {
1449         Set<String> readOnlyFields = getConditionallyReadOnlyPropertyNames();
1450         Set<String> requiredFields = getConditionallyRequiredPropertyNames();
1451         Set<String> hiddenFields = getConditionallyHiddenPropertyNames();
1452 
1453         for (Iterator iter = this.getRows().iterator(); iter.hasNext();) {
1454             Row row = (Row) iter.next();
1455             for (Iterator iterator = row.getFields().iterator(); iterator.hasNext();) {
1456                 Field field = (Field) iterator.next();
1457 
1458                 if (readOnlyFields != null && readOnlyFields.contains(field.getPropertyName())) {
1459                     field.setReadOnly(true);
1460                 }
1461 
1462                 if (requiredFields != null && requiredFields.contains(field.getPropertyName())) {
1463                     field.setFieldRequired(true);
1464                 }
1465 
1466                 if (hiddenFields != null && hiddenFields.contains(field.getPropertyName())) {
1467                     field.setFieldType(Field.HIDDEN);
1468                 }
1469             }
1470         }
1471     }
1472 
1473     /**
1474      * @return Set of property names that should be set as read only based on the current search
1475      *         contents, note request parms containing search field values can be retrieved with
1476      *         {@link #getParameters()}
1477      */
1478     public Set<String> getConditionallyReadOnlyPropertyNames() {
1479         return new HashSet<String>();
1480     }
1481 
1482     /**
1483      * @return Set of property names that should be set as required based on the current search
1484      *         contents, note request parms containing search field values can be retrieved with
1485      *         {@link #getParameters()}
1486      */
1487     public Set<String> getConditionallyRequiredPropertyNames() {
1488         return new HashSet<String>();
1489     }
1490 
1491     /**
1492      * @return Set of property names that should be set as hidden based on the current search
1493      *         contents, note request parms containing search field values can be retrieved with
1494      *         {@link #getParameters()}
1495      */
1496     public Set<String> getConditionallyHiddenPropertyNames() {
1497         return new HashSet<String>();
1498     }
1499 
1500     /**
1501      * Helper method to get the value for a property out of the row-field graph. If property is
1502      * multi-value then the values will be joined by a semi-colon.
1503      *
1504      * @param propertyName - name of property to retrieve value for
1505      * @return current property value as a String
1506      */
1507     protected String getCurrentSearchFieldValue(String propertyName) {
1508         String currentValue = null;
1509 
1510         boolean fieldFound = false;
1511         for (Iterator iter = this.getRows().iterator(); iter.hasNext();) {
1512             Row row = (Row) iter.next();
1513             for (Iterator iterator = row.getFields().iterator(); iterator.hasNext();) {
1514                 Field field = (Field) iterator.next();
1515 
1516                 if (StringUtils.equalsIgnoreCase(propertyName, field.getPropertyName())) {
1517                     if (Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType())) {
1518                         currentValue = StringUtils.join(field.getPropertyValues(), ";");
1519                     } else {
1520                         currentValue = field.getPropertyValue();
1521                     }
1522                     fieldFound = true;
1523                 }
1524 
1525                 if (fieldFound) {
1526                     break;
1527                 }
1528             }
1529 
1530             if (fieldFound) {
1531                 break;
1532             }
1533         }
1534 
1535         return currentValue;
1536     }
1537 }