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("&nbsp;");
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("&nbsp;");
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}