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