001/** 002 * Copyright 2005-2017 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.kew.impl.document.search; 017 018import org.apache.commons.beanutils.PropertyUtils; 019import org.apache.commons.lang.ArrayUtils; 020import org.apache.commons.lang.BooleanUtils; 021import org.apache.commons.lang.ObjectUtils; 022import org.apache.commons.lang.StringUtils; 023import org.kuali.rice.core.api.CoreApiServiceLocator; 024import org.kuali.rice.core.api.config.property.Config; 025import org.kuali.rice.core.api.config.property.ConfigContext; 026import org.kuali.rice.core.api.search.SearchOperator; 027import org.kuali.rice.core.api.uif.RemotableAttributeField; 028import org.kuali.rice.core.api.util.KeyValue; 029import org.kuali.rice.core.api.util.RiceKeyConstants; 030import org.kuali.rice.core.api.util.type.KualiDecimal; 031import org.kuali.rice.core.api.util.type.KualiPercent; 032import org.kuali.rice.core.web.format.Formatter; 033import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator; 034import org.kuali.rice.kew.api.KEWPropertyConstants; 035import org.kuali.rice.kew.api.KewApiConstants; 036import org.kuali.rice.kew.api.document.attribute.DocumentAttribute; 037import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria; 038import org.kuali.rice.kew.api.document.search.DocumentSearchCriteriaContract; 039import org.kuali.rice.kew.api.document.search.DocumentSearchResult; 040import org.kuali.rice.kew.api.document.search.DocumentSearchResults; 041import org.kuali.rice.kew.docsearch.DocumentSearchCriteriaProcessor; 042import org.kuali.rice.kew.docsearch.DocumentSearchCriteriaProcessorKEWAdapter; 043import org.kuali.rice.kew.docsearch.service.DocumentSearchService; 044import org.kuali.rice.kew.doctype.bo.DocumentType; 045import org.kuali.rice.kew.exception.WorkflowServiceError; 046import org.kuali.rice.kew.exception.WorkflowServiceErrorException; 047import org.kuali.rice.kew.framework.document.search.AttributeFields; 048import org.kuali.rice.kew.framework.document.search.DocumentSearchCriteriaConfiguration; 049import org.kuali.rice.kew.framework.document.search.DocumentSearchResultSetConfiguration; 050import org.kuali.rice.kew.framework.document.search.StandardResultField; 051import org.kuali.rice.kew.lookup.valuefinder.SavedSearchValuesFinder; 052import org.kuali.rice.kew.service.KEWServiceLocator; 053import org.kuali.rice.kew.user.UserUtils; 054import org.kuali.rice.kew.util.DocumentTypeWindowTargets; 055import org.kuali.rice.kim.api.identity.Person; 056import org.kuali.rice.kns.datadictionary.BusinessObjectEntry; 057import org.kuali.rice.kns.lookup.HtmlData; 058import org.kuali.rice.kns.lookup.KualiLookupableHelperServiceImpl; 059import org.kuali.rice.kns.lookup.LookupUtils; 060import org.kuali.rice.kns.util.FieldUtils; 061import org.kuali.rice.kns.web.struts.form.LookupForm; 062import org.kuali.rice.kns.web.ui.Column; 063import org.kuali.rice.kns.web.ui.Field; 064import org.kuali.rice.kns.web.ui.ResultRow; 065import org.kuali.rice.kns.web.ui.Row; 066import org.kuali.rice.krad.bo.BusinessObject; 067import org.kuali.rice.krad.exception.ValidationException; 068import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 069import org.kuali.rice.krad.util.GlobalVariables; 070import org.kuali.rice.krad.util.KRADConstants; 071 072import java.lang.reflect.InvocationTargetException; 073import java.math.BigDecimal; 074import java.text.MessageFormat; 075import java.util.*; 076import java.util.regex.Matcher; 077import java.util.regex.Pattern; 078 079/** 080 * Implementation of lookupable helper service which handles the complex lookup behavior required by the KEW 081 * document search screen. 082 * 083 * @author Kuali Rice Team (rice.collab@kuali.org) 084 */ 085public class DocumentSearchCriteriaBoLookupableHelperService extends KualiLookupableHelperServiceImpl { 086 087 static final String SAVED_SEARCH_NAME_PARAM = "savedSearchToLoadAndExecute"; 088 static final String DOCUMENT_TYPE_NAME_PARAM = "documentTypeName"; 089 static final String TARGET_SPEC_PARAM = "targetSpec"; 090 static final String DOCUMENT_TARGET_SPEC_PARAM = "documentTargetSpec"; 091 static final String ROUTE_LOG_TARGET_SPEC_PARAM = "routeLogTargetSpec"; 092 static final String SHOW_SUPER_USER_BUTTON_PARAM = "showSuperUserButton"; 093 094 // warning message keys 095 096 private static final String EXCEED_THRESHOLD_MESSAGE_KEY = "docsearch.DocumentSearchService.exceededThreshold"; 097 private static final String SECURITY_FILTERED_MESSAGE_KEY = "docsearch.DocumentSearchService.securityFiltered"; 098 private static final String EXCEED_THRESHOLD_AND_SECURITY_FILTERED_MESSAGE_KEY = "docsearch.DocumentSearchService.exceededThresholdAndSecurityFiltered"; 099 100 private static final boolean DOCUMENT_HANDLER_POPUP_DEFAULT = true; 101 private static final boolean ROUTE_LOG_POPUP_DEFAULT = true; 102 103 // injected services 104 105 private DocumentSearchService documentSearchService; 106 private DocumentSearchCriteriaProcessor documentSearchCriteriaProcessor; 107 private DocumentSearchCriteriaTranslator documentSearchCriteriaTranslator; 108 109 // These two fields are *only* used to pass side-channel information across the superclass API boundary between 110 // performLookup and getSearchResultsHelper. 111 // (in theory these could be replaced with some threadlocal subterfuge, but keeping as-is for simplicity) 112 private DocumentSearchResults searchResults = null; 113 private DocumentSearchCriteria criteria = null; 114 115 private DocumentTypeWindowTargets targets; 116 117 @Override 118 public void setParameters(Map<String, String[]> parameters) { 119 super.setParameters(parameters); 120 populateTargets(); 121 } 122 123 private void populateTargets() { 124 String defaultDocumentTarget = isDocumentHandlerPopup() ? "_blank" : "_self"; 125 String defaultRouteLogTarget = isRouteLogPopup() ? "_blank" : "_self"; 126 String targetSpec = StringUtils.join(getParameters().get(TARGET_SPEC_PARAM), ","); 127 String documentTargetSpec = StringUtils.join(getParameters().get(DOCUMENT_TARGET_SPEC_PARAM), ","); 128 String routeLogTargetSpec = StringUtils.join(getParameters().get(ROUTE_LOG_TARGET_SPEC_PARAM), ","); 129 if (documentTargetSpec == null) { 130 documentTargetSpec = targetSpec; 131 getParameters().put(DOCUMENT_TARGET_SPEC_PARAM, getParameters().get(TARGET_SPEC_PARAM)); 132 } 133 if (routeLogTargetSpec == null) { 134 routeLogTargetSpec = targetSpec; 135 getParameters().put(ROUTE_LOG_TARGET_SPEC_PARAM, getParameters().get(TARGET_SPEC_PARAM)); 136 } 137 this.targets = new DocumentTypeWindowTargets(documentTargetSpec, routeLogTargetSpec, 138 defaultDocumentTarget, defaultRouteLogTarget, KEWServiceLocator.getDocumentTypeService()); 139 } 140 141 @Override 142 protected List<? extends BusinessObject> getSearchResultsHelper(Map<String, String> fieldValues, boolean unbounded) { 143 criteria = loadCriteria(fieldValues); 144 searchResults = null; 145 try { 146 //KULRICE-12307: Document search API saves searches to user's saved document searches 147 searchResults = KEWServiceLocator.getDocumentSearchService().lookupDocuments(GlobalVariables.getUserSession().getPrincipalId(), criteria, true); 148 if (searchResults.isCriteriaModified()) { 149 criteria = searchResults.getCriteria(); 150 } 151 } catch (WorkflowServiceErrorException wsee) { 152 for (WorkflowServiceError workflowServiceError : (List<WorkflowServiceError>) wsee.getServiceErrors()) { 153 if (workflowServiceError.getMessageMap() != null && workflowServiceError.getMessageMap().hasErrors()) { 154 // merge the message maps 155 GlobalVariables.getMessageMap().merge(workflowServiceError.getMessageMap()); 156 } else { 157 GlobalVariables.getMessageMap().putError(workflowServiceError.getMessage(), RiceKeyConstants.ERROR_CUSTOM, workflowServiceError.getMessage()); 158 } 159 } 160 } 161 162 if (!GlobalVariables.getMessageMap().hasNoErrors() || searchResults == null) { 163 throw new ValidationException("error with doc search"); 164 } 165 166 populateResultWarningMessages(searchResults); 167 168 List<DocumentSearchResult> individualSearchResults = searchResults.getSearchResults(); 169 170 setBackLocation(fieldValues.get(KRADConstants.BACK_LOCATION)); 171 setDocFormKey(fieldValues.get(KRADConstants.DOC_FORM_KEY)); 172 173 applyCriteriaChangesToFields(criteria); 174 175 return populateSearchResults(individualSearchResults); 176 } 177 178 /** 179 * Inspects the lookup results to determine if any warning messages should be published to the message map. 180 */ 181 protected void populateResultWarningMessages(DocumentSearchResults searchResults) { 182 // check various warning conditions 183 boolean overThreshold = searchResults.isOverThreshold(); 184 int numFiltered = searchResults.getNumberOfSecurityFilteredResults(); 185 int numResults = searchResults.getSearchResults().size(); 186 if (overThreshold && numFiltered > 0) { 187 GlobalVariables.getMessageMap().putWarning(KRADConstants.GLOBAL_MESSAGES, EXCEED_THRESHOLD_AND_SECURITY_FILTERED_MESSAGE_KEY, String.valueOf(numResults), String.valueOf(numFiltered)); 188 } else if (numFiltered > 0) { 189 GlobalVariables.getMessageMap().putWarning(KRADConstants.GLOBAL_MESSAGES, SECURITY_FILTERED_MESSAGE_KEY, String.valueOf(numFiltered)); 190 } else if (overThreshold) { 191 GlobalVariables.getMessageMap().putWarning(KRADConstants.GLOBAL_MESSAGES, EXCEED_THRESHOLD_MESSAGE_KEY, String.valueOf(numResults)); 192 } 193 } 194 195 /** 196 * Applies changes that might have happened to the criteria back to the fields so that they show up on the form. 197 * Namely, this handles populating the form with today's date if the create date was not filled in on the form. 198 */ 199 protected void applyCriteriaChangesToFields(DocumentSearchCriteriaContract criteria) { 200 Field field = getFormFields().getField(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + "dateCreated"); 201 if (field != null && StringUtils.isEmpty(field.getPropertyValue())) { 202 if (criteria.getDateCreatedFrom() != null) { 203 field.setPropertyValue(CoreApiServiceLocator.getDateTimeService().toDateString(criteria.getDateCreatedFrom().toDate())); 204 } 205 } 206 } 207 208 // CURRENT_USER token pattern: CURRENT_USER(.type) surrounded by positive lookahead/lookbehind for non-alphanum terminal tokens 209 // (to support expression operators) 210 private static final Pattern CURRENT_USER_PATTERN = Pattern.compile("(?<=[\\s\\p{Punct}]|^)CURRENT_USER(\\.\\w+)?(?=[\\s\\p{Punct}]|$)"); 211 212 protected static String replaceCurrentUserToken(String value, Person person) { 213 Matcher matcher = CURRENT_USER_PATTERN.matcher(value); 214 boolean matched = false; 215 StringBuffer sb = new StringBuffer(); 216 while (matcher.find()) { 217 matched = true; 218 String idType = "principalName"; 219 if (matcher.groupCount() > 0) { 220 String group = matcher.group(1); 221 if (group != null) { 222 idType = group.substring(1); // discard period after CURRENT_USER 223 } 224 } 225 String idValue = UserUtils.getIdValue(idType, person); 226 if (!StringUtils.isBlank(idValue)) { 227 value = idValue; 228 } else { 229 value = matcher.group(); 230 } 231 matcher.appendReplacement(sb, value); 232 233 } 234 matcher.appendTail(sb); 235 return matched ? sb.toString() : null; 236 } 237 238 /** 239 * Cleans up various issues with fieldValues coming from the lookup form (namely, that they don't include 240 * multi-valued field values!). Handles these by adding them comma-separated. 241 */ 242 protected static Map<String, String> cleanupFieldValues(Map<String, String> fieldValues, Map<String, String[]> parameters) { 243 Map<String, String> cleanedUpFieldValues = new HashMap<String, String>(fieldValues); 244 if (ArrayUtils.isNotEmpty(parameters.get(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_STATUS_CODE))) { 245 cleanedUpFieldValues.put(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_STATUS_CODE, 246 StringUtils.join(parameters.get(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_STATUS_CODE), ",")); 247 } 248 if (ArrayUtils.isNotEmpty(parameters.get(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_DOC_STATUS))) { 249 cleanedUpFieldValues.put(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_DOC_STATUS, 250 StringUtils.join(parameters.get(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_DOC_STATUS), ",")); 251 } 252 Map<String, String> documentAttributeFieldValues = new HashMap<String, String>(); 253 Set<String> validAttributeNames = getValidSearchableAttributeNames(fieldValues.get(DOCUMENT_TYPE_NAME_PARAM)); 254 for (String parameterName : parameters.keySet()) { 255 if (parameterName.contains(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX)) { 256 // Check to see if this document attribute is in the list of valid attributes 257 String attributeName = StringUtils.substringAfter(parameterName, KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX); 258 if(!validAttributeNames.contains(attributeName)) { 259 continue; 260 } 261 String[] value = parameters.get(parameterName); 262 if (ArrayUtils.isNotEmpty(value)) { 263 if ( parameters.containsKey(parameterName + KRADConstants.CHECKBOX_PRESENT_ON_FORM_ANNOTATION)) { 264 documentAttributeFieldValues.put(parameterName, "Y"); 265 } else { 266 documentAttributeFieldValues.put(parameterName, StringUtils.join(value, " " + SearchOperator.OR.op() + " ")); 267 } 268 } 269 } 270 } 271 // if any of the document attributes are range values, process them 272 documentAttributeFieldValues.putAll(LookupUtils.preProcessRangeFields(documentAttributeFieldValues)); 273 cleanedUpFieldValues.putAll(documentAttributeFieldValues); 274 275 replaceCurrentUserInFields(cleanedUpFieldValues); 276 277 return cleanedUpFieldValues; 278 } 279 280 /** 281 * This method takes in a document type name and returns a set containing 282 * the names of valid searchable attributes for that document type 283 * @param documentTypeName The name of the document type to find attributes for 284 * @return A set containing the names of the searchable attributes for the given document type 285 */ 286 protected static Set<String> getValidSearchableAttributeNames(String documentTypeName) { 287 Set<String> validAttributeNames = new HashSet<String>(); 288 if(StringUtils.isNotBlank(documentTypeName)) { 289 // We have a document type name in the search criteria so fetch the document type 290 DocumentType documentType = getValidDocumentType(documentTypeName); 291 if(documentType != null) { 292 // We have a valid document type so use the doc search mediator to find its searchable attribute fields 293 DocumentSearchCriteriaConfiguration searchConfiguration = KEWServiceLocator.getDocumentSearchCustomizationMediator() 294 .getDocumentSearchCriteriaConfiguration(documentType); 295 if (searchConfiguration != null) { 296 List<AttributeFields> attributeFields = searchConfiguration.getSearchAttributeFields(); 297 if (attributeFields != null) { 298 for (AttributeFields fields : attributeFields) { 299 if(fields.getRemotableAttributeFields() != null) { 300 for(RemotableAttributeField field : fields.getRemotableAttributeFields()) { 301 validAttributeNames.add(field.getName()); 302 } 303 } 304 } 305 } 306 } 307 } 308 } 309 return validAttributeNames; 310 } 311 312 protected static void replaceCurrentUserInFields(Map<String, String> fields) { 313 Person person = GlobalVariables.getUserSession().getPerson(); 314 // replace the dynamic CURRENT_USER token 315 for (Map.Entry<String, String> entry: fields.entrySet()) { 316 if (StringUtils.isNotEmpty(entry.getValue())) { 317 String replaced = replaceCurrentUserToken(entry.getValue(), person); 318 if (replaced != null) { 319 entry.setValue(replaced); 320 } 321 } 322 } 323 } 324 325 /** 326 * Loads the document search criteria from the given map of field values as submitted from the search screen, and 327 * populates the current form Rows/Fields with the saved criteria fields 328 */ 329 protected DocumentSearchCriteria loadCriteria(Map<String, String> fieldValues) { 330 fieldValues = cleanupFieldValues(fieldValues, getParameters()); 331 String[] savedSearchToLoad = getParameters().get(SAVED_SEARCH_NAME_PARAM); 332 boolean savedSearch = savedSearchToLoad != null && savedSearchToLoad.length > 0 && StringUtils.isNotBlank(savedSearchToLoad[0]); 333 if (savedSearch) { 334 DocumentSearchCriteria criteria = getDocumentSearchService().getNamedSearchCriteria(GlobalVariables.getUserSession().getPrincipalId(), savedSearchToLoad[0]); 335 if (criteria != null) { 336 getFormFields().setFieldValues(getDocumentSearchCriteriaTranslator().translateCriteriaToFields(criteria)); 337 return criteria; 338 } 339 } 340 // either it wasn't a saved search or the saved search failed to resolve 341 return getDocumentSearchCriteriaTranslator().translateFieldsToCriteria(fieldValues); 342 } 343 344 protected List<DocumentSearchCriteriaBo> populateSearchResults(List<DocumentSearchResult> lookupResults) { 345 List<DocumentSearchCriteriaBo> searchResults = new ArrayList<DocumentSearchCriteriaBo>(); 346 for (DocumentSearchResult searchResult : lookupResults) { 347 DocumentSearchCriteriaBo result = new DocumentSearchCriteriaBo(); 348 result.populateFromDocumentSearchResult(searchResult); 349 searchResults.add(result); 350 } 351 return searchResults; 352 } 353 354 @Override 355 public Collection<? extends BusinessObject> performLookup(LookupForm lookupForm, Collection<ResultRow> resultTable, boolean bounded) { 356 Collection<? extends BusinessObject> lookupResult = super.performLookup(lookupForm, resultTable, bounded); 357 postProcessResults(resultTable, this.searchResults); 358 return lookupResult; 359 } 360 361 /** 362 * Overrides a Field value; sets a fallback/restored value if there is no new value 363 */ 364 protected void overrideFieldValue(Field field, Map<String, String[]> newValues, Map<String, String[]> oldValues) { 365 if (StringUtils.isNotBlank(field.getPropertyName())) { 366 if (newValues.get(field.getPropertyName()) != null) { 367 getFormFields().setFieldValue(field, newValues.get(field.getPropertyName())); 368 } else if (oldValues.get(field.getPropertyName()) != null) { 369 getFormFields().setFieldValue(field, oldValues.get(field.getPropertyName())); 370 } 371 } 372 } 373 374 /** 375 * Handles toggling between form views. 376 * Reads and sets the Rows state. 377 */ 378 protected void toggleFormView() { 379 Map<String,String[]> fieldValues = new HashMap<String,String[]>(); 380 Map<String, String[]> savedValues = getFormFields().getFieldValues(); 381 382 // the original implementation saved the form values and then re-applied them 383 // we do the same here, however I suspect we may be able to avoid this re-application 384 // of existing values 385 386 for (Field field: getFormFields().getFields()) { 387 overrideFieldValue(field, this.getParameters(), savedValues); 388 // if we are sure this does not depend on or cause side effects in other fields 389 // then this phase can be extracted and these loops simplified 390 applyFieldAuthorizationsFromNestedLookups(field); 391 fieldValues.put(field.getPropertyName(), new String[] { field.getPropertyValue() }); 392 } 393 394 // checkForAdditionalFields generates the form (setRows) 395 if (checkForAdditionalFieldsMultiValued(fieldValues)) { 396 for (Field field: getFormFields().getFields()) { 397 overrideFieldValue(field, this.getParameters(), savedValues); 398 fieldValues.put(field.getPropertyName(), new String[] { field.getPropertyValue() }); 399 } 400 } 401 402 // unset the clear search param, since this is not really a state, but just an action 403 // it can never be toggled "off", just "on" 404 getFormFields().setFieldValue(DocumentSearchCriteriaProcessorKEWAdapter.CLEARSAVED_SEARCH_FIELD, ""); 405 } 406 407 /** 408 * Loads a saved search 409 * @return returns true on success to run the loaded search, false on error. 410 */ 411 protected boolean loadSavedSearch(boolean ignoreErrors) { 412 Map<String,String[]> fieldValues = new HashMap<String,String[]>(); 413 414 String savedSearchName = getSavedSearchName(); 415 if(StringUtils.isEmpty(savedSearchName) || "*ignore*".equals(savedSearchName)) { 416 if(!ignoreErrors) { 417 GlobalVariables.getMessageMap().putError(SAVED_SEARCH_NAME_PARAM, RiceKeyConstants.ERROR_CUSTOM, "You must select a saved search"); 418 } else { 419 //if we're ignoring errors and we got an error just return, no reason to continue. Also set false to indicate not to perform lookup 420 return false; 421 } 422 getFormFields().setFieldValue(SAVED_SEARCH_NAME_PARAM, ""); 423 } 424 if (!GlobalVariables.getMessageMap().hasNoErrors()) { 425 throw new ValidationException("errors in search criteria"); 426 } 427 428 DocumentSearchCriteria criteria = KEWServiceLocator.getDocumentSearchService().getSavedSearchCriteria(GlobalVariables.getUserSession().getPrincipalId(), savedSearchName); 429 430 // get the document type 431 String docTypeName = criteria.getDocumentTypeName(); 432 433 // update the parameters to include whether or not this is an advanced search 434 if(this.getParameters().containsKey(KRADConstants.ADVANCED_SEARCH_FIELD)) { 435 Map<String, String[]> parameters = this.getParameters(); 436 String[] params = (String[])parameters.get(KRADConstants.ADVANCED_SEARCH_FIELD); 437 if (ArrayUtils.isNotEmpty(params)) { 438 params[0] = criteria.getIsAdvancedSearch(); 439 this.setParameters(parameters); 440 } 441 } 442 443 // and set the rows based on doc type 444 setRows(docTypeName); 445 446 // clear the name of the search in the form 447 //fieldValues.put(SAVED_SEARCH_NAME_PARAM, new String[0]); 448 449 // set the custom document attribute values on the search form 450 for (Map.Entry<String, List<String>> entry: criteria.getDocumentAttributeValues().entrySet()) { 451 fieldValues.put(entry.getKey(), entry.getValue().toArray(new String[entry.getValue().size()])); 452 } 453 454 // sets the field values on the form, trying criteria object properties if a field value is not present in the map 455 for (Field field : getFormFields().getFields()) { 456 if (field.getPropertyName() != null && !field.getPropertyName().equals("")) { 457 // UI Fields know whether they are single or multiple value 458 // just set both so they can make the determination and render appropriately 459 String[] values = null; 460 if (fieldValues.get(field.getPropertyName()) != null) { 461 values = fieldValues.get(field.getPropertyName()); 462 } else { 463 //may be on the root of the criteria object, try looking there: 464 try { 465 if (field.isRanged() && field.isDatePicker()) { 466 if (field.getPropertyName().startsWith(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX)) { 467 String lowerBoundName = field.getPropertyName().replace(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX, "") + "From"; 468 Object lowerBoundDate = PropertyUtils.getProperty(criteria, lowerBoundName); 469 if (lowerBoundDate != null) { 470 values = new String[] { CoreApiServiceLocator.getDateTimeService().toDateTimeString(((org.joda.time.DateTime)lowerBoundDate).toDate()) }; 471 } 472 } else { 473 // the upper bound prefix may or may not be on the propertyName. Using "replace" just in case. 474 String upperBoundName = field.getPropertyName().replace(KRADConstants.LOOKUP_RANGE_UPPER_BOUND_PROPERTY_PREFIX, "") + "To"; 475 Object upperBoundDate = PropertyUtils.getProperty(criteria, upperBoundName); 476 if (upperBoundDate != null) { 477 values = new String[] { CoreApiServiceLocator.getDateTimeService().toDateTimeString( 478 ((org.joda.time.DateTime)upperBoundDate) 479 .toDate()) }; 480 } 481 } 482 } else { 483 values = new String[] { ObjectUtils.toString(PropertyUtils.getProperty(criteria, field.getPropertyName())) }; 484 } 485 } catch (IllegalAccessException e) { 486 e.printStackTrace(); 487 } catch (InvocationTargetException e) { 488 e.printStackTrace(); 489 } catch (NoSuchMethodException e) { 490 // e.printStackTrace(); 491 //hmm what to do here, we should be able to find everything either in the search atts or at the base as far as I know. 492 } 493 } 494 if (values != null) { 495 getFormFields().setFieldValue(field, values); 496 } 497 } 498 } 499 500 return true; 501 } 502 503 /** 504 * Performs custom document search/lookup actions. 505 * 1) switching between simple/detailed search 506 * 2) switching between non-superuser/superuser search 507 * 3) clearing saved search results 508 * 4) restoring a saved search and executing the search 509 * @param ignoreErrors 510 * @return whether to rerun the previous search; false in cases 1-3 because we are just updating the form 511 */ 512 @Override 513 public boolean performCustomAction(boolean ignoreErrors) { 514 //boolean isConfigAction = isAdvancedSearch() || isSuperUserSearch() || isClearSavedSearch(); 515 if (isClearSavedSearch()) { 516 KEWServiceLocator.getDocumentSearchService().clearNamedSearches(GlobalVariables.getUserSession().getPrincipalId()); 517 return false; 518 } 519 else if (getSavedSearchName() != null) { 520 return loadSavedSearch(ignoreErrors); 521 } else { 522 toggleFormView(); 523 // Finally, return false to prevent the search from being performed and to skip the other custom processing below. 524 return false; 525 } 526 } 527 528 /** 529 * Custom implementation of getInquiryUrl that sets up doc handler link. 530 */ 531 @Override 532 public HtmlData getInquiryUrl(BusinessObject bo, String propertyName) { 533 DocumentSearchCriteriaBo criteriaBo = (DocumentSearchCriteriaBo)bo; 534 if (KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_DOCUMENT_ID.equals(propertyName)) { 535 return generateDocumentHandlerUrl(criteriaBo.getDocumentId(), criteriaBo.getDocumentType(), 536 isSuperUserSearch()); 537 } else if (KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_ROUTE_LOG.equals(propertyName)) { 538 return generateRouteLogUrl(criteriaBo.getDocumentId(), criteriaBo.getDocumentType()); 539 } else if (KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_INITIATOR_DISPLAY_NAME.equals(propertyName)) { 540 return generateInitiatorUrl(criteriaBo.getInitiatorPrincipalId(), criteriaBo.getDocumentType()); 541 } 542 return super.getInquiryUrl(bo, propertyName); 543 } 544 545 /** 546 * Generates the appropriate document handler url for the given document. If superUserSearch is true then a super 547 * user doc handler link will be generated if the document type policy allows it. 548 */ 549 protected HtmlData.AnchorHtmlData generateDocumentHandlerUrl(String documentId, DocumentType documentType, boolean superUserSearch) { 550 HtmlData.AnchorHtmlData link = new HtmlData.AnchorHtmlData(); 551 link.setDisplayText(documentId); 552 link.setTarget(this.targets.getDocumentTarget(documentType.getName())); 553 String url = ConfigContext.getCurrentContextConfig().getProperty(Config.KEW_URL) + "/"; 554 if (superUserSearch) { 555 if (documentType.getUseWorkflowSuperUserDocHandlerUrl().getPolicyValue().booleanValue()) { 556 url += "SuperUser.do?methodToCall=displaySuperUserDocument&documentId=" + documentId; 557 } else { 558 url = KewApiConstants.DOC_HANDLER_REDIRECT_PAGE 559 + "?" + KewApiConstants.COMMAND_PARAMETER + "=" 560 + KewApiConstants.SUPERUSER_COMMAND + "&" 561 + KewApiConstants.DOCUMENT_ID_PARAMETER + "=" 562 + documentId; 563 } 564 } else { 565 url += KewApiConstants.DOC_HANDLER_REDIRECT_PAGE + "?" 566 + KewApiConstants.COMMAND_PARAMETER + "=" 567 + KewApiConstants.DOCSEARCH_COMMAND + "&" 568 + KewApiConstants.DOCUMENT_ID_PARAMETER + "=" 569 + documentId; 570 } 571 link.setHref(url); 572 return link; 573 } 574 575 protected HtmlData.AnchorHtmlData generateRouteLogUrl(String documentId, DocumentType documentType) { 576 HtmlData.AnchorHtmlData link = new HtmlData.AnchorHtmlData(); 577 link.setTarget(this.targets.getRouteLogTarget(documentType.getName())); 578 link.setDisplayText("Route Log for document " + documentId); 579 String url = ConfigContext.getCurrentContextConfig().getProperty(Config.KEW_URL) + "/" + 580 "RouteLog.do?documentId=" + documentId; 581 // if the link is not set to open in new window, tell it to render back button 582 if (!"_blank".equals(link.getTarget())) { 583 url += "&showBackButton=true"; 584 } 585 link.setHref(url); 586 return link; 587 } 588 589 protected HtmlData.AnchorHtmlData generateInitiatorUrl(String principalId, DocumentType documentType) { 590 HtmlData.AnchorHtmlData link = new HtmlData.AnchorHtmlData(); 591 if (StringUtils.isBlank(principalId) ) { 592 return link; 593 } 594 link.setTarget(this.targets.getRouteLogTarget(documentType.getName())); 595 link.setDisplayText("Initiator Inquiry for User with ID:" + principalId); 596 String url = ConfigContext.getCurrentContextConfig().getProperty(Config.KIM_URL) + "/" + 597 "identityManagementPersonInquiry.do?principalId=" + principalId; 598 link.setHref(url); 599 return link; 600 } 601 602 /** 603 * Returns true if the document handler should open in a new window. 604 */ 605 protected boolean isDocumentHandlerPopup() { 606 return BooleanUtils.toBooleanDefaultIfNull( 607 CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean( 608 KewApiConstants.KEW_NAMESPACE, 609 KRADConstants.DetailTypes.DOCUMENT_SEARCH_DETAIL_TYPE, 610 KewApiConstants.DOCUMENT_SEARCH_DOCUMENT_POPUP_IND), 611 DOCUMENT_HANDLER_POPUP_DEFAULT); 612 } 613 614 /** 615 * Returns true if the route log should open in a new window. 616 */ 617 public boolean isRouteLogPopup() { 618 return BooleanUtils.toBooleanDefaultIfNull( 619 CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(KewApiConstants.KEW_NAMESPACE, 620 KRADConstants.DetailTypes.DOCUMENT_SEARCH_DETAIL_TYPE, 621 KewApiConstants.DOCUMENT_SEARCH_ROUTE_LOG_POPUP_IND), ROUTE_LOG_POPUP_DEFAULT); 622 } 623 624 /** 625 * Parses a boolean request parameter 626 */ 627 protected boolean isFlagSet(String flagName) { 628 if(this.getParameters().containsKey(flagName)) { 629 String[] params = (String[])this.getParameters().get(flagName); 630 if (ArrayUtils.isNotEmpty(params)) { 631 return "YES".equalsIgnoreCase(params[0]); 632 } 633 } 634 return false; 635 } 636 637 /** 638 * Returns true if the current search being executed is a super user search. 639 */ 640 protected boolean isSuperUserSearch() { 641 return isFlagSet(DocumentSearchCriteriaProcessorKEWAdapter.SUPERUSER_SEARCH_FIELD); 642 } 643 644 /** 645 * Returns true if the current search being executed is an "advanced" search. 646 */ 647 protected boolean isAdvancedSearch() { 648 return isFlagSet(KRADConstants.ADVANCED_SEARCH_FIELD); 649 } 650 651 /** 652 * Returns true if the current "search" being executed is an "clear" search. 653 */ 654 protected boolean isClearSavedSearch() { 655 return isFlagSet(DocumentSearchCriteriaProcessorKEWAdapter.CLEARSAVED_SEARCH_FIELD); 656 } 657 658 protected String getSavedSearchName() { 659 String[] savedSearchName = getParameters().get(SAVED_SEARCH_NAME_PARAM); 660 if (savedSearchName != null && savedSearchName.length > 0) { 661 return savedSearchName[0]; 662 } 663 return null; 664 } 665 666 /** 667 * Override setRows in order to post-process and add documenttype-dependent fields 668 */ 669 @Override 670 protected void setRows() { 671 this.setRows(null); 672 } 673 674 /** 675 * Returns wrapper around current form fields 676 */ 677 protected FormFields getFormFields() { 678 return new FormFields(this.getRows()); 679 } 680 681 /** 682 * Sets the rows for the search criteria. This method will delegate to the DocumentSearchCriteriaProcessor 683 * in order to pull in fields for custom search attributes. 684 * 685 * @param documentTypeName the name of the document type currently entered on the form, if this is a valid document 686 * type then it may have search attribute fields that need to be displayed; documentType name may also be loaded 687 * via a saved search 688 */ 689 protected void setRows(String documentTypeName) { 690 // Always call superclass to regenerate the rows since state may have changed (namely, documentTypeName parsed from params) 691 super.setRows(); 692 693 List<Row> lookupRows = new ArrayList<Row>(); 694 //copy the current rows 695 for (Row row : getRows()) { 696 lookupRows.add(row); 697 } 698 //clear out 699 getRows().clear(); 700 701 DocumentType docType = getValidDocumentType(documentTypeName); 702 703 boolean advancedSearch = isAdvancedSearch(); 704 boolean superUserSearch = isSuperUserSearch(); 705 706 //call get rows 707 List<Row> rows = getDocumentSearchCriteriaProcessor().getRows(docType,lookupRows, advancedSearch, superUserSearch); 708 addTargetSpecRows(rows); 709 addShowSuperUserButtonRow(rows); 710 711 BusinessObjectEntry boe = (BusinessObjectEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(this.getBusinessObjectClass().getName()); 712 int numCols = boe.getLookupDefinition().getNumOfColumns(); 713 if(numCols == 0) { 714 numCols = KRADConstants.DEFAULT_NUM_OF_COLUMNS; 715 } 716 717 super.getRows().addAll(FieldUtils.wrapFields(new FormFields(rows).getFieldList(), numCols)); 718 719 } 720 721 /** 722 * Add a hidden field to hold the "targetSpec" 723 */ 724 private void addTargetSpecRows(List<Row> rows) { 725 Field documentTargetSpecField = new Field(); 726 documentTargetSpecField.setPropertyName(DOCUMENT_TARGET_SPEC_PARAM); 727 documentTargetSpecField.setPropertyValue(StringUtils.join(getParameters().get(DOCUMENT_TARGET_SPEC_PARAM), ",")); 728 documentTargetSpecField.setFieldType(Field.HIDDEN); 729 730 Field routeLogTargetSpecField = new Field(); 731 routeLogTargetSpecField.setPropertyName(ROUTE_LOG_TARGET_SPEC_PARAM); 732 routeLogTargetSpecField.setPropertyValue(StringUtils.join(getParameters().get(ROUTE_LOG_TARGET_SPEC_PARAM), ",")); 733 routeLogTargetSpecField.setFieldType(Field.HIDDEN); 734 735 Row hiddenRow = new Row(); 736 hiddenRow.setHidden(true); 737 hiddenRow.setFields(Arrays.asList(documentTargetSpecField, routeLogTargetSpecField)); 738 rows.add(hiddenRow); 739 } 740 741 /** 742 * Add a hidden field to hold the "showSuperUserButton" 743 */ 744 private void addShowSuperUserButtonRow(List<Row> rows) { 745 Field showSuperUserButtonField = new Field(); 746 showSuperUserButtonField.setPropertyName(SHOW_SUPER_USER_BUTTON_PARAM); 747 showSuperUserButtonField.setPropertyValue(getParameters().get(SHOW_SUPER_USER_BUTTON_PARAM)); 748 showSuperUserButtonField.setFieldType(Field.HIDDEN); 749 750 Row hiddenRow = new Row(); 751 hiddenRow.setHidden(true); 752 hiddenRow.setFields(Collections.singletonList(showSuperUserButtonField)); 753 rows.add(hiddenRow); 754 } 755 756 /** 757 * Checks for a valid document type with the given name in a case-sensitive manner. 758 * 759 * @return the DocumentType which matches the given name or null if no valid document type could be found 760 */ 761 private static DocumentType getValidDocumentType(String documentTypeName) { 762 if (StringUtils.isNotEmpty(documentTypeName)) { 763 DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByNameCaseInsensitive(documentTypeName.trim()); 764 if (documentType != null && documentType.isActive()) { 765 return documentType; 766 } 767 } 768 return null; 769 } 770 771 private static String TOGGLE_BUTTON = "<input type='image' name=''{0}'' id=''{0}'' class='tinybutton' src=''..{1}/images/tinybutton-{2}search.gif'' alt=''{3} search'' title=''{3} search''/>"; 772 773 @Override 774 public String getSupplementalMenuBar() { 775 boolean advancedSearch = isAdvancedSearch(); 776 boolean superUserSearch = isSuperUserSearch(); 777 StringBuilder suppMenuBar = new StringBuilder(); 778 779 // Add the detailed-search-toggling button. 780 // to mimic previous behavior, basic search button is shown both when currently rendering detailed search AND super user search 781 // as super user search is essentially a detailed search 782 String type = advancedSearch ? "basic" : "detailed"; 783 suppMenuBar.append(MessageFormat.format(TOGGLE_BUTTON, "toggleAdvancedSearch", KewApiConstants.WEBAPP_DIRECTORY, type, type)); 784 785 if (showSuperUserButton() || superUserSearch) { 786 // Add the superuser-search-toggling button. 787 suppMenuBar.append(" "); 788 suppMenuBar.append(MessageFormat.format(TOGGLE_BUTTON, "toggleSuperUserSearch", KewApiConstants.WEBAPP_DIRECTORY, superUserSearch ? "nonsupu" : "superuser", superUserSearch ? "non-superuser" : "superuser")); 789 } 790 791 // Add the "clear saved searches" button. 792 suppMenuBar.append(" "); 793 suppMenuBar.append(MessageFormat.format(TOGGLE_BUTTON, DocumentSearchCriteriaProcessorKEWAdapter.CLEARSAVED_SEARCH_FIELD, KewApiConstants.WEBAPP_DIRECTORY, "clearsaved", "clear saved searches")); 794 795 // Wire up the onblur for document type name 796 suppMenuBar.append("<script type=\"text/javascript\">" 797 + " jQuery(document).ready(function () {" 798 + " jQuery(\"#documentTypeName\").blur(function () { validateDocTypeAndRefresh( this ); });" 799 + "});</script>"); 800 801 return suppMenuBar.toString(); 802 } 803 804 private boolean showSuperUserButton() { 805 // because of the way a "refresh" works after a lookup, the super user button will not be in a parameter, so we 806 // have to check the current field value first 807 Field field = getFormFields().getField(SHOW_SUPER_USER_BUTTON_PARAM); 808 String propertyValue = field.getPropertyValue(); 809 if (!StringUtils.isBlank(propertyValue)) { 810 return Boolean.parseBoolean(propertyValue); 811 } 812 // now fall back to checking the parameters 813 String[] showSuperUserButton = getParameters().get(SHOW_SUPER_USER_BUTTON_PARAM); 814 return showSuperUserButton == null || showSuperUserButton.length == 0 || !"false".equals(showSuperUserButton[0]); 815 } 816 817 @Override 818 public boolean shouldDisplayHeaderNonMaintActions() { 819 return true; 820 } 821 822 @Override 823 public boolean shouldDisplayLookupCriteria() { 824 return true; 825 } 826 827 /** 828 * Determines if there should be more search fields rendered based on already entered search criteria, and 829 * generates additional form rows. 830 */ 831 @Override 832 public boolean checkForAdditionalFields(Map<String, String> fieldValues) { 833 return checkForAdditionalFieldsForDocumentType(fieldValues.get(DOCUMENT_TYPE_NAME_PARAM)); 834 } 835 836 private boolean checkForAdditionalFieldsMultiValued(Map<String, String[]> fieldValues) { 837 String[] valArray = fieldValues.get(DOCUMENT_TYPE_NAME_PARAM); 838 String val = null; 839 if (valArray != null && valArray.length > 0) { 840 val = valArray[0]; 841 } 842 return checkForAdditionalFieldsForDocumentType(val); 843 } 844 845 private boolean checkForAdditionalFieldsForDocumentType(String documentTypeName) { 846 if (StringUtils.isNotBlank(documentTypeName)) { 847 setRows(documentTypeName); 848 } 849 return true; 850 } 851 852 @Override 853 public Field getExtraField() { 854 SavedSearchValuesFinder savedSearchValuesFinder = new SavedSearchValuesFinder(); 855 List<KeyValue> savedSearchValues = savedSearchValuesFinder.getKeyValues(); 856 Field savedSearch = new Field(); 857 savedSearch.setPropertyName(SAVED_SEARCH_NAME_PARAM); 858 savedSearch.setFieldType(Field.DROPDOWN_SCRIPT); 859 savedSearch.setScript("customLookupChanged()"); 860 savedSearch.setFieldValidValues(savedSearchValues); 861 savedSearch.setFieldLabel("Saved Searches"); 862 return savedSearch; 863 } 864 865 @Override 866 public void performClear(LookupForm lookupForm) { 867 //KULRICE-7709 Convert dateCreated value to range before loadCriteria 868 Map<String, String> formFields = LookupUtils.preProcessRangeFields(lookupForm.getFields()); 869 DocumentSearchCriteria criteria = loadCriteria(formFields); 870 super.performClear(lookupForm); 871 repopulateSearchTypeFlags(); 872 DocumentType documentType = getValidDocumentType(criteria.getDocumentTypeName()); 873 if (documentType != null) { 874 DocumentSearchCriteria clearedCriteria = documentSearchService.clearCriteria(documentType, criteria); 875 applyCriteriaChangesToFields(DocumentSearchCriteria.Builder.create(clearedCriteria)); 876 } 877 } 878 879 /** 880 * Repopulate the fields indicating advanced/superuser search type. 881 */ 882 protected void repopulateSearchTypeFlags() { 883 boolean advancedSearch = isAdvancedSearch(); 884 boolean superUserSearch = isSuperUserSearch(); 885 boolean showSuperUserButton = showSuperUserButton(); 886 Map<String, String[]> values = new HashMap<String, String[]>(); 887 values.put(KRADConstants.ADVANCED_SEARCH_FIELD, new String[] { advancedSearch ? "YES" : "NO" }); 888 values.put(DocumentSearchCriteriaProcessorKEWAdapter.SUPERUSER_SEARCH_FIELD, new String[] { superUserSearch ? "YES" : "NO" }); 889 values.put(SHOW_SUPER_USER_BUTTON_PARAM, new String[] { Boolean.toString(showSuperUserButton) }); 890 getFormFields().setFieldValues(values); 891 } 892 893 /** 894 * Takes a collection of result rows and does final processing on them. 895 */ 896 protected void postProcessResults(Collection<ResultRow> resultRows, DocumentSearchResults searchResults) { 897 if (resultRows.size() != searchResults.getSearchResults().size()) { 898 throw new IllegalStateException("Encountered a mismatch between ResultRow items and document search results " 899 + resultRows.size() + " != " + searchResults.getSearchResults().size()); 900 } 901 DocumentType documentType = getValidDocumentType(criteria.getDocumentTypeName()); 902 DocumentSearchResultSetConfiguration resultSetConfiguration = null; 903 DocumentSearchCriteriaConfiguration criteriaConfiguration = null; 904 if (documentType != null) { 905 resultSetConfiguration = KEWServiceLocator.getDocumentSearchCustomizationMediator().customizeResultSetConfiguration(documentType, criteria); 906 criteriaConfiguration = KEWServiceLocator.getDocumentSearchCustomizationMediator().getDocumentSearchCriteriaConfiguration(documentType); 907 } 908 int index = 0; 909 for (ResultRow resultRow : resultRows) { 910 DocumentSearchResult searchResult = searchResults.getSearchResults().get(index); 911 executeColumnCustomization(resultRow, searchResult, resultSetConfiguration, criteriaConfiguration); 912 index++; 913 } 914 } 915 916 /** 917 * Executes customization of columns, could include removing certain columns or adding additional columns to the 918 * result row (in cases where columns are added by document search customization, such as searchable attributes). 919 */ 920 protected void executeColumnCustomization(ResultRow resultRow, DocumentSearchResult searchResult, 921 DocumentSearchResultSetConfiguration resultSetConfiguration, 922 DocumentSearchCriteriaConfiguration criteriaConfiguration) { 923 if (resultSetConfiguration == null) { 924 resultSetConfiguration = DocumentSearchResultSetConfiguration.Builder.create().build(); 925 } 926 if (criteriaConfiguration == null) { 927 criteriaConfiguration = DocumentSearchCriteriaConfiguration.Builder.create().build(); 928 } 929 List<StandardResultField> standardFieldsToRemove = resultSetConfiguration.getStandardResultFieldsToRemove(); 930 if (standardFieldsToRemove == null) { 931 standardFieldsToRemove = Collections.emptyList(); 932 } 933 List<Column> newColumns = new ArrayList<Column>(); 934 for (Column standardColumn : resultRow.getColumns()) { 935 if (!standardFieldsToRemove.contains(StandardResultField.fromFieldName(standardColumn.getPropertyName()))) { 936 newColumns.add(standardColumn); 937 // modify the route log column so that xml values are not escaped (allows for the route log <img ...> to be 938 // rendered properly) 939 if (standardColumn.getPropertyName().equals( 940 KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_ROUTE_LOG)) { 941 standardColumn.setEscapeXMLValue(false); 942 } 943 } 944 } 945 946 // determine which document attribute fields should be added 947 List<RemotableAttributeField> searchAttributeFields = criteriaConfiguration.getFlattenedSearchAttributeFields(); 948 List<String> additionalFieldNamesToInclude = new ArrayList<String>(); 949 if (!resultSetConfiguration.isOverrideSearchableAttributes()) { 950 for (RemotableAttributeField searchAttributeField : searchAttributeFields) { 951 // TODO - KULRICE-5738 - add check here to make sure the searchable attribute should be displayed in result set 952 // right now this is default always including all searchable attributes! 953 if (searchAttributeField.getAttributeLookupSettings() == null || 954 searchAttributeField.getAttributeLookupSettings().isInResults()) { 955 additionalFieldNamesToInclude.add(searchAttributeField.getName()); 956 } 957 } 958 } 959 if (resultSetConfiguration.getCustomFieldNamesToAdd() != null) { 960 additionalFieldNamesToInclude.addAll(resultSetConfiguration.getCustomFieldNamesToAdd()); 961 } 962 963 // now assemble the custom columns 964 List<Column> customColumns = new ArrayList<Column>(); 965 List<Column> additionalAttributeColumns = FieldUtils.constructColumnsFromAttributeFields(resultSetConfiguration.getAdditionalAttributeFields()); 966 967 outer:for (String additionalFieldNameToInclude : additionalFieldNamesToInclude) { 968 // search the search attribute fields 969 for (RemotableAttributeField searchAttributeField : searchAttributeFields) { 970 if (additionalFieldNameToInclude.equals(searchAttributeField.getName())) { 971 Column searchAttributeColumn = FieldUtils.constructColumnFromAttributeField(searchAttributeField); 972 wrapDocumentAttributeColumnName(searchAttributeColumn); 973 customColumns.add(searchAttributeColumn); 974 continue outer; 975 } 976 } 977 for (Column additionalAttributeColumn : additionalAttributeColumns) { 978 if (additionalFieldNameToInclude.equals(additionalAttributeColumn.getPropertyName())) { 979 wrapDocumentAttributeColumnName(additionalAttributeColumn); 980 customColumns.add(additionalAttributeColumn); 981 continue outer; 982 } 983 } 984 LOG.warn("Failed to locate a proper column definition for requested additional field to include in" 985 + "result set with name '" 986 + additionalFieldNameToInclude 987 + "'"); 988 } 989 populateCustomColumns(customColumns, searchResult); 990 991 // if there is an action custom column, always put that before any other field 992 for (Column column : customColumns){ 993 if (column.getColumnTitle().equals(KRADConstants.ACTIONS_COLUMN_TITLE)){ 994 newColumns.add(0, column); 995 customColumns.remove(column); 996 break; 997 } 998 } 999 1000 // now merge the custom columns into the standard columns right before the route log (if the route log column wasn't removed!) 1001 if (newColumns.isEmpty() || !StandardResultField.ROUTE_LOG.isFieldNameValid(newColumns.get(newColumns.size() - 1).getPropertyName())) { 1002 newColumns.addAll(customColumns); 1003 } else { 1004 newColumns.addAll(newColumns.size() - 1, customColumns); 1005 } 1006 resultRow.setColumns(newColumns); 1007 } 1008 1009 protected void populateCustomColumns(List<Column> customColumns, DocumentSearchResult searchResult) { 1010 for (Column customColumn : customColumns) { 1011 DocumentAttribute documentAttribute = searchResult.getSingleDocumentAttributeByName(customColumn.getPropertyName()); 1012 if (documentAttribute != null && documentAttribute.getValue() != null) { 1013 wrapDocumentAttributeColumnName(customColumn); 1014 // list moving forward if the attribute has more than one value 1015 Formatter formatter = customColumn.getFormatter(); 1016 Object attributeValue = documentAttribute.getValue(); 1017 if (formatter.getPropertyType().equals(KualiDecimal.class) 1018 && documentAttribute.getValue() instanceof BigDecimal) { 1019 attributeValue = new KualiDecimal((BigDecimal)attributeValue); 1020 } else if (formatter.getPropertyType().equals(KualiPercent.class) 1021 && documentAttribute.getValue() instanceof BigDecimal) { 1022 attributeValue = new KualiPercent((BigDecimal)attributeValue); 1023 } 1024 customColumn.setPropertyValue(formatter.format(attributeValue).toString()); 1025 1026 //populate the custom column columnAnchor because it is used for determining if the result field is displayed 1027 //as static string or links 1028 HtmlData anchor = customColumn.getColumnAnchor(); 1029 if (anchor != null && anchor instanceof HtmlData.AnchorHtmlData){ 1030 HtmlData.AnchorHtmlData anchorHtml = (HtmlData.AnchorHtmlData)anchor; 1031 if (StringUtils.isEmpty(anchorHtml.getHref()) && StringUtils.isEmpty(anchorHtml.getTitle())){ 1032 customColumn.setColumnAnchor(new HtmlData.AnchorHtmlData(formatter.format(attributeValue).toString(), documentAttribute.getName())); 1033 } 1034 } 1035 } 1036 } 1037 } 1038 1039 private void wrapDocumentAttributeColumnName(Column column) { 1040 // TODO - comment out for now, not sure we really want to do this... 1041 //column.setPropertyName(DOCUMENT_ATTRIBUTE_PROPERTY_NAME_PREFIX + column.getPropertyName()); 1042 } 1043 1044 public void setDocumentSearchService(DocumentSearchService documentSearchService) { 1045 this.documentSearchService = documentSearchService; 1046 } 1047 1048 public DocumentSearchService getDocumentSearchService() { 1049 return documentSearchService; 1050 } 1051 1052 public DocumentSearchCriteriaProcessor getDocumentSearchCriteriaProcessor() { 1053 return documentSearchCriteriaProcessor; 1054 } 1055 1056 public void setDocumentSearchCriteriaProcessor(DocumentSearchCriteriaProcessor documentSearchCriteriaProcessor) { 1057 this.documentSearchCriteriaProcessor = documentSearchCriteriaProcessor; 1058 } 1059 1060 public DocumentSearchCriteriaTranslator getDocumentSearchCriteriaTranslator() { 1061 return documentSearchCriteriaTranslator; 1062 } 1063 1064 public void setDocumentSearchCriteriaTranslator(DocumentSearchCriteriaTranslator documentSearchCriteriaTranslator) { 1065 this.documentSearchCriteriaTranslator = documentSearchCriteriaTranslator; 1066 } 1067}