001 /**
002 * Copyright 2005-2013 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.coreservice.framework.CoreFrameworkServiceLocator;
020 import org.kuali.rice.coreservice.framework.parameter.ParameterService;
021 import org.kuali.rice.core.api.CoreApiServiceLocator;
022 import org.kuali.rice.core.api.config.property.ConfigurationService;
023 import org.kuali.rice.core.api.encryption.EncryptionService;
024 import org.kuali.rice.core.api.search.SearchOperator;
025 import org.kuali.rice.core.api.util.RiceKeyConstants;
026 import org.kuali.rice.core.api.util.cache.CopiedObject;
027 import org.kuali.rice.core.api.util.type.TypeUtils;
028 import org.kuali.rice.core.web.format.DateFormatter;
029 import org.kuali.rice.core.web.format.Formatter;
030 import org.kuali.rice.kim.api.identity.Person;
031 import org.kuali.rice.kns.document.authorization.BusinessObjectRestrictions;
032 import org.kuali.rice.kns.document.authorization.FieldRestriction;
033 import org.kuali.rice.kns.inquiry.Inquirable;
034 import org.kuali.rice.kns.service.BusinessObjectAuthorizationService;
035 import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
036 import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
037 import org.kuali.rice.kns.service.KNSServiceLocator;
038 import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
039 import org.kuali.rice.kns.util.FieldUtils;
040 import org.kuali.rice.kns.web.comparator.CellComparatorHelper;
041 import org.kuali.rice.kns.web.struts.form.LookupForm;
042 import org.kuali.rice.kns.web.struts.form.MultipleValueLookupForm;
043 import org.kuali.rice.kns.web.ui.Column;
044 import org.kuali.rice.kns.web.ui.Field;
045 import org.kuali.rice.kns.web.ui.ResultRow;
046 import org.kuali.rice.kns.web.ui.Row;
047 import org.kuali.rice.krad.bo.BusinessObject;
048 import org.kuali.rice.krad.bo.PersistableBusinessObject;
049 import org.kuali.rice.krad.datadictionary.AttributeSecurity;
050 import org.kuali.rice.krad.datadictionary.mask.MaskFormatter;
051 import org.kuali.rice.krad.exception.ValidationException;
052 import org.kuali.rice.krad.service.BusinessObjectService;
053 import org.kuali.rice.krad.service.DataDictionaryService;
054 import org.kuali.rice.krad.service.KRADServiceLocator;
055 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
056 import org.kuali.rice.krad.service.LookupService;
057 import org.kuali.rice.krad.service.PersistenceStructureService;
058 import org.kuali.rice.krad.service.SequenceAccessorService;
059 import org.kuali.rice.krad.util.ExternalizableBusinessObjectUtils;
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 public abstract class AbstractLookupableHelperServiceImpl implements LookupableHelperService {
082
083 protected static final String TITLE_RETURN_URL_PREPENDTEXT_PROPERTY = "title.return.url.value.prependtext";
084 protected static final String TITLE_ACTION_URL_PREPENDTEXT_PROPERTY = "title.action.url.value.prependtext";
085 protected static final String ACTION_URLS_CHILDREN_SEPARATOR = " | ";
086 protected static final String ACTION_URLS_CHILDREN_STARTER = " [";
087 protected static final String ACTION_URLS_CHILDREN_END = "]";
088 protected static final String ACTION_URLS_SEPARATOR = " ";
089 protected static final String ACTION_URLS_EMPTY = " ";
090
091 protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AbstractLookupableHelperServiceImpl.class);
092
093 protected Class businessObjectClass;
094 protected Map<String, String[]> parameters;
095 protected BusinessObjectDictionaryService businessObjectDictionaryService;
096 protected BusinessObjectMetaDataService businessObjectMetaDataService;
097 protected DataDictionaryService dataDictionaryService;
098 protected PersistenceStructureService persistenceStructureService;
099 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 }