Coverage Report - org.kuali.rice.kew.docsearch.service.impl.DocumentSearchServiceImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
DocumentSearchServiceImpl
0%
0/278
0%
0/156
4.069
 
 1  
 /**
 2  
  * Copyright 2005-2011 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  * http://www.opensource.org/licenses/ecl2.php
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.kuali.rice.kew.docsearch.service.impl;
 17  
 
 18  
 import org.apache.commons.collections.CollectionUtils;
 19  
 import org.apache.commons.lang.StringUtils;
 20  
 import org.joda.time.DateTime;
 21  
 import org.joda.time.MutableDateTime;
 22  
 import org.kuali.rice.core.api.CoreApiServiceLocator;
 23  
 import org.kuali.rice.core.api.config.property.ConfigContext;
 24  
 import org.kuali.rice.core.api.config.property.ConfigurationService;
 25  
 import org.kuali.rice.core.api.reflect.ObjectDefinition;
 26  
 import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
 27  
 import org.kuali.rice.core.api.uif.RemotableAttributeError;
 28  
 import org.kuali.rice.core.api.uif.RemotableAttributeField;
 29  
 import org.kuali.rice.core.api.util.ConcreteKeyValue;
 30  
 import org.kuali.rice.core.api.util.KeyValue;
 31  
 import org.kuali.rice.kew.api.WorkflowRuntimeException;
 32  
 import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
 33  
 import org.kuali.rice.kew.api.document.search.DocumentSearchResult;
 34  
 import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
 35  
 import org.kuali.rice.kew.docsearch.DocumentSearchInternalUtils;
 36  
 import org.kuali.rice.kew.docsearch.DocumentSearchCustomizationMediator;
 37  
 import org.kuali.rice.kew.framework.document.search.AttributeFields;
 38  
 import org.kuali.rice.kew.api.document.attribute.DocumentAttribute;
 39  
 import org.kuali.rice.kew.api.document.attribute.DocumentAttributeFactory;
 40  
 import org.kuali.rice.kew.docsearch.dao.DocumentSearchDAO;
 41  
 import org.kuali.rice.kew.docsearch.service.DocumentSearchService;
 42  
 import org.kuali.rice.kew.doctype.SecuritySession;
 43  
 import org.kuali.rice.kew.doctype.bo.DocumentType;
 44  
 import org.kuali.rice.kew.exception.WorkflowServiceError;
 45  
 import org.kuali.rice.kew.exception.WorkflowServiceErrorException;
 46  
 import org.kuali.rice.kew.exception.WorkflowServiceErrorImpl;
 47  
 import org.kuali.rice.kew.framework.document.search.DocumentSearchCriteriaConfiguration;
 48  
 import org.kuali.rice.kew.framework.document.search.DocumentSearchResultValue;
 49  
 import org.kuali.rice.kew.framework.document.search.DocumentSearchResultValues;
 50  
 import org.kuali.rice.kew.impl.document.search.DocumentSearchGenerator;
 51  
 import org.kuali.rice.kew.impl.document.search.DocumentSearchGeneratorImpl;
 52  
 import org.kuali.rice.kew.service.KEWServiceLocator;
 53  
 import org.kuali.rice.kew.useroptions.UserOptions;
 54  
 import org.kuali.rice.kew.useroptions.UserOptionsService;
 55  
 import org.kuali.rice.kew.api.KewApiConstants;
 56  
 import org.kuali.rice.kim.api.group.Group;
 57  
 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
 58  
 import org.kuali.rice.krad.service.KRADServiceLocator;
 59  
 import org.kuali.rice.krad.util.GlobalVariables;
 60  
 
 61  
 import java.io.IOException;
 62  
 import java.util.ArrayList;
 63  
 import java.util.Collection;
 64  
 import java.util.Collections;
 65  
 import java.util.HashMap;
 66  
 import java.util.HashSet;
 67  
 import java.util.LinkedHashMap;
 68  
 import java.util.List;
 69  
 import java.util.Map;
 70  
 import java.util.Set;
 71  
 
 72  0
 public class DocumentSearchServiceImpl implements DocumentSearchService {
 73  
 
 74  0
         private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DocumentSearchServiceImpl.class);
 75  
 
 76  
         private static final int MAX_SEARCH_ITEMS = 5;
 77  
         private static final String LAST_SEARCH_ORDER_OPTION = "DocSearch.LastSearch.Order";
 78  
         private static final String NAMED_SEARCH_ORDER_BASE = "DocSearch.NamedSearch.";
 79  
         private static final String LAST_SEARCH_BASE_NAME = "DocSearch.LastSearch.Holding";
 80  
 
 81  
         private volatile ConfigurationService kualiConfigurationService;
 82  
     private DocumentSearchCustomizationMediator documentSearchCustomizationMediator;
 83  
 
 84  
         private DocumentSearchDAO docSearchDao;
 85  
         private UserOptionsService userOptionsService;
 86  
 
 87  
         public void setDocumentSearchDAO(DocumentSearchDAO docSearchDao) {
 88  0
                 this.docSearchDao = docSearchDao;
 89  0
         }
 90  
 
 91  
         public void setUserOptionsService(UserOptionsService userOptionsService) {
 92  0
                 this.userOptionsService = userOptionsService;
 93  0
         }
 94  
 
 95  
     public void setDocumentSearchCustomizationMediator(DocumentSearchCustomizationMediator documentSearchCustomizationMediator) {
 96  0
         this.documentSearchCustomizationMediator = documentSearchCustomizationMediator;
 97  0
     }
 98  
 
 99  
     protected DocumentSearchCustomizationMediator getDocumentSearchCustomizationMediator() {
 100  0
         return this.documentSearchCustomizationMediator;
 101  
     }
 102  
 
 103  
         public void clearNamedSearches(String principalId) {
 104  0
                 String[] clearListNames = { NAMED_SEARCH_ORDER_BASE + "%", LAST_SEARCH_BASE_NAME + "%", LAST_SEARCH_ORDER_OPTION + "%" };
 105  0
         for (String clearListName : clearListNames)
 106  
         {
 107  0
             List<UserOptions> records = userOptionsService.findByUserQualified(principalId, clearListName);
 108  0
             for (UserOptions userOptions : records) {
 109  0
                 userOptionsService.deleteUserOptions((UserOptions) userOptions);
 110  
             }
 111  
         }
 112  0
         }
 113  
 
 114  
     public DocumentSearchCriteria getNamedSearchCriteria(String principalId, String searchName) {
 115  0
         return getSavedSearchCriteria(principalId, NAMED_SEARCH_ORDER_BASE + searchName);
 116  
     }
 117  
 
 118  
     public DocumentSearchCriteria getSavedSearchCriteria(String principalId, String searchName) {
 119  0
         UserOptions savedSearch = userOptionsService.findByOptionId(searchName, principalId);
 120  0
         if (savedSearch == null) {
 121  0
             return null;
 122  
         }
 123  0
         return getCriteriaFromSavedSearch(savedSearch);
 124  
     }
 125  
 
 126  
     protected DocumentSearchCriteria getCriteriaFromSavedSearch(UserOptions savedSearch) {
 127  0
         String optionValue = savedSearch.getOptionVal();
 128  
         try {
 129  0
             return DocumentSearchInternalUtils.unmarshalDocumentSearchCriteria(optionValue);
 130  0
         } catch (IOException e) {
 131  0
             throw new WorkflowRuntimeException("Failed to load saved search for name '" + savedSearch.getOptionId() + "'", e);
 132  
         }
 133  
     }
 134  
 
 135  
     private String getOptionCriteriaField(UserOptions userOption, String fieldName) {
 136  0
         String value = userOption.getOptionVal();
 137  0
         if (value != null) {
 138  0
             String[] fields = value.split(",,");
 139  0
             for (String field : fields)
 140  
             {
 141  0
                 if (field.startsWith(fieldName + "="))
 142  
                 {
 143  0
                     return field.substring(field.indexOf(fieldName) + fieldName.length() + 1, field.length());
 144  
                 }
 145  
             }
 146  
         }
 147  0
         return null;
 148  
     }
 149  
 
 150  
     @Override
 151  
         public DocumentSearchResults lookupDocuments(String principalId, DocumentSearchCriteria criteria) {
 152  0
                 DocumentSearchGenerator docSearchGenerator = getStandardDocumentSearchGenerator();
 153  0
                 DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(criteria.getDocumentTypeName());
 154  0
         DocumentSearchCriteria.Builder criteriaBuilder = DocumentSearchCriteria.Builder.create(criteria);
 155  0
         validateDocumentSearchCriteria(docSearchGenerator, criteriaBuilder);
 156  0
         DocumentSearchCriteria builtCriteria = applyCriteriaCustomizations(documentType, criteriaBuilder.build());
 157  0
         builtCriteria = applyCriteriaDefaults(builtCriteria);
 158  0
         boolean criteriaModified = !criteria.equals(builtCriteria);
 159  0
         List<RemotableAttributeField> searchFields = determineSearchFields(documentType);
 160  0
         DocumentSearchResults.Builder searchResults = docSearchDao.findDocuments(docSearchGenerator, builtCriteria, criteriaModified, searchFields);
 161  0
         if (documentType != null) {
 162  0
             DocumentSearchResultValues resultValues = getDocumentSearchCustomizationMediator().customizeResults(documentType, builtCriteria, searchResults.build());
 163  0
             if (resultValues != null && CollectionUtils.isNotEmpty(resultValues.getResultValues())) {
 164  0
                 Map<String, DocumentSearchResultValue> resultValueMap = new HashMap<String, DocumentSearchResultValue>();
 165  0
                 for (DocumentSearchResultValue resultValue : resultValues.getResultValues()) {
 166  0
                     resultValueMap.put(resultValue.getDocumentId(), resultValue);
 167  
                 }
 168  0
                 for (DocumentSearchResult.Builder result : searchResults.getSearchResults()) {
 169  0
                     DocumentSearchResultValue value = resultValueMap.get(result.getDocument().getDocumentId());
 170  0
                     if (value != null) {
 171  0
                         applyResultCustomization(result, value);
 172  
                     }
 173  0
                 }
 174  
             }
 175  
         }
 176  
 
 177  0
         if (StringUtils.isNotBlank(principalId) && !searchResults.getSearchResults().isEmpty()) {
 178  0
             DocumentSearchResults builtResults = searchResults.build();
 179  0
             Set<String> authorizedDocumentIds = KEWServiceLocator.getDocumentSecurityService().documentSearchResultAuthorized(
 180  
                     principalId, builtResults, new SecuritySession(principalId));
 181  0
             if (CollectionUtils.isNotEmpty(authorizedDocumentIds)) {
 182  0
                 int numFiltered = 0;
 183  0
                 List<DocumentSearchResult.Builder> finalResults = new ArrayList<DocumentSearchResult.Builder>();
 184  0
                 for (DocumentSearchResult.Builder result : searchResults.getSearchResults()) {
 185  0
                     if (authorizedDocumentIds.contains(result.getDocument().getDocumentId())) {
 186  0
                         finalResults.add(result);
 187  
                     } else {
 188  0
                         numFiltered++;
 189  
                     }
 190  
                 }
 191  0
                 searchResults.setSearchResults(finalResults);
 192  0
                 searchResults.setNumberOfSecurityFilteredResults(numFiltered);
 193  0
             } else {
 194  0
                 searchResults.setNumberOfSecurityFilteredResults(searchResults.getSearchResults().size());
 195  0
                 searchResults.setSearchResults(Collections.<DocumentSearchResult.Builder>emptyList());
 196  
             }
 197  
         }
 198  0
         saveSearch(principalId, builtCriteria);
 199  0
         return searchResults.build();
 200  
         }
 201  
 
 202  
     protected void applyResultCustomization(DocumentSearchResult.Builder result, DocumentSearchResultValue value) {
 203  0
         Map<String, List<DocumentAttribute.AbstractBuilder<?>>> customizedAttributeMap =
 204  
                 new LinkedHashMap<String, List<DocumentAttribute.AbstractBuilder<?>>>();
 205  0
         for (DocumentAttribute customizedAttribute : value.getDocumentAttributes()) {
 206  0
             List<DocumentAttribute.AbstractBuilder<?>> attributesForName = customizedAttributeMap.get(value.getDocumentId());
 207  0
             if (attributesForName == null) {
 208  0
                 attributesForName = new ArrayList<DocumentAttribute.AbstractBuilder<?>>();
 209  0
                 customizedAttributeMap.put(value.getDocumentId(), attributesForName);
 210  
             }
 211  0
             attributesForName.add(DocumentAttributeFactory.loadContractIntoBuilder(customizedAttribute));
 212  0
         }
 213  
         // keep track of what we've already applied customizations for, since those will replace existing attributes with that name
 214  0
         Set<String> documentAttributeNamesCustomized = new HashSet<String>();
 215  0
         List<DocumentAttribute.AbstractBuilder<?>> newDocumentAttributes = new ArrayList<DocumentAttribute.AbstractBuilder<?>>();
 216  0
         for (DocumentAttribute.AbstractBuilder<?> documentAttribute : result.getDocumentAttributes()) {
 217  0
             String name = documentAttribute.getName();
 218  0
             if (!documentAttributeNamesCustomized.contains(name) && customizedAttributeMap.containsKey(name)) {
 219  0
                 documentAttributeNamesCustomized.add(name);
 220  0
                 newDocumentAttributes.addAll(customizedAttributeMap.get(name));
 221  
             }
 222  0
         }
 223  0
     }
 224  
 
 225  
 
 226  
     /**
 227  
      * Applies any document type-specific customizations to the lookup criteria.  If no customizations are configured
 228  
      * for the document type, this method will simply return the criteria that is passed to it.  If
 229  
      * the given DocumentType is null, then this method will also simply return the criteria that is passed to it.
 230  
      */
 231  
     protected DocumentSearchCriteria applyCriteriaCustomizations(DocumentType documentType, DocumentSearchCriteria criteria) {
 232  0
         if (documentType == null) {
 233  0
             return criteria;
 234  
         }
 235  0
         DocumentSearchCriteria customizedCriteria = getDocumentSearchCustomizationMediator().customizeCriteria(documentType, criteria);
 236  0
         if (customizedCriteria != null) {
 237  0
             return customizedCriteria;
 238  
         }
 239  0
         return criteria;
 240  
     }
 241  
 
 242  
     protected DocumentSearchCriteria applyCriteriaDefaults(DocumentSearchCriteria criteria) {
 243  0
         DocumentSearchCriteria.Builder comparisonCriteria = createEmptyComparisonCriteria(criteria);
 244  0
         boolean isCriteriaEmpty = criteria.equals(comparisonCriteria.build());
 245  0
         boolean isTitleOnly = false;
 246  0
         if (!isCriteriaEmpty) {
 247  0
             comparisonCriteria.setTitle(criteria.getTitle());
 248  0
             isTitleOnly = criteria.equals(comparisonCriteria.build());
 249  
         }
 250  
 
 251  0
         if (isCriteriaEmpty || isTitleOnly) {
 252  0
             DocumentSearchCriteria.Builder criteriaBuilder = DocumentSearchCriteria.Builder.create(criteria);
 253  0
             Integer defaultCreateDateDaysAgoValue = null;
 254  0
             if (isCriteriaEmpty) {
 255  
                 // if they haven't set any criteria, default the from created date to today minus days from constant variable
 256  0
                 defaultCreateDateDaysAgoValue = KewApiConstants.DOCUMENT_SEARCH_NO_CRITERIA_CREATE_DATE_DAYS_AGO;
 257  0
             } else if (isTitleOnly) {
 258  
                 // If the document title is the only field which was entered, we want to set the "from" date to be X
 259  
                 // days ago.  This will allow for a more efficient query.
 260  0
                 defaultCreateDateDaysAgoValue = KewApiConstants.DOCUMENT_SEARCH_DOC_TITLE_CREATE_DATE_DAYS_AGO;
 261  
             }
 262  0
             if (defaultCreateDateDaysAgoValue != null) {
 263  
                 // add a default create date
 264  0
                 MutableDateTime mutableDateTime = new MutableDateTime();
 265  0
                 mutableDateTime.addDays(defaultCreateDateDaysAgoValue.intValue());
 266  0
                 criteriaBuilder.setDateCreatedFrom(mutableDateTime.toDateTime());
 267  
             }
 268  0
             criteria = criteriaBuilder.build();
 269  
         }
 270  0
         return criteria;
 271  
     }
 272  
 
 273  
     protected DocumentSearchCriteria.Builder createEmptyComparisonCriteria(DocumentSearchCriteria criteria) {
 274  0
         DocumentSearchCriteria.Builder builder = DocumentSearchCriteria.Builder.create();
 275  
         // copy over the fields that shouldn't be considered when determining if the criteria is empty
 276  0
         builder.setSaveName(criteria.getSaveName());
 277  0
         builder.setStartAtIndex(criteria.getStartAtIndex());
 278  0
         builder.setMaxResults(criteria.getMaxResults());
 279  0
         return builder;
 280  
     }
 281  
 
 282  
     protected List<RemotableAttributeField> determineSearchFields(DocumentType documentType) {
 283  0
         List<RemotableAttributeField> searchFields = new ArrayList<RemotableAttributeField>();
 284  0
         if (documentType != null) {
 285  0
             DocumentSearchCriteriaConfiguration searchConfiguration =
 286  
                     getDocumentSearchCustomizationMediator().getDocumentSearchCriteriaConfiguration(documentType);
 287  0
             if (searchConfiguration != null) {
 288  0
                 List<AttributeFields> attributeFields = searchConfiguration.getSearchAttributeFields();
 289  0
                 if (attributeFields != null) {
 290  0
                     for (AttributeFields fields : attributeFields) {
 291  0
                         searchFields.addAll(fields.getRemotableAttributeFields());
 292  
                     }
 293  
                 }
 294  
             }
 295  
         }
 296  0
         return searchFields;
 297  
     }
 298  
 
 299  
     public DocumentSearchGenerator getStandardDocumentSearchGenerator() {
 300  0
         String searchGeneratorClass = ConfigContext.getCurrentContextConfig().getProperty(KewApiConstants.STANDARD_DOC_SEARCH_GENERATOR_CLASS_CONFIG_PARM);
 301  0
         if (searchGeneratorClass == null){
 302  0
             return new DocumentSearchGeneratorImpl();
 303  
         }
 304  0
             return (DocumentSearchGenerator)GlobalResourceLoader.getObject(new ObjectDefinition(searchGeneratorClass));
 305  
     }
 306  
 
 307  
     @Override
 308  
     public void validateDocumentSearchCriteria(DocumentSearchGenerator docSearchGenerator, DocumentSearchCriteria.Builder criteria) {
 309  0
         List<WorkflowServiceError> errors = this.validateWorkflowDocumentSearchCriteria(criteria);
 310  0
         List<RemotableAttributeError> searchAttributeErrors = docSearchGenerator.validateSearchableAttributes(criteria);
 311  0
         if (!CollectionUtils.isEmpty(searchAttributeErrors)) {
 312  
             // attribute errors are fully materialized error messages, so the only "key" that makes sense is to use "error.custom"
 313  0
             for (RemotableAttributeError searchAttributeError : searchAttributeErrors) {
 314  0
                 for (String errorMessage : searchAttributeError.getErrors()) {
 315  0
                     WorkflowServiceError error = new WorkflowServiceErrorImpl(errorMessage, "error.custom", errorMessage);
 316  0
                     errors.add(error);
 317  0
                 }
 318  
             }
 319  
         }
 320  0
         if (!errors.isEmpty() || !GlobalVariables.getMessageMap().hasNoErrors()) {
 321  0
             throw new WorkflowServiceErrorException("Document Search Validation Errors", errors);
 322  
         }
 323  0
     }
 324  
 
 325  
     protected List<WorkflowServiceError> validateWorkflowDocumentSearchCriteria(DocumentSearchCriteria.Builder criteria) {
 326  0
         List<WorkflowServiceError> errors = new ArrayList<WorkflowServiceError>();
 327  
 
 328  
         // trim the principal names, validation isn't really necessary, because if not found, no results will be
 329  
         // returned.
 330  0
         criteria.setApproverPrincipalName(trimCriteriaValue(criteria.getApproverPrincipalName()));
 331  0
         criteria.setViewerPrincipalName(trimCriteriaValue(criteria.getViewerPrincipalName()));
 332  0
         criteria.setInitiatorPrincipalName(trimCriteriaValue(criteria.getInitiatorPrincipalName()));
 333  0
         validateGroupCriteria(criteria, errors);
 334  0
         criteria.setDocumentId(criteria.getDocumentId());
 335  0
         return errors;
 336  
     }
 337  
 
 338  
     private String trimCriteriaValue(String criteriaValue) {
 339  0
         if (StringUtils.isNotBlank(criteriaValue)) {
 340  0
             criteriaValue = criteriaValue.trim();
 341  
         }
 342  0
         if (StringUtils.isBlank(criteriaValue)) {
 343  0
             return null;
 344  
         }
 345  0
         return criteriaValue;
 346  
     }
 347  
 
 348  
     private void validateGroupCriteria(DocumentSearchCriteria.Builder criteria, List<WorkflowServiceError> errors) {
 349  0
         if (StringUtils.isNotBlank(criteria.getViewerGroupId())) {
 350  0
             Group group = KimApiServiceLocator.getGroupService().getGroup(criteria.getViewerGroupId());
 351  0
             if (group == null) {
 352  0
                 errors.add(new WorkflowServiceErrorImpl("Workgroup Viewer Name is not a workgroup", "docsearch.DocumentSearchService.workgroup.viewer"));
 353  
             }
 354  0
         } else {
 355  0
             criteria.setViewerGroupId(null);
 356  
         }
 357  0
     }
 358  
 
 359  
     @Override
 360  
         public List<KeyValue> getNamedSearches(String principalId) {
 361  0
                 List<UserOptions> namedSearches = userOptionsService.findByUserQualified(principalId, NAMED_SEARCH_ORDER_BASE + "%");
 362  0
                 List<KeyValue> sortedNamedSearches = new ArrayList<KeyValue>(0);
 363  0
                 if (!namedSearches.isEmpty()) {
 364  0
                         Collections.sort(namedSearches);
 365  0
                         for (UserOptions namedSearch : namedSearches) {
 366  0
                                 KeyValue keyValue = new ConcreteKeyValue(namedSearch.getOptionId(), namedSearch.getOptionId().substring(NAMED_SEARCH_ORDER_BASE.length(), namedSearch.getOptionId().length()));
 367  0
                                 sortedNamedSearches.add(keyValue);
 368  0
                         }
 369  
                 }
 370  0
                 return sortedNamedSearches;
 371  
         }
 372  
 
 373  
     @Override
 374  
         public List<KeyValue> getMostRecentSearches(String principalId) {
 375  0
                 UserOptions order = userOptionsService.findByOptionId(LAST_SEARCH_ORDER_OPTION, principalId);
 376  0
                 List<KeyValue> sortedMostRecentSearches = new ArrayList<KeyValue>();
 377  0
                 if (order != null && order.getOptionVal() != null && !"".equals(order.getOptionVal())) {
 378  0
                         List<UserOptions> mostRecentSearches = userOptionsService.findByUserQualified(principalId, LAST_SEARCH_BASE_NAME + "%");
 379  0
                         String[] ordered = order.getOptionVal().split(",");
 380  0
             for (String anOrdered : ordered) {
 381  0
                 UserOptions matchingOption = null;
 382  0
                 for (UserOptions option : mostRecentSearches) {
 383  0
                     if (anOrdered.equals(option.getOptionId())) {
 384  0
                         matchingOption = option;
 385  0
                         break;
 386  
                     }
 387  
                 }
 388  0
                 if (matchingOption != null) {
 389  0
                     DocumentSearchCriteria matchingCriteria = getCriteriaFromSavedSearch(matchingOption);
 390  0
                         sortedMostRecentSearches.add(new ConcreteKeyValue(anOrdered, getSavedSearchAbbreviatedString(matchingCriteria)));
 391  
                 }
 392  
             }
 393  
                 }
 394  0
                 return sortedMostRecentSearches;
 395  
         }
 396  
 
 397  
     public DocumentSearchCriteria clearCriteria(DocumentType documentType, DocumentSearchCriteria criteria) {
 398  0
         DocumentSearchCriteria clearedCriteria = getDocumentSearchCustomizationMediator().customizeClearCriteria(
 399  
                 documentType, criteria);
 400  0
         if (clearedCriteria == null) {
 401  0
             clearedCriteria = getStandardDocumentSearchGenerator().clearSearch(criteria);
 402  
         }
 403  0
         return clearedCriteria;
 404  
     }
 405  
 
 406  
     protected String getSavedSearchAbbreviatedString(DocumentSearchCriteria criteria) {
 407  0
         Map<String, String> abbreviatedStringMap = new LinkedHashMap<String, String>();
 408  0
         addAbbreviatedString(abbreviatedStringMap, "Doc Type", criteria.getDocumentTypeName());
 409  0
         addAbbreviatedString(abbreviatedStringMap, "Initiator", criteria.getInitiatorPrincipalName());
 410  0
         addAbbreviatedString(abbreviatedStringMap, "Doc Id", criteria.getDocumentId());
 411  0
         addAbbreviatedRangeString(abbreviatedStringMap, "Created", criteria.getDateCreatedFrom(),
 412  
                 criteria.getDateCreatedTo());
 413  0
         addAbbreviatedString(abbreviatedStringMap, "Title", criteria.getTitle());
 414  0
         addAbbreviatedString(abbreviatedStringMap, "App Doc Id", criteria.getApplicationDocumentId());
 415  0
         addAbbreviatedRangeString(abbreviatedStringMap, "Approved", criteria.getDateApprovedFrom(),
 416  
                 criteria.getDateApprovedTo());
 417  0
         addAbbreviatedRangeString(abbreviatedStringMap, "Modified", criteria.getDateLastModifiedFrom(), criteria.getDateLastModifiedTo());
 418  0
         addAbbreviatedRangeString(abbreviatedStringMap, "Finalized", criteria.getDateFinalizedFrom(), criteria.getDateFinalizedTo());
 419  0
         addAbbreviatedRangeString(abbreviatedStringMap, "App Doc Status Changed", criteria.getDateApplicationDocumentStatusChangedFrom(), criteria.getDateApplicationDocumentStatusChangedTo());
 420  0
         addAbbreviatedString(abbreviatedStringMap, "Approver", criteria.getApproverPrincipalName());
 421  0
         addAbbreviatedString(abbreviatedStringMap, "Viewer", criteria.getViewerPrincipalName());
 422  0
         addAbbreviatedString(abbreviatedStringMap, "Group Viewer", criteria.getViewerGroupId());
 423  0
         addAbbreviatedString(abbreviatedStringMap, "Node", criteria.getRouteNodeName());
 424  0
         addAbbreviatedMultiValuedString(abbreviatedStringMap, "Status", criteria.getDocumentStatuses());
 425  0
         addAbbreviatedMultiValuedString(abbreviatedStringMap, "Category", criteria.getDocumentStatusCategories());
 426  0
         for (String documentAttributeName : criteria.getDocumentAttributeValues().keySet()) {
 427  0
             addAbbreviatedMultiValuedString(abbreviatedStringMap, documentAttributeName, criteria.getDocumentAttributeValues().get(documentAttributeName));
 428  
         }
 429  0
         StringBuilder stringBuilder = new StringBuilder();
 430  0
         int iteration = 0;
 431  0
         for (String label : abbreviatedStringMap.keySet()) {
 432  0
             stringBuilder.append(label).append("=").append(abbreviatedStringMap.get(label));
 433  0
             if (iteration < abbreviatedStringMap.keySet().size()) {
 434  0
                 stringBuilder.append("; ");
 435  
             }
 436  
         }
 437  0
         return stringBuilder.toString();
 438  
     }
 439  
 
 440  
     protected void addAbbreviatedString(Map<String, String> abbreviatedStringMap, String label, String value) {
 441  0
         if (StringUtils.isNotBlank(value)) {
 442  0
             abbreviatedStringMap.put(label, value);
 443  
         }
 444  0
     }
 445  
 
 446  
     protected void addAbbreviatedMultiValuedString(Map<String, String> abbreviatedStringMap, String label, Collection<? extends Object> values) {
 447  0
         if (CollectionUtils.isNotEmpty(values)) {
 448  0
             List<String> stringValues = new ArrayList<String>();
 449  0
             for (Object value : values) {
 450  0
                 stringValues.add(value.toString());
 451  
             }
 452  0
             abbreviatedStringMap.put(label, StringUtils.join(stringValues, ","));
 453  
         }
 454  0
     }
 455  
 
 456  
     protected void addAbbreviatedRangeString(Map<String, String> abbreviatedStringMap, String label, DateTime dateFrom, DateTime dateTo) {
 457  0
         if (dateFrom != null || dateTo != null) {
 458  0
             StringBuilder abbreviatedString = new StringBuilder();
 459  0
             if (dateFrom != null) {
 460  0
                 abbreviatedString.append(CoreApiServiceLocator.getDateTimeService().toDateString(dateFrom.toDate()));
 461  
             }
 462  0
             abbreviatedString.append("..");
 463  0
             if (dateTo != null) {
 464  0
                 abbreviatedString.append(CoreApiServiceLocator.getDateTimeService().toDateString(dateTo.toDate()));
 465  
             }
 466  0
             abbreviatedStringMap.put(label, abbreviatedString.toString());
 467  
         }
 468  0
     }
 469  
 
 470  
     /**
 471  
      * Saves a DocumentSearchCriteria into the UserOptions.  This method operates in one of two ways:
 472  
      * 1) The search is named: the criteria is saved under NAMED_SEARCH_ORDER_BASE + <name>
 473  
      * 2) The search is unnamed: the criteria is given a name that indicates its order, which is saved in a second user option
 474  
      *    which contains a list of these names comprising recent searches
 475  
      * @param principalId the user to save the criteria under
 476  
      * @param criteria the doc lookup criteria
 477  
      */
 478  
     private void saveSearch(String principalId, DocumentSearchCriteria criteria) {
 479  0
         if (StringUtils.isBlank(principalId)) {
 480  0
             return;
 481  
         }
 482  
 
 483  
         // TODO - KULRICE-5832 - need to add support for whether or not the saved search is a detailed/advanced search, this was originally stored with savedSearchString in Rice 1.x
 484  
 
 485  
         try {
 486  0
             String savedSearchString = DocumentSearchInternalUtils.marshalDocumentSearchCriteria(criteria);
 487  
 
 488  0
             if (StringUtils.isNotBlank(criteria.getSaveName())) {
 489  0
                 userOptionsService.save(principalId, NAMED_SEARCH_ORDER_BASE + criteria.getSaveName(), savedSearchString);
 490  
             } else {
 491  
                 // first determine the current ordering
 492  0
                 UserOptions searchOrder = userOptionsService.findByOptionId(LAST_SEARCH_ORDER_OPTION, principalId);
 493  
                 // no previous searches, save under first id
 494  0
                 if (searchOrder == null) {
 495  0
                     userOptionsService.save(principalId, LAST_SEARCH_BASE_NAME + "0", savedSearchString);
 496  0
                     userOptionsService.save(principalId, LAST_SEARCH_ORDER_OPTION, LAST_SEARCH_BASE_NAME + "0");
 497  
                 } else {
 498  0
                     String[] currentOrder = searchOrder.getOptionVal().split(",");
 499  
                     // we have reached MAX_SEARCH_ITEMS
 500  0
                     if (currentOrder.length == MAX_SEARCH_ITEMS) {
 501  
                         // move the last item to the front of the list, and save
 502  
                         // over this key with the new criteria
 503  
                         // [5,4,3,2,1] => [1,5,4,3,2]
 504  0
                         String searchName = currentOrder[currentOrder.length - 1];
 505  0
                         String[] newOrder = new String[MAX_SEARCH_ITEMS];
 506  0
                         newOrder[0] = searchName;
 507  0
                         for (int i = 0; i < currentOrder.length - 1; i++) {
 508  0
                             newOrder[i + 1] = currentOrder[i];
 509  
                         }
 510  
                         // rejoins items with comma separator...
 511  0
                         String newSearchOrder = "";
 512  0
                         for (String aNewOrder : newOrder) {
 513  0
                             if (!"".equals(newSearchOrder)) {
 514  0
                                 newSearchOrder += ",";
 515  
                             }
 516  0
                             newSearchOrder += aNewOrder;
 517  
                         }
 518  
                         // save the search string under the searchName (which used to be the last name in the list)
 519  0
                         userOptionsService.save(principalId, searchName, savedSearchString);
 520  0
                         userOptionsService.save(principalId, LAST_SEARCH_ORDER_OPTION, newSearchOrder);
 521  0
                     } else {
 522  
                         // saves the search to the front of the list with incremented index
 523  
                         // [3,2,1] => [4,3,2,1]
 524  
                         // here we need to do a push to identify the highest used number which is from the
 525  
                         // first one in the array, and then add one to it, and push the rest back one
 526  0
                         int absMax = 0;
 527  0
                         for (String aCurrentOrder : currentOrder) {
 528  0
                             int current = new Integer(aCurrentOrder.substring(LAST_SEARCH_BASE_NAME.length(),
 529  
                                     aCurrentOrder.length()));
 530  0
                             if (current > absMax) {
 531  0
                                 absMax = current;
 532  
                             }
 533  
                         }
 534  0
                         String searchName = LAST_SEARCH_BASE_NAME + ++absMax;
 535  0
                         String[] newOrder = new String[currentOrder.length + 1];
 536  0
                         newOrder[0] = searchName;
 537  0
                         for (int i = 0; i < currentOrder.length; i++) {
 538  0
                             newOrder[i + 1] = currentOrder[i];
 539  
                         }
 540  0
                         String newSearchOrder = "";
 541  0
                         for (String aNewOrder : newOrder) {
 542  0
                             if (!"".equals(newSearchOrder)) {
 543  0
                                 newSearchOrder += ",";
 544  
                             }
 545  0
                             newSearchOrder += aNewOrder;
 546  
                         }
 547  0
                         userOptionsService.save(principalId, searchName, savedSearchString);
 548  0
                         userOptionsService.save(principalId, LAST_SEARCH_ORDER_OPTION, newSearchOrder);
 549  
                     }
 550  
                 }
 551  
             }
 552  0
         } catch (Exception e) {
 553  
             // we don't want the failure when saving a search to affect the ability of the document search to succeed
 554  
             // and return it's results, so just log and return
 555  0
             LOG.error("Unable to save search due to exception", e);
 556  0
         }
 557  0
     }
 558  
 
 559  
         public ConfigurationService getKualiConfigurationService() {
 560  0
                 if (kualiConfigurationService == null) {
 561  0
                         kualiConfigurationService = KRADServiceLocator.getKualiConfigurationService();
 562  
                 }
 563  0
                 return kualiConfigurationService;
 564  
         }
 565  
 
 566  
 }