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.joda.time.DateTime;
21  import org.kuali.rice.core.api.reflect.ObjectDefinition;
22  import org.kuali.rice.core.api.uif.DataType;
23  import org.kuali.rice.core.api.uif.RemotableAttributeField;
24  import org.kuali.rice.core.api.util.ClassLoaderUtils;
25  import org.kuali.rice.core.api.util.RiceConstants;
26  import org.kuali.rice.core.framework.resourceloader.ObjectDefinitionResolver;
27  import org.kuali.rice.kew.api.WorkflowRuntimeException;
28  import org.kuali.rice.kew.api.document.lookup.DocumentLookupConfiguration;
29  import org.kuali.rice.kew.docsearch.web.SearchAttributeFormContainer;
30  import org.kuali.rice.kew.doctype.bo.DocumentType;
31  import org.kuali.rice.kew.doctype.service.DocumentTypeService;
32  import org.kuali.rice.kew.service.KEWServiceLocator;
33  import org.kuali.rice.kew.user.UserUtils;
34  import org.kuali.rice.kns.util.FieldUtils;
35  import org.kuali.rice.kns.web.ui.Field;
36  import org.kuali.rice.krad.UserSession;
37  import org.kuali.rice.krad.util.GlobalVariables;
38  
39  import java.sql.Date;
40  import java.sql.Timestamp;
41  import java.util.ArrayList;
42  import java.util.Arrays;
43  import java.util.HashMap;
44  import java.util.Iterator;
45  import java.util.List;
46  import java.util.Map;
47  import java.util.StringTokenizer;
48  
49  
50  /**
51   * Various static utility methods for helping with Searcha.
52   *
53   * @author Kuali Rice Team (rice.collab@kuali.org)
54   */
55  public final class DocSearchUtils {
56  
57      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DocSearchUtils.class);
58  
59      public static final List<Class<? extends SearchableAttributeValue>> SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST =
60              new ArrayList<Class<? extends SearchableAttributeValue>>();
61      static {
62          SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST.add(SearchableAttributeStringValue.class);
63          SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST.add(SearchableAttributeFloatValue.class);
64          SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST.add(SearchableAttributeLongValue.class);
65          SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST.add(SearchableAttributeDateTimeValue.class);
66      }
67  
68      private DocSearchUtils() {
69      	throw new UnsupportedOperationException("do not call");
70      }
71      
72      public static List<SearchableAttributeValue> getSearchableAttributeValueObjectTypes() {
73          List<SearchableAttributeValue> searchableAttributeValueClasses = new ArrayList<SearchableAttributeValue>();
74          for (Class<? extends SearchableAttributeValue> searchAttributeValueClass : SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST) {
75              ObjectDefinition objDef = new ObjectDefinition(searchAttributeValueClass);
76              SearchableAttributeValue attributeValue = (SearchableAttributeValue) ObjectDefinitionResolver.createObject(objDef, ClassLoaderUtils.getDefaultClassLoader(), false);
77              searchableAttributeValueClasses.add(attributeValue);
78          }
79          return searchableAttributeValueClasses;
80      }
81  
82      /**
83       * TODO - Rice 2.0 - Move once migrated over to new doc search framework
84       */
85      public static SearchableAttributeValue getSearchableAttributeValueByDataTypeString(String dataType) {
86          SearchableAttributeValue returnableValue = null;
87          if (StringUtils.isBlank(dataType)) {
88              return returnableValue;
89          }
90          for (SearchableAttributeValue attValue : getSearchableAttributeValueObjectTypes())
91          {
92              if (dataType.equalsIgnoreCase(attValue.getAttributeDataType()))
93              {
94                  if (returnableValue != null)
95                  {
96                      String errorMsg = "Found two SearchableAttributeValue objects with same data type string ('" + dataType + "' while ignoring case):  " + returnableValue.getClass().getName() + " and " + attValue.getClass().getName();
97                      LOG.error("getSearchableAttributeValueByDataTypeString() " + errorMsg);
98                      throw new RuntimeException(errorMsg);
99                  }
100                 LOG.debug("getSearchableAttributeValueByDataTypeString() SearchableAttributeValue class name is " + attValue.getClass().getName() + "... ojbConcreteClassName is " + attValue.getOjbConcreteClass());
101                 ObjectDefinition objDef = new ObjectDefinition(attValue.getClass());
102                 returnableValue = (SearchableAttributeValue) ObjectDefinitionResolver.createObject(objDef, ClassLoaderUtils.getDefaultClassLoader(), false);
103             }
104         }
105         return returnableValue;
106     }
107 
108     public static SearchableAttributeValue getSearchableAttributeValueByDataTypeString(DataType dataType) {
109         if (dataType == null || dataType == DataType.STRING || dataType == DataType.BOOLEAN) {
110             return new SearchableAttributeStringValue();
111         } else if (dataType == DataType.DATE || dataType == DataType.TRUNCATED_DATE) {
112             return new SearchableAttributeDateTimeValue();
113         } else if (dataType == DataType.FLOAT || dataType == DataType.DOUBLE) {
114             return new SearchableAttributeFloatValue();
115         } else if (dataType == DataType.INTEGER || dataType == DataType.LONG) {
116             return new SearchableAttributeLongValue();
117         }
118         throw new IllegalArgumentException("Could not determine appropriate searchable attribute data type to use for the given DataType: " + dataType);
119     }
120 
121     public static  List<SearchAttributeCriteriaComponent> translateSearchFieldToCriteriaComponent(RemotableAttributeField searchField) {
122         List<SearchAttributeCriteriaComponent> components = new ArrayList<SearchAttributeCriteriaComponent>();
123         SearchableAttributeValue searchableAttributeValue = DocSearchUtils.getSearchableAttributeValueByDataTypeString(searchField.getDataType());
124         List<Field> fields = FieldUtils.convertRemotableAttributeField(searchField);
125         for (Field field : fields) {
126             SearchAttributeCriteriaComponent sacc = new SearchAttributeCriteriaComponent(field.getPropertyName(), null, field.getPropertyName(), searchableAttributeValue);
127 
128             sacc.setRangeSearch(field.isMemberOfRange());
129             sacc.setSearchInclusive(field.isInclusive());
130             sacc.setSearchable(field.isIndexedForSearch());
131             sacc.setLookupableFieldType(field.getFieldType());
132             sacc.setCanHoldMultipleValues(Field.MULTI_VALUE_FIELD_TYPES.contains(field.getFieldType()));
133             components.add(sacc);
134         }
135         return components;
136     }
137 
138     public static String getDisplayValueWithDateOnly(DateTime value) {
139         return getDisplayValueWithDateOnly(new Timestamp(value.getMillis()));
140     }
141 
142     public static String getDisplayValueWithDateOnly(Timestamp value) {
143         return RiceConstants.getDefaultDateFormat().format(new Date(value.getTime()));
144     }
145 
146     public static String getDisplayValueWithDateTime(Timestamp value) {
147         return RiceConstants.getDefaultDateAndTimeFormat().format(new Date(value.getTime()));
148     }
149 
150 
151 
152     private static final String CURRENT_USER_PREFIX = "CURRENT_USER.";
153 
154     /**
155      * Build List of searchable attributes from saved searchable attributes string
156      *
157      * @param searchableAttributeString
158      *            String representation of searchable attributes
159      * @param documentTypeName document type name
160      * @return searchable attributes list
161      */
162     public static List<SearchAttributeCriteriaComponent> buildSearchableAttributesFromString(String searchableAttributeString, String documentTypeName) {
163         List<SearchAttributeCriteriaComponent> searchableAttributes = new ArrayList<SearchAttributeCriteriaComponent>();
164         Map<String, SearchAttributeCriteriaComponent> criteriaComponentsByKey = new HashMap<String, SearchAttributeCriteriaComponent>();
165 
166         DocumentType docType = getDocumentType(documentTypeName);
167 
168         if (docType != null) {
169 
170             DocumentLookupConfiguration lookupConfiguration = KEWServiceLocator.getDocumentLookupCustomizationMediator().getDocumentLookupConfiguration(docType);
171             List<RemotableAttributeField> searchFields = lookupConfiguration.getFlattenedSearchAttributeFields();
172 
173             for (RemotableAttributeField searchField : searchFields) {
174                 List<SearchAttributeCriteriaComponent> components = DocSearchUtils.translateSearchFieldToCriteriaComponent(searchField);
175                 for (SearchAttributeCriteriaComponent searchableAttributeComponent : components) {
176                     criteriaComponentsByKey.put(searchField.getName(), searchableAttributeComponent);
177                 }
178             }
179 
180         }
181 
182         Map<String, List<String>> checkForMultiValueSearchableAttributes = new HashMap<String, List<String>>();
183         if ((searchableAttributeString != null) && (searchableAttributeString.trim().length() > 0)) {
184             StringTokenizer tokenizer = new StringTokenizer(searchableAttributeString, ",");
185             while (tokenizer.hasMoreTokens()) {
186                 String searchableAttribute = tokenizer.nextToken();
187                 int index = searchableAttribute.indexOf(":");
188                 if (index != -1) {
189                     String key = searchableAttribute.substring(0, index);
190                     String value = searchableAttribute.substring(index + 1);
191                     if (value.startsWith(CURRENT_USER_PREFIX)) {
192                         String idType = value.substring(CURRENT_USER_PREFIX.length());
193                         UserSession session = GlobalVariables.getUserSession();
194                         String idValue = UserUtils.getIdValue(idType, session.getPerson());
195                         if (!StringUtils.isBlank(idValue)) {
196                             value = idValue;
197                         }
198                     }
199                     SearchAttributeCriteriaComponent critComponent = criteriaComponentsByKey.get(key);
200                     if (critComponent == null) {
201                         // here we potentially have a change to the searchable attributes dealing with naming or ranges... so
202                         // we just ignore the values
203                         continue;
204                     }
205                     if (critComponent.getSearchableAttributeValue() == null) {
206                         String errorMsg = "Cannot find SearchableAttributeValue for given key '" + key + "'";
207                         LOG.error("buildSearchableAttributesFromString() " + errorMsg);
208                         throw new RuntimeException(errorMsg);
209                     }
210                     if (critComponent.isCanHoldMultipleValues()) {
211                         // should be multivalue
212                         if (checkForMultiValueSearchableAttributes.containsKey(key)) {
213                             List<String> keyList = checkForMultiValueSearchableAttributes.get(key);
214                             keyList.add(value);
215                             checkForMultiValueSearchableAttributes.put(key, keyList);
216                         } else {
217                             List<String> tempList = new ArrayList<String>();
218                             tempList.add(value);
219                             // tempList.addAll(Arrays.asList(new String[]{value}));
220                             checkForMultiValueSearchableAttributes.put(key, tempList);
221                             searchableAttributes.add(critComponent);
222                         }
223                     } else {
224                         // should be single value
225                         if (checkForMultiValueSearchableAttributes.containsKey(key)) {
226                             // attempting to use multiple values in a field that does not support it
227                             String error = "Attempting to add multiple values to a search attribute (key: '" + key + "') that does not suppor them";
228                             LOG.error("buildSearchableAttributesFromString() " + error);
229                             // we don't blow chunks here in case an attribute has been altered from multi-value to
230                             // non-multi-value
231                         }
232                         critComponent.setValue(value);
233                         searchableAttributes.add(critComponent);
234                     }
235 
236                 }
237             }
238             for (SearchAttributeCriteriaComponent criteriaComponent : searchableAttributes)
239             {
240                 if (criteriaComponent.isCanHoldMultipleValues())
241                 {
242                     List<String> values = checkForMultiValueSearchableAttributes.get(criteriaComponent.getFormKey());
243                     criteriaComponent.setValue(null);
244                     criteriaComponent.setValues(values);
245                 }
246             }
247         }
248 
249         return searchableAttributes;
250     }
251 
252     /**
253      * This method takes the given <code>propertyFields</code> parameter and populates the {@link DocSearchCriteriaDTO}
254      * object search attributes based on the document type name set on the <code>criteria</code> object.<br>
255      * <br>
256      * This is identical to calling {@link #addSearchableAttributesToCriteria(DocSearchCriteriaDTO, List, String, boolean)}
257      * with a boolean value of false for the <code>setAttributesStrictly</code> parameter.
258      *
259      * @param criteria -
260      *            The object that needs a list of {@link SearchAttributeCriteriaComponent} objects set up based on the
261      *            document type name and <code>propertyFields</code> parameter
262      * @param propertyFields -
263      *            The list of {@link SearchAttributeFormContainer} objects that need to be converted to
264      *            {@link SearchAttributeCriteriaComponent} objects and set on the <code>criteria</code> parameter
265      * @param searchAttributesString -
266      *            A potential string that must be parsed to use to set attributes on the <code>criteria</code> object
267      */
268     public static void addSearchableAttributesToCriteria(DocSearchCriteriaDTO criteria, List propertyFields, String searchAttributesString) {
269         addSearchableAttributesToCriteria(criteria, propertyFields, searchAttributesString, false);
270     }
271 
272     /**
273      * This method takes the given <code>propertyFields</code> parameter and populates the {@link DocSearchCriteriaDTO}
274      * object search attributes based on the document type name set on the <code>criteria</code> object.<br>
275      * <br>
276      * This is identical to calling {@link #addSearchableAttributesToCriteria(DocSearchCriteriaDTO, List, String, boolean)}
277      * with a null value for the <code>searchAttributesString</code> parameter.
278      *
279      * @param criteria -
280      *            The object that needs a list of {@link SearchAttributeCriteriaComponent} objects set up based on the
281      *            document type name and <code>propertyFields</code> parameter
282      * @param propertyFields -
283      *            The list of {@link SearchAttributeFormContainer} objects that need to be converted to
284      *            {@link SearchAttributeCriteriaComponent} objects and set on the <code>criteria</code> parameter
285      * @param setAttributesStrictly -
286      *            A boolean to specify whether to explicitly throw an error when a given value from
287      *            <code>propertyFields</code> does not match a search attribute on the specified document type. If set to
288      *            true an error with be thrown. If set to false the mismatch will be ignored.
289      */
290     public static void addSearchableAttributesToCriteria(DocSearchCriteriaDTO criteria, List propertyFields, boolean setAttributesStrictly) {
291         addSearchableAttributesToCriteria(criteria, propertyFields, null, setAttributesStrictly);
292     }
293 
294     /**
295      * This method takes the given <code>propertyFields</code> parameter and populates the {@link DocSearchCriteriaDTO}
296      * object search attributes based on the document type name set on the <code>criteria</code> object.
297      *
298      * @param criteria -
299      *            The object that needs a list of {@link SearchAttributeCriteriaComponent} objects set up based on the
300      *            document type name and <code>propertyFields</code> parameter
301      * @param propertyFields -
302      *            The list of {@link SearchAttributeFormContainer} objects that need to be converted to
303      *            {@link SearchAttributeCriteriaComponent} objects and set on the <code>criteria</code> parameter
304      * @param searchAttributesString -
305      *            A potential string that must be parsed to use to set attributes on the <code>criteria</code> object
306      * @param setAttributesStrictly -
307      *            A boolean to specify whether to explicitly throw an error when a given value from
308      *            <code>propertyFields</code> does not match a search attribute on the specified document type. If set to
309      *            true an error with be thrown. If set to false the mismatch will be ignored.
310      */
311     public static void addSearchableAttributesToCriteria(DocSearchCriteriaDTO criteria, List propertyFields, String searchAttributesString, boolean setAttributesStrictly) {
312         if (criteria != null) {
313             DocumentType docType = getDocumentType(criteria.getDocTypeFullName());
314             if (docType == null) {
315                 return;
316             }
317             criteria.getSearchableAttributes().clear();
318             Map<String, SearchAttributeCriteriaComponent> urlParameterSearchAttributesByFormKey = new HashMap<String, SearchAttributeCriteriaComponent>();
319 
320             if (!StringUtils.isBlank(searchAttributesString)) {
321                 List<SearchAttributeCriteriaComponent> components = buildSearchableAttributesFromString(searchAttributesString, docType.getName());
322                 for (SearchAttributeCriteriaComponent component : components) {
323                     urlParameterSearchAttributesByFormKey.put(component.getFormKey(), component);
324                     criteria.addSearchableAttribute(component);
325                 }
326             }
327 
328             if (!propertyFields.isEmpty()) {
329                 Map<String, SearchAttributeCriteriaComponent> criteriaComponentsByFormKey = new HashMap<String, SearchAttributeCriteriaComponent>();
330 
331                 DocumentLookupConfiguration lookupConfiguration = KEWServiceLocator.getDocumentLookupCustomizationMediator().getDocumentLookupConfiguration(docType);
332                 List<RemotableAttributeField> searchFields = lookupConfiguration.getFlattenedSearchAttributeFields();
333 
334                 for (RemotableAttributeField searchField : searchFields) {
335                     List<SearchAttributeCriteriaComponent> components = DocSearchUtils.translateSearchFieldToCriteriaComponent(searchField);
336                     for (SearchAttributeCriteriaComponent searchableAttributeComponent : components) {
337                         criteriaComponentsByFormKey.put(searchField.getName(), searchableAttributeComponent);
338                     }
339                 }
340 
341                 for (Iterator iterator = propertyFields.iterator(); iterator.hasNext();) {
342                     SearchAttributeFormContainer propertyField = (SearchAttributeFormContainer) iterator.next();
343                     SearchAttributeCriteriaComponent sacc = criteriaComponentsByFormKey.get(propertyField.getKey());
344                     if (sacc != null) {
345                         if (sacc.getSearchableAttributeValue() == null) {
346                             String errorMsg = "Searchable attribute with form field key " + sacc.getFormKey() + " does not have a valid SearchableAttributeValue";
347                             LOG.error("addSearchableAttributesToCriteria() " + errorMsg);
348                             throw new RuntimeException(errorMsg);
349                         }
350                         // if the url parameter has already set up the search attribute change the propertyField
351                         if (urlParameterSearchAttributesByFormKey.containsKey(sacc.getFormKey())) {
352                             setupPropertyField(urlParameterSearchAttributesByFormKey.get(sacc.getFormKey()), propertyFields);
353                         } else {
354                             if (Field.MULTI_VALUE_FIELD_TYPES.contains(sacc.getLookupableFieldType())) {
355                                 // set the multivalue lookup indicator
356                                 sacc.setCanHoldMultipleValues(true);
357                                 if (propertyField.getValues() == null) {
358                                     sacc.setValues(new ArrayList<String>());
359                                 } else {
360                                     sacc.setValues(Arrays.asList(propertyField.getValues()));
361                                 }
362                             } else {
363                                 sacc.setValue(propertyField.getValue());
364                             }
365                             criteria.addSearchableAttribute(sacc);
366                         }
367                     } else {
368                         if (setAttributesStrictly) {
369                             String message = "Cannot find matching search attribute with key '" + propertyField.getKey() + "' on document type '" + docType.getName() + "'";
370                             LOG.error(message);
371                             throw new WorkflowRuntimeException(message);
372                         }
373                     }
374                 }
375             }
376         }
377     }
378 
379     public static void setupPropertyField(SearchAttributeCriteriaComponent searchableAttribute, List propertyFields) {
380         SearchAttributeFormContainer propertyField = getPropertyField(searchableAttribute.getFormKey(), propertyFields);
381         if (propertyField != null) {
382             propertyField.setValue(searchableAttribute.getValue());
383             if (searchableAttribute.getValues() != null) {
384                 propertyField.setValues(searchableAttribute.getValues().toArray(new String[searchableAttribute.getValues().size()]));
385             }
386         }
387     }
388 
389     public static SearchAttributeFormContainer getPropertyField(String key, List propertyFields) {
390         if (StringUtils.isBlank(key)) {
391             return null;
392         }
393         for (Iterator iter = propertyFields.iterator(); iter.hasNext();) {
394             SearchAttributeFormContainer container = (SearchAttributeFormContainer) iter.next();
395             if (key.equals(container.getKey())) {
396                 return container;
397             }
398         }
399         return null;
400     }
401 
402     private static DocumentType getDocumentType(String docTypeName) {
403         if ((docTypeName != null && !"".equals(docTypeName))) {
404             return ((DocumentTypeService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE)).findByName(docTypeName);
405         }
406         return null;
407     }
408 
409 }