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