View Javadoc

1   /*
2    * Copyright 2005-2007 The Kuali Foundation
3    *
4    *
5    * Licensed under the Educational Community License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.opensource.org/licenses/ecl2.php
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.kuali.rice.kew.docsearch;
18  
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.exception.RiceRuntimeException;
21  import org.kuali.rice.core.reflect.ObjectDefinition;
22  import org.kuali.rice.core.resourceloader.ObjectDefinitionResolver;
23  import org.kuali.rice.core.util.ClassLoaderUtils;
24  import org.kuali.rice.core.util.RiceConstants;
25  import org.kuali.rice.kew.docsearch.web.SearchAttributeFormContainer;
26  import org.kuali.rice.kew.doctype.bo.DocumentType;
27  import org.kuali.rice.kew.doctype.service.DocumentTypeService;
28  import org.kuali.rice.kew.exception.WorkflowRuntimeException;
29  import org.kuali.rice.kew.service.KEWServiceLocator;
30  import org.kuali.rice.kew.user.UserUtils;
31  import org.kuali.rice.kew.util.Utilities;
32  import org.kuali.rice.kew.web.session.UserSession;
33  import org.kuali.rice.kns.web.ui.Field;
34  import org.kuali.rice.kns.web.ui.Row;
35  
36  import java.sql.Date;
37  import java.sql.Timestamp;
38  import java.util.*;
39  import java.util.regex.Matcher;
40  import java.util.regex.Pattern;
41  
42  
43  /**
44   * Various static utility methods for helping with Searcha.
45   *
46   * @author Kuali Rice Team (rice.collab@kuali.org)
47   */
48  public class DocSearchUtils {
49      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DocSearchUtils.class);
50  
51  //    private static final String DATE_REGEX_PASS = "^\\d{2}/\\d{2}/\\d{4}$|^\\d{2}-\\d{2}-\\d{4}$"; // matches MM/dd/yyyy or MM-dd-yyyy
52  //    private static final String DATE_REGEX_PASS_SPLIT = "(\\d{2})[/|-](\\d{2})[/|-](\\d{4})";
53      private static final String DATE_REGEX_SMALL_TWO_DIGIT_YEAR = "^\\d{1,2}/\\d{1,2}/\\d{2}$|^\\d{1,2}-\\d{1,2}-\\d{2}$"; // matches M/d/yy or MM/dd/yy or M-d-yy or MM-dd-yy
54      private static final String DATE_REGEX_SMALL_TWO_DIGIT_YEAR_SPLIT = "(\\d{1,2})[/,-](\\d{1,2})[/,-](\\d{2})";
55      private static final String DATE_REGEX_SMALL_FOUR_DIGIT_YEAR = "^\\d{1,2}/\\d{1,2}/\\d{4}$|^\\d{1,2}-\\d{1,2}-\\d{4}$"; // matches M/d/yyyy or MM/dd/yyyy or M-d-yyyy or MM-dd-yyyy
56      private static final String DATE_REGEX_SMALL_FOUR_DIGIT_YEAR_SPLIT = "(\\d{1,2})[/,-](\\d{1,2})[/,-](\\d{4})";
57  
58      private static final String DATE_REGEX_SMALL_FOUR_DIGIT_YEAR_FIRST = "^\\d{4}/\\d{1,2}/\\d{1,2}$|^\\d{4}-\\d{1,2}-\\d{1,2}$"; // matches yyyy/M/d or yyyy/MM/dd or yyyy-M-d or yyyy-MM-dd
59      private static final String DATE_REGEX_SMALL_FOUR_DIGIT_YEAR_FIRST_SPLIT = "(\\d{4})[/,-](\\d{1,2})[/,-](\\d{1,2})";
60  
61      private static final String DATE_REGEX_WHOLENUM_SMALL = "^\\d{6}$"; // matches MMddyy
62      private static final String DATE_REGEX_WHOLENUM_SMALL_SPLIT = "(\\d{2})(\\d{2})(\\d{2})";
63      private static final String DATE_REGEX_WHOLENUM_LARGE = "^\\d{8}$"; // matches MMddyyyy
64      private static final String DATE_REGEX_WHOLENUM_LARGE_SPLIT = "(\\d{2})(\\d{2})(\\d{4})";
65  
66      private static final String TIME_REGEX = "([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])";
67  	private static final Map<String, String> REGEX_EXPRESSION_MAP_TO_REGEX_SPLIT_EXPRESSION = new HashMap<String, String>();
68  	static {
69  //		REGEX_EXPRESSION_MAP_TO_REGEX_SPLIT_EXPRESSION.put(DATE_REGEX_PASS, DATE_REGEX_PASS_SPLIT);
70  		REGEX_EXPRESSION_MAP_TO_REGEX_SPLIT_EXPRESSION.put(DATE_REGEX_SMALL_TWO_DIGIT_YEAR, DATE_REGEX_SMALL_TWO_DIGIT_YEAR_SPLIT);
71          REGEX_EXPRESSION_MAP_TO_REGEX_SPLIT_EXPRESSION.put(DATE_REGEX_SMALL_FOUR_DIGIT_YEAR, DATE_REGEX_SMALL_FOUR_DIGIT_YEAR_SPLIT);
72          REGEX_EXPRESSION_MAP_TO_REGEX_SPLIT_EXPRESSION.put(DATE_REGEX_SMALL_FOUR_DIGIT_YEAR_FIRST, DATE_REGEX_SMALL_FOUR_DIGIT_YEAR_FIRST_SPLIT);
73  		REGEX_EXPRESSION_MAP_TO_REGEX_SPLIT_EXPRESSION.put(DATE_REGEX_WHOLENUM_SMALL, DATE_REGEX_WHOLENUM_SMALL_SPLIT);
74  		REGEX_EXPRESSION_MAP_TO_REGEX_SPLIT_EXPRESSION.put(DATE_REGEX_WHOLENUM_LARGE,DATE_REGEX_WHOLENUM_LARGE_SPLIT);
75  	}
76  
77      public static final List DOCUMENT_SEARCH_DATE_VALIDATION_REGEX_EXPRESSIONS = Arrays.asList(DATE_REGEX_SMALL_FOUR_DIGIT_YEAR, DATE_REGEX_SMALL_FOUR_DIGIT_YEAR_FIRST);
78  
79      public static List<SearchableAttributeValue> getSearchableAttributeValueObjectTypes() {
80          List<SearchableAttributeValue> searchableAttributeValueClasses = new ArrayList<SearchableAttributeValue>();
81          for (Object aSEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST : SearchableAttribute.SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST)
82          {
83              Class searchAttributeValueClass = (Class) aSEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST;
84              ObjectDefinition objDef = new ObjectDefinition(searchAttributeValueClass);
85              SearchableAttributeValue attributeValue = (SearchableAttributeValue) ObjectDefinitionResolver.createObject(objDef, ClassLoaderUtils.getDefaultClassLoader(), false);
86              searchableAttributeValueClasses.add(attributeValue);
87          }
88          return searchableAttributeValueClasses;
89      }
90  
91      public static SearchableAttributeValue getSearchableAttributeValueByDataTypeString(String dataType) {
92          SearchableAttributeValue returnableValue = null;
93          if (StringUtils.isBlank(dataType)) {
94              return returnableValue;
95          }
96          for (SearchableAttributeValue attValue : getSearchableAttributeValueObjectTypes())
97          {
98              if (dataType.equalsIgnoreCase(attValue.getAttributeDataType()))
99              {
100                 if (returnableValue != null)
101                 {
102                     String errorMsg = "Found two SearchableAttributeValue objects with same data type string ('" + dataType + "' while ignoring case):  " + returnableValue.getClass().getName() + " and " + attValue.getClass().getName();
103                     LOG.error("getSearchableAttributeValueByDataTypeString() " + errorMsg);
104                     throw new RuntimeException(errorMsg);
105                 }
106                 LOG.debug("getSearchableAttributeValueByDataTypeString() SearchableAttributeValue class name is " + attValue.getClass().getName() + "... ojbConcreteClassName is " + attValue.getOjbConcreteClass());
107                 ObjectDefinition objDef = new ObjectDefinition(attValue.getClass());
108                 returnableValue = (SearchableAttributeValue) ObjectDefinitionResolver.createObject(objDef, ClassLoaderUtils.getDefaultClassLoader(), false);
109             }
110         }
111         return returnableValue;
112     }
113 
114     /**
115      * A method to format any variety of date strings into a common format
116      *
117      * @param date
118      *            A string date in one of a few different formats
119      * @return A string representing a date in the format yyyy/MM/dd or null if date is invalid
120      */
121     public static String getSqlFormattedDate(String date) {
122         DateComponent dc = formatDateToDateComponent(date, Arrays.asList(REGEX_EXPRESSION_MAP_TO_REGEX_SPLIT_EXPRESSION.keySet().toArray()));
123         if (dc == null) {
124             return null;
125         }
126         return dc.getYear() + "/" + dc.getMonth() + "/" + dc.getDate();
127     }
128 
129     /**
130      * A method to format any variety of date strings into a common format
131      *
132      * @param date
133      *            A string date in one of a few different formats
134      * @return A string representing a date in the format MM/dd/yyyy or null if date is invalid
135      */
136     public static String getEntryFormattedDate(String date) {
137         Pattern p = Pattern.compile(TIME_REGEX);
138         Matcher util = p.matcher(date);
139         if (util.find()) {
140             date = StringUtils.substringBeforeLast(date, " ");
141         }
142         DateComponent dc = formatDateToDateComponent(date, DOCUMENT_SEARCH_DATE_VALIDATION_REGEX_EXPRESSIONS);
143         if (dc == null) {
144             return null;
145         }
146         return dc.getMonth() + "/" + dc.getDate() + "/" + dc.getYear();
147     }
148 
149     private static DateComponent formatDateToDateComponent(String date, List regularExpressionList) {
150         String matchingRegexExpression = null;
151         for (Iterator iter = regularExpressionList.iterator(); iter.hasNext();) {
152             String matchRegex = (String) iter.next();
153             if (!REGEX_EXPRESSION_MAP_TO_REGEX_SPLIT_EXPRESSION.containsKey(matchRegex)) {
154                 String errorMsg = "";
155                 LOG.error("formatDateToDateComponent(String,List) " + errorMsg);
156 
157             }
158             Pattern p = Pattern.compile(matchRegex);
159             if ((p.matcher(date)).matches()) {
160                 matchingRegexExpression = matchRegex;
161                 break;
162             }
163         }
164 
165         if (matchingRegexExpression == null) {
166             String errorMsg = "formatDate(String,List) Date string given '" + date + "' is not valid according to Workflow defaults.  Returning null value.";
167             if (StringUtils.isNotBlank(date)) {
168                 LOG.warn(errorMsg);
169             } else {
170                 LOG.debug(errorMsg);
171             }
172             return null;
173         }
174         String regexSplitExpression = (String) REGEX_EXPRESSION_MAP_TO_REGEX_SPLIT_EXPRESSION.get(matchingRegexExpression);
175 
176         // Check date formats and reformat to yyyy/MM/dd
177         // well formed MM/dd/yyyy
178         Pattern p = Pattern.compile(regexSplitExpression);
179         Matcher util = p.matcher(date);
180         util.matches();
181         if (regexSplitExpression.equals(DATE_REGEX_SMALL_TWO_DIGIT_YEAR_SPLIT)) {
182             StringBuffer yearBuf = new StringBuffer();
183             StringBuffer monthBuf = new StringBuffer();
184             StringBuffer dateBuf = new StringBuffer();
185             Integer year = new Integer(util.group(3));
186 
187             if (year <= 50) {
188                 yearBuf.append("20").append(util.group(3));
189             } else if (util.group(3).length() < 3) {
190                 yearBuf.append("19").append(util.group(3));
191             } else {
192                 yearBuf.append(util.group(3));
193             }
194 
195             if (util.group(1).length() < 2) {
196                 monthBuf.append("0").append(util.group(1));
197             } else {
198                 monthBuf.append(util.group(1));
199             }
200 
201             if (util.group(2).length() < 2) {
202                 dateBuf.append("0").append(util.group(2));
203             } else {
204                 dateBuf.append(util.group(2));
205             }
206 
207             return new DateComponent(yearBuf.toString(), monthBuf.toString(), dateBuf.toString());
208 
209             // small date format M/d/yyyy | MM/dd/yyyy | M-d-yyyy | MM-dd-yyyy
210         } else if (regexSplitExpression.equals(DATE_REGEX_SMALL_FOUR_DIGIT_YEAR_SPLIT)) {
211             StringBuffer yearBuf = new StringBuffer(util.group(3));
212             StringBuffer monthBuf = new StringBuffer();
213             StringBuffer dateBuf = new StringBuffer();
214 
215             if (util.group(1).length() < 2) {
216                 monthBuf.append("0").append(util.group(1));
217             } else {
218                 monthBuf.append(util.group(1));
219             }
220 
221             if (util.group(2).length() < 2) {
222                 dateBuf.append("0").append(util.group(2));
223             } else {
224                 dateBuf.append(util.group(2));
225             }
226 
227             return new DateComponent(yearBuf.toString(), monthBuf.toString(), dateBuf.toString());
228 
229             // small date format yyyy/M/d | yyyy/MM/dd | yyyy-M-d | yyyy-MM-dd
230         } else if (regexSplitExpression.equals(DATE_REGEX_SMALL_FOUR_DIGIT_YEAR_FIRST_SPLIT)) {
231             StringBuffer yearBuf = new StringBuffer(util.group(1));
232             StringBuffer monthBuf = new StringBuffer();
233             StringBuffer dateBuf = new StringBuffer();
234 
235             if (util.group(2).length() < 2) {
236                 monthBuf.append("0").append(util.group(2));
237             } else {
238                 monthBuf.append(util.group(2));
239             }
240 
241             if (util.group(3).length() < 2) {
242                 dateBuf.append("0").append(util.group(3));
243             } else {
244                 dateBuf.append(util.group(3));
245             }
246 
247             return new DateComponent(yearBuf.toString(), monthBuf.toString(), dateBuf.toString());
248 
249             // large number MMddyyyy
250         } else if (regexSplitExpression.equals(DATE_REGEX_WHOLENUM_LARGE_SPLIT)) {
251             return new DateComponent(util.group(3), util.group(1), util.group(2));
252 
253             // small number MMddyy
254         } else if (regexSplitExpression.equals(DATE_REGEX_WHOLENUM_SMALL_SPLIT)) {
255             StringBuffer yearBuf = new StringBuffer();
256             Integer year = new Integer(util.group(3));
257 
258             if (year < 50) {
259                 yearBuf.append("20");
260             } else {
261                 yearBuf.append("19");
262             }
263             yearBuf.append(util.group(3));
264             return new DateComponent(yearBuf.toString(), util.group(1), util.group(2));
265         } else {
266             LOG.warn("formatDate(String,List) Date string given '" + date + "' is not valid according to Workflow defaults.  Returning null value.");
267             return null;
268         }
269     }
270 
271     public static String getDisplayValueWithDateOnly(Timestamp value) {
272         return RiceConstants.getDefaultDateFormat().format(new Date(value.getTime()));
273     }
274 
275     public static String getDisplayValueWithDateTime(Timestamp value) {
276         return RiceConstants.getDefaultDateAndTimeFormat().format(new Date(value.getTime()));
277     }
278 
279     public static Timestamp convertStringDateToTimestamp(String dateWithoutTime) {
280         Pattern p = Pattern.compile(TIME_REGEX);
281         Matcher util = p.matcher(dateWithoutTime);
282         if (util.find()) {
283             dateWithoutTime = StringUtils.substringBeforeLast(dateWithoutTime, " ");
284         }
285         DateComponent formattedDate = formatDateToDateComponent(dateWithoutTime, Arrays.asList(REGEX_EXPRESSION_MAP_TO_REGEX_SPLIT_EXPRESSION.keySet().toArray()));
286         if (formattedDate == null) {
287             return null;
288         }
289         Calendar c = Calendar.getInstance();
290         c.clear();
291         c.set(Calendar.MONTH, Integer.valueOf(formattedDate.getMonth()) - 1);
292         c.set(Calendar.DATE, Integer.valueOf(formattedDate.getDate()));
293         c.set(Calendar.YEAR, Integer.valueOf(formattedDate.getYear()));
294         return Utilities.convertCalendar(c);
295     }
296 
297     public static class DateComponent {
298         protected String month;
299         protected String date;
300         protected String year;
301 
302         public DateComponent(String year, String month, String date) {
303             this.month = month;
304             this.date = date;
305             this.year = year;
306         }
307 
308         public String getDate() {
309             return date;
310         }
311 
312         public String getMonth() {
313             return month;
314         }
315 
316         public String getYear() {
317             return year;
318         }
319     }
320 
321     private static final String CURRENT_USER_PREFIX = "CURRENT_USER.";
322 
323     /**
324      * Build List of searchable attributes from saved searchable attributes string
325      *
326      * @param searchableAttributeString
327      *            String representation of searchable attributes
328      * @param documentTypeName document type name
329      * @return searchable attributes list
330      */
331     public static List<SearchAttributeCriteriaComponent> buildSearchableAttributesFromString(String searchableAttributeString, String documentTypeName) {
332         List<SearchAttributeCriteriaComponent> searchableAttributes = new ArrayList<SearchAttributeCriteriaComponent>();
333         Map<String, SearchAttributeCriteriaComponent> criteriaComponentsByKey = new HashMap<String, SearchAttributeCriteriaComponent>();
334 
335         DocumentType docType = getDocumentType(documentTypeName);
336 
337         if (docType != null) {
338 
339             for (SearchableAttribute searchableAttribute : docType.getSearchableAttributes()) {
340             	//KFSMI-1466 - DocumentSearchContext
341                 for (Row row : searchableAttribute.getSearchingRows(
342                 		DocSearchUtils.getDocumentSearchContext("", docType.getName(), ""))) {
343                     for (org.kuali.rice.kns.web.ui.Field field : row.getFields()) {
344                         if (field instanceof Field) {
345                             SearchableAttributeValue searchableAttributeValue = DocSearchUtils.getSearchableAttributeValueByDataTypeString(field.getFieldDataType());
346                             SearchAttributeCriteriaComponent sacc = new SearchAttributeCriteriaComponent(field.getPropertyName(), null, field.getPropertyName(), searchableAttributeValue);
347                             sacc.setRangeSearch(field.isMemberOfRange());
348                             sacc.setSearchInclusive(field.isInclusive());
349                             sacc.setSearchable(field.isIndexedForSearch());
350                             sacc.setLookupableFieldType(field.getFieldType());
351                             sacc.setCanHoldMultipleValues(Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType()));
352                             criteriaComponentsByKey.put(field.getPropertyName(), sacc);
353                         } else {
354                             throw new RiceRuntimeException("Fields must be of type org.kuali.rice.kew.docsearch.Field");
355                         }
356                     }
357                 }
358             }
359         }
360 
361         Map<String, List<String>> checkForMultiValueSearchableAttributes = new HashMap<String, List<String>>();
362         if ((searchableAttributeString != null) && (searchableAttributeString.trim().length() > 0)) {
363             StringTokenizer tokenizer = new StringTokenizer(searchableAttributeString, ",");
364             while (tokenizer.hasMoreTokens()) {
365                 String searchableAttribute = tokenizer.nextToken();
366                 int index = searchableAttribute.indexOf(":");
367                 if (index != -1) {
368                     String key = searchableAttribute.substring(0, index);
369                     // String savedKey = key;
370                     // if (key.indexOf(SearchableAttribute.RANGE_LOWER_BOUND_PROPERTY_PREFIX) == 0) {
371                     // savedKey = key.substring(SearchableAttribute.RANGE_LOWER_BOUND_PROPERTY_PREFIX.length());
372                     // } else if (key.indexOf(SearchableAttribute.RANGE_UPPER_BOUND_PROPERTY_PREFIX) == 0) {
373                     // savedKey = key.substring(SearchableAttribute.RANGE_UPPER_BOUND_PROPERTY_PREFIX.length());
374                     // }
375                     String value = searchableAttribute.substring(index + 1);
376                     if (value.startsWith(CURRENT_USER_PREFIX)) {
377                         String idType = value.substring(CURRENT_USER_PREFIX.length());
378                         UserSession session = UserSession.getAuthenticatedUser();
379                         String idValue = UserUtils.getIdValue(idType, session.getPerson());
380                         if (!StringUtils.isBlank(idValue)) {
381                             value = idValue;
382                         }
383                     }
384                     SearchAttributeCriteriaComponent critComponent = (SearchAttributeCriteriaComponent) criteriaComponentsByKey.get(key);
385                     if (critComponent == null) {
386                         // here we potentially have a change to the searchable attributes dealing with naming or ranges... so
387                         // we just ignore the values
388                         continue;
389                     }
390                     if (critComponent.getSearchableAttributeValue() == null) {
391                         String errorMsg = "Cannot find SearchableAttributeValue for given key '" + key + "'";
392                         LOG.error("buildSearchableAttributesFromString() " + errorMsg);
393                         throw new RuntimeException(errorMsg);
394                     }
395                     if (critComponent.isCanHoldMultipleValues()) {
396                         // should be multivalue
397                         if (checkForMultiValueSearchableAttributes.containsKey(key)) {
398                             List<String> keyList = checkForMultiValueSearchableAttributes.get(key);
399                             keyList.add(value);
400                             checkForMultiValueSearchableAttributes.put(key, keyList);
401                         } else {
402                             List<String> tempList = new ArrayList<String>();
403                             tempList.add(value);
404                             // tempList.addAll(Arrays.asList(new String[]{value}));
405                             checkForMultiValueSearchableAttributes.put(key, tempList);
406                             searchableAttributes.add(critComponent);
407                         }
408                     } else {
409                         // should be single value
410                         if (checkForMultiValueSearchableAttributes.containsKey(key)) {
411                             // attempting to use multiple values in a field that does not support it
412                             String error = "Attempting to add multiple values to a search attribute (key: '" + key + "') that does not suppor them";
413                             LOG.error("buildSearchableAttributesFromString() " + error);
414                             // we don't blow chunks here in case an attribute has been altered from multi-value to
415                             // non-multi-value
416                         }
417                         critComponent.setValue(value);
418                         searchableAttributes.add(critComponent);
419                     }
420 
421                 }
422             }
423             for (SearchAttributeCriteriaComponent criteriaComponent : searchableAttributes)
424             {
425                 if (criteriaComponent.isCanHoldMultipleValues())
426                 {
427                     List<String> values = (List<String>) checkForMultiValueSearchableAttributes.get(criteriaComponent.getFormKey());
428                     criteriaComponent.setValue(null);
429                     criteriaComponent.setValues(values);
430                 }
431             }
432         }
433 
434         return searchableAttributes;
435     }
436 
437     /**
438      * This method takes the given <code>propertyFields</code> parameter and populates the {@link DocSearchCriteriaDTO}
439      * object search attributes based on the document type name set on the <code>criteria</code> object.<br>
440      * <br>
441      * This is identical to calling {@link #addSearchableAttributesToCriteria(DocSearchCriteriaDTO, List, String, boolean)}
442      * with a boolean value of false for the <code>setAttributesStrictly</code> parameter.
443      *
444      * @param criteria -
445      *            The object that needs a list of {@link SearchAttributeCriteriaComponent} objects set up based on the
446      *            document type name and <code>propertyFields</code> parameter
447      * @param propertyFields -
448      *            The list of {@link SearchAttributeFormContainer} objects that need to be converted to
449      *            {@link SearchAttributeCriteriaComponent} objects and set on the <code>criteria</code> parameter
450      * @param searchAttributesString -
451      *            A potential string that must be parsed to use to set attributes on the <code>criteria</code> object
452      */
453     public static void addSearchableAttributesToCriteria(DocSearchCriteriaDTO criteria, List propertyFields, String searchAttributesString) {
454         addSearchableAttributesToCriteria(criteria, propertyFields, searchAttributesString, false);
455     }
456 
457     /**
458      * This method takes the given <code>propertyFields</code> parameter and populates the {@link DocSearchCriteriaDTO}
459      * object search attributes based on the document type name set on the <code>criteria</code> object.<br>
460      * <br>
461      * This is identical to calling {@link #addSearchableAttributesToCriteria(DocSearchCriteriaDTO, List, String, boolean)}
462      * with a null value for the <code>searchAttributesString</code> parameter.
463      *
464      * @param criteria -
465      *            The object that needs a list of {@link SearchAttributeCriteriaComponent} objects set up based on the
466      *            document type name and <code>propertyFields</code> parameter
467      * @param propertyFields -
468      *            The list of {@link SearchAttributeFormContainer} objects that need to be converted to
469      *            {@link SearchAttributeCriteriaComponent} objects and set on the <code>criteria</code> parameter
470      * @param setAttributesStrictly -
471      *            A boolean to specify whether to explicitly throw an error when a given value from
472      *            <code>propertyFields</code> does not match a search attribute on the specified document type. If set to
473      *            true an error with be thrown. If set to false the mismatch will be ignored.
474      */
475     public static void addSearchableAttributesToCriteria(DocSearchCriteriaDTO criteria, List propertyFields, boolean setAttributesStrictly) {
476         addSearchableAttributesToCriteria(criteria, propertyFields, null, setAttributesStrictly);
477     }
478 
479     /**
480      * This method takes the given <code>propertyFields</code> parameter and populates the {@link DocSearchCriteriaDTO}
481      * object search attributes based on the document type name set on the <code>criteria</code> object.
482      *
483      * @param criteria -
484      *            The object that needs a list of {@link SearchAttributeCriteriaComponent} objects set up based on the
485      *            document type name and <code>propertyFields</code> parameter
486      * @param propertyFields -
487      *            The list of {@link SearchAttributeFormContainer} objects that need to be converted to
488      *            {@link SearchAttributeCriteriaComponent} objects and set on the <code>criteria</code> parameter
489      * @param searchAttributesString -
490      *            A potential string that must be parsed to use to set attributes on the <code>criteria</code> object
491      * @param setAttributesStrictly -
492      *            A boolean to specify whether to explicitly throw an error when a given value from
493      *            <code>propertyFields</code> does not match a search attribute on the specified document type. If set to
494      *            true an error with be thrown. If set to false the mismatch will be ignored.
495      */
496     public static void addSearchableAttributesToCriteria(DocSearchCriteriaDTO criteria, List propertyFields, String searchAttributesString, boolean setAttributesStrictly) {
497         if (criteria != null) {
498             DocumentType docType = getDocumentType(criteria.getDocTypeFullName());
499             if (docType == null) {
500                 return;
501             }
502             criteria.getSearchableAttributes().clear();
503             Map<String, SearchAttributeCriteriaComponent> urlParameterSearchAttributesByFormKey = new HashMap<String, SearchAttributeCriteriaComponent>();
504             if (!StringUtils.isBlank(searchAttributesString)) {
505                 List<SearchAttributeCriteriaComponent> components = buildSearchableAttributesFromString(searchAttributesString, docType.getName());
506                 for (SearchAttributeCriteriaComponent component : components) {
507                     urlParameterSearchAttributesByFormKey.put(component.getFormKey(), component);
508                     criteria.addSearchableAttribute(component);
509                 }
510 //                docSearchForm.setSearchableAttributes(null);
511             }
512             if (!propertyFields.isEmpty()) {
513                 Map<String, SearchAttributeCriteriaComponent> criteriaComponentsByFormKey = new HashMap<String, SearchAttributeCriteriaComponent>();
514                 for (SearchableAttribute searchableAttribute : docType.getSearchableAttributes()) {
515                 	//KFSMI-1466 - DocumentSearchContext
516                     for (Row row : searchableAttribute.getSearchingRows(
517                     		DocSearchUtils.getDocumentSearchContext("", docType.getName(), ""))) {
518                         for (org.kuali.rice.kns.web.ui.Field field : row.getFields()) {
519                             if (field instanceof Field) {
520                                 SearchableAttributeValue searchableAttributeValue = DocSearchUtils.getSearchableAttributeValueByDataTypeString(field.getFieldDataType());
521                                 SearchAttributeCriteriaComponent sacc = new SearchAttributeCriteriaComponent(field.getPropertyName(), null, field.getPropertyName(), searchableAttributeValue);
522                                 sacc.setRangeSearch(field.isMemberOfRange());
523                                 sacc.setSearchInclusive(field.isInclusive());
524                                 sacc.setLookupableFieldType(field.getFieldType());
525                                 sacc.setSearchable(field.isIndexedForSearch());
526                                 sacc.setCanHoldMultipleValues(field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType()));
527                                 criteriaComponentsByFormKey.put(field.getPropertyName(), sacc);
528                             } else {
529                                 throw new RiceRuntimeException("Fields must be of type org.kuali.rice.kew.docsearch.Field");
530                             }
531                         }
532                     }
533                 }
534                 for (Iterator iterator = propertyFields.iterator(); iterator.hasNext();) {
535                     SearchAttributeFormContainer propertyField = (SearchAttributeFormContainer) iterator.next();
536                     SearchAttributeCriteriaComponent sacc = (SearchAttributeCriteriaComponent) criteriaComponentsByFormKey.get(propertyField.getKey());
537                     if (sacc != null) {
538                         if (sacc.getSearchableAttributeValue() == null) {
539                             String errorMsg = "Searchable attribute with form field key " + sacc.getFormKey() + " does not have a valid SearchableAttributeValue";
540                             LOG.error("addSearchableAttributesToCriteria() " + errorMsg);
541                             throw new RuntimeException(errorMsg);
542                         }
543                         // if the url parameter has already set up the search attribute change the propertyField
544                         if (urlParameterSearchAttributesByFormKey.containsKey(sacc.getFormKey())) {
545                             setupPropertyField(urlParameterSearchAttributesByFormKey.get(sacc.getFormKey()), propertyFields);
546                         } else {
547                             //if ((Field.CHECKBOX_YES_NO.equals(sacc.getLookupableFieldType())) && (!propertyField.isValueSet())) {
548                                 // value was not set on the form so we must use the alternate value which for checkbox is the
549                                 // 'unchecked' value
550                             //    sacc.setValue(propertyField.getAlternateValue());
551                             //} else
552                             if (Field.MULTI_VALUE_FIELD_TYPES.contains(sacc.getLookupableFieldType())) {
553                                 // set the multivalue lookup indicator
554                                 sacc.setCanHoldMultipleValues(true);
555                                 if (propertyField.getValues() == null) {
556                                     sacc.setValues(new ArrayList<String>());
557                                 } else {
558                                     sacc.setValues(Arrays.asList(propertyField.getValues()));
559                                 }
560                             } else {
561                                 sacc.setValue(propertyField.getValue());
562                             }
563                             criteria.addSearchableAttribute(sacc);
564                         }
565                     } else {
566                         if (setAttributesStrictly) {
567                             String message = "Cannot find matching search attribute with key '" + propertyField.getKey() + "' on document type '" + docType.getName() + "'";
568                             LOG.error(message);
569                             throw new WorkflowRuntimeException(message);
570                         }
571                     }
572                 }
573             }
574         }
575     }
576 
577     public static void setupPropertyField(SearchAttributeCriteriaComponent searchableAttribute, List propertyFields) {
578         SearchAttributeFormContainer propertyField = getPropertyField(searchableAttribute.getFormKey(), propertyFields);
579         if (propertyField != null) {
580             propertyField.setValue(searchableAttribute.getValue());
581             if (searchableAttribute.getValues() != null) {
582                 propertyField.setValues(searchableAttribute.getValues().toArray(new String[searchableAttribute.getValues().size()]));
583             }
584         }
585     }
586 
587     public static SearchAttributeFormContainer getPropertyField(String key, List propertyFields) {
588         if (StringUtils.isBlank(key)) {
589             return null;
590         }
591         for (Iterator iter = propertyFields.iterator(); iter.hasNext();) {
592             SearchAttributeFormContainer container = (SearchAttributeFormContainer) iter.next();
593             if (key.equals(container.getKey())) {
594                 return container;
595             }
596         }
597         return null;
598     }
599 
600     private static DocumentType getDocumentType(String docTypeName) {
601         if ((docTypeName != null && !"".equals(docTypeName))) {
602             return ((DocumentTypeService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(docTypeName);
603         }
604         return null;
605     }
606 
607     public static DocumentSearchContext getDocumentSearchContext(String documentId, String documentTypeName, String documentContent){
608     	DocumentSearchContext documentSearchContext = new DocumentSearchContext();
609     	documentSearchContext.setDocumentId(documentId);
610     	documentSearchContext.setDocumentTypeName(documentTypeName);
611     	documentSearchContext.setDocumentContent(documentContent);
612     	return documentSearchContext;
613     }
614 }