1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.kew.docsearch;
17
18 import java.io.IOException;
19 import java.sql.Date;
20 import java.sql.Timestamp;
21 import java.text.ParseException;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.EnumSet;
26 import java.util.List;
27
28 import org.apache.commons.collections.CollectionUtils;
29 import org.apache.commons.lang.StringUtils;
30 import org.apache.log4j.Logger;
31 import org.codehaus.jackson.map.ObjectMapper;
32 import org.codehaus.jackson.map.annotate.JsonSerialize;
33 import org.joda.time.DateTime;
34 import org.joda.time.MutableDateTime;
35 import org.kuali.rice.core.api.CoreApiServiceLocator;
36 import org.kuali.rice.core.api.data.DataType;
37 import org.kuali.rice.core.api.reflect.ObjectDefinition;
38 import org.kuali.rice.core.api.search.Range;
39 import org.kuali.rice.core.api.search.SearchExpressionUtils;
40 import org.kuali.rice.core.api.uif.AttributeLookupSettings;
41 import org.kuali.rice.core.api.uif.RemotableAttributeError;
42 import org.kuali.rice.core.api.uif.RemotableAttributeField;
43 import org.kuali.rice.core.api.util.ClassLoaderUtils;
44 import org.kuali.rice.core.api.util.RiceConstants;
45 import org.kuali.rice.core.api.util.RiceKeyConstants;
46 import org.kuali.rice.core.framework.persistence.jdbc.sql.SQLUtils;
47 import org.kuali.rice.core.framework.resourceloader.ObjectDefinitionResolver;
48 import org.kuali.rice.kew.api.KewApiConstants;
49 import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
50 import org.kuali.rice.krad.util.GlobalVariables;
51
52 import com.google.common.base.Function;
53
54
55
56
57
58
59 public class DocumentSearchInternalUtils {
60
61 private static final Logger LOG = Logger.getLogger(DocumentSearchInternalUtils.class);
62
63 private static final boolean CASE_SENSITIVE_DEFAULT = false;
64
65 private static final String STRING_ATTRIBUTE_TABLE_NAME = "KREW_DOC_HDR_EXT_T";
66 private static final String DATE_TIME_ATTRIBUTE_TABLE_NAME = "KREW_DOC_HDR_EXT_DT_T";
67 private static final String DECIMAL_ATTRIBUTE_TABLE_NAME = "KREW_DOC_HDR_EXT_FLT_T";
68 private static final String INTEGER_ATTRIBUTE_TABLE_NAME = "KREW_DOC_HDR_EXT_LONG_T";
69
70 private static final List<SearchableAttributeConfiguration> CONFIGURATIONS =
71 new ArrayList<SearchableAttributeConfiguration>();
72 public static final List<Class<? extends SearchableAttributeValue>> SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST =
73 new ArrayList<Class<? extends SearchableAttributeValue>>();
74
75 static {
76 SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST.add(SearchableAttributeStringValue.class);
77 SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST.add(SearchableAttributeFloatValue.class);
78 SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST.add(SearchableAttributeLongValue.class);
79 SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST.add(SearchableAttributeDateTimeValue.class);
80 }
81
82 static {
83
84 CONFIGURATIONS.add(new SearchableAttributeConfiguration(
85 STRING_ATTRIBUTE_TABLE_NAME,
86 EnumSet.of(DataType.BOOLEAN, DataType.STRING, DataType.MARKUP),
87 String.class));
88
89 CONFIGURATIONS.add(new SearchableAttributeConfiguration(
90 DATE_TIME_ATTRIBUTE_TABLE_NAME,
91 EnumSet.of(DataType.DATE, DataType.TRUNCATED_DATE, DataType.DATETIME),
92 Timestamp.class));
93
94 CONFIGURATIONS.add(new SearchableAttributeConfiguration(
95 DECIMAL_ATTRIBUTE_TABLE_NAME,
96 EnumSet.of(DataType.FLOAT, DataType.DOUBLE, DataType.CURRENCY),
97 Float.TYPE));
98
99 CONFIGURATIONS.add(new SearchableAttributeConfiguration(
100 INTEGER_ATTRIBUTE_TABLE_NAME,
101 EnumSet.of(DataType.INTEGER, DataType.LONG),
102 Long.TYPE));
103
104 }
105
106
107
108
109
110
111 private static ObjectMapper getObjectMapper() { return ObjectMapperHolder.objectMapper; }
112
113 private static class ObjectMapperHolder {
114 static final ObjectMapper objectMapper = initializeObjectMapper();
115
116 private static ObjectMapper initializeObjectMapper() {
117 ObjectMapper jsonMapper = new ObjectMapper();
118 jsonMapper.getSerializationConfig().setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
119 return jsonMapper;
120 }
121 }
122
123 public static boolean isLookupCaseSensitive(RemotableAttributeField remotableAttributeField) {
124 if (remotableAttributeField == null) {
125 throw new IllegalArgumentException("remotableAttributeField was null");
126 }
127 AttributeLookupSettings lookupSettings = remotableAttributeField.getAttributeLookupSettings();
128 if (lookupSettings != null) {
129 if (lookupSettings.isCaseSensitive() != null) {
130 return lookupSettings.isCaseSensitive().booleanValue();
131 }
132 }
133 return CASE_SENSITIVE_DEFAULT;
134 }
135
136 public static String getAttributeTableName(RemotableAttributeField attributeField) {
137 return getConfigurationForField(attributeField).getTableName();
138 }
139
140 public static Class<?> getDataTypeClass(RemotableAttributeField attributeField) {
141 return getConfigurationForField(attributeField).getDataTypeClass();
142 }
143
144 private static SearchableAttributeConfiguration getConfigurationForField(RemotableAttributeField attributeField) {
145 for (SearchableAttributeConfiguration configuration : CONFIGURATIONS) {
146 DataType dataType = attributeField.getDataType();
147 if (dataType == null) {
148 dataType = DataType.STRING;
149 }
150 if (configuration.getSupportedDataTypes().contains(dataType)) {
151 return configuration;
152 }
153 }
154 throw new IllegalArgumentException("Failed to determine proper searchable attribute configuration for given data type of '" + attributeField.getDataType() + "'");
155 }
156
157 public static List<SearchableAttributeValue> getSearchableAttributeValueObjectTypes() {
158 List<SearchableAttributeValue> searchableAttributeValueClasses = new ArrayList<SearchableAttributeValue>();
159 for (Class<? extends SearchableAttributeValue> searchAttributeValueClass : SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST) {
160 ObjectDefinition objDef = new ObjectDefinition(searchAttributeValueClass);
161 SearchableAttributeValue attributeValue = (SearchableAttributeValue) ObjectDefinitionResolver.createObject(
162 objDef, ClassLoaderUtils.getDefaultClassLoader(), false);
163 searchableAttributeValueClasses.add(attributeValue);
164 }
165 return searchableAttributeValueClasses;
166 }
167
168 public static SearchableAttributeValue getSearchableAttributeValueByDataTypeString(String dataType) {
169 SearchableAttributeValue returnableValue = null;
170 if (StringUtils.isBlank(dataType)) {
171 return returnableValue;
172 }
173 for (SearchableAttributeValue attValue : getSearchableAttributeValueObjectTypes())
174 {
175 if (dataType.equalsIgnoreCase(attValue.getAttributeDataType()))
176 {
177 if (returnableValue != null)
178 {
179 String errorMsg = "Found two SearchableAttributeValue objects with same data type string ('" + dataType + "' while ignoring case): " + returnableValue.getClass().getName() + " and " + attValue.getClass().getName();
180 LOG.error("getSearchableAttributeValueByDataTypeString() " + errorMsg);
181 throw new RuntimeException(errorMsg);
182 }
183 LOG.debug("getSearchableAttributeValueByDataTypeString() SearchableAttributeValue class name is " + attValue.getClass().getName() + "... ojbConcreteClassName is " + attValue.getOjbConcreteClass());
184 ObjectDefinition objDef = new ObjectDefinition(attValue.getClass());
185 returnableValue = (SearchableAttributeValue) ObjectDefinitionResolver.createObject(objDef, ClassLoaderUtils.getDefaultClassLoader(), false);
186 }
187 }
188 return returnableValue;
189 }
190
191 public static String getDisplayValueWithDateOnly(DateTime value) {
192 return getDisplayValueWithDateOnly(new Timestamp(value.getMillis()));
193 }
194
195 public static String getDisplayValueWithDateOnly(Timestamp value) {
196 return RiceConstants.getDefaultDateFormat().format(new Date(value.getTime()));
197 }
198
199 public static DateTime getLowerDateTimeBound(String dateRange) throws ParseException {
200 Range range = SearchExpressionUtils.parseRange(dateRange);
201 if (range == null) {
202 throw new IllegalArgumentException("Failed to parse date range from given string: " + dateRange);
203 }
204 if (range.getLowerBoundValue() != null) {
205 java.util.Date lowerRangeDate = null;
206 try{
207 lowerRangeDate = CoreApiServiceLocator.getDateTimeService().convertToDate(range.getLowerBoundValue());
208 }catch(ParseException pe){
209 GlobalVariables.getMessageMap().putError("dateFrom", RiceKeyConstants.ERROR_CUSTOM, pe.getMessage());
210 }
211 MutableDateTime dateTime = new MutableDateTime(lowerRangeDate);
212 dateTime.setMillisOfDay(0);
213 return dateTime.toDateTime();
214 }
215 return null;
216 }
217
218 public static DateTime getUpperDateTimeBound(String dateRange) throws ParseException {
219 Range range = SearchExpressionUtils.parseRange(dateRange);
220 if (range == null) {
221 throw new IllegalArgumentException("Failed to parse date range from given string: " + dateRange);
222 }
223 if (range.getUpperBoundValue() != null) {
224 java.util.Date upperRangeDate = null;
225 try{
226 upperRangeDate = CoreApiServiceLocator.getDateTimeService().convertToDate(range.getUpperBoundValue());
227 }catch(ParseException pe){
228 GlobalVariables.getMessageMap().putError("dateCreated", RiceKeyConstants.ERROR_CUSTOM, pe.getMessage());
229 }
230 MutableDateTime dateTime = new MutableDateTime(upperRangeDate);
231
232 dateTime.setMillisOfDay((24 * 60 * 60 * 1000) - 1);
233 return dateTime.toDateTime();
234 }
235 return null;
236 }
237
238 public static class SearchableAttributeConfiguration {
239
240 private final String tableName;
241 private final EnumSet<DataType> supportedDataTypes;
242 private final Class<?> dataTypeClass;
243
244 public SearchableAttributeConfiguration(String tableName,
245 EnumSet<DataType> supportedDataTypes,
246 Class<?> dataTypeClass) {
247 this.tableName = tableName;
248 this.supportedDataTypes = supportedDataTypes;
249 this.dataTypeClass = dataTypeClass;
250 }
251
252 public String getTableName() {
253 return tableName;
254 }
255
256 public EnumSet<DataType> getSupportedDataTypes() {
257 return supportedDataTypes;
258 }
259
260 public Class<?> getDataTypeClass() {
261 return dataTypeClass;
262 }
263
264 }
265
266
267
268
269
270
271
272 public static DocumentSearchCriteria unmarshalDocumentSearchCriteria(String string) throws IOException {
273 DocumentSearchCriteria.Builder builder = getObjectMapper().readValue(string, DocumentSearchCriteria.Builder.class);
274
275 builder.normalizeDateTimes();
276
277 return builder.build();
278 }
279
280
281
282
283
284
285
286 public static String marshalDocumentSearchCriteria(DocumentSearchCriteria criteria) throws IOException {
287
288
289
290
291
292
293 return getObjectMapper().writeValueAsString(criteria);
294 }
295
296 public static List<RemotableAttributeError> validateSearchFieldValues(String fieldName, SearchableAttributeValue attributeValue, List<String> searchValues, String errorMessagePrefix, List<String> resultingValues, Function<String, Collection<RemotableAttributeError>> customValidator) {
297 List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>();
298
299 if (CollectionUtils.isEmpty(searchValues)) {
300 return errors;
301 }
302 for (String searchValue: searchValues) {
303 errors.addAll(validateSearchFieldValue(fieldName, attributeValue, searchValue, errorMessagePrefix, resultingValues, customValidator));
304 }
305 return Collections.unmodifiableList(errors);
306 }
307
308
309
310
311
312
313
314
315
316
317
318 public static List<RemotableAttributeError> validateSearchFieldValue(String fieldName, SearchableAttributeValue attributeValue, String enteredValue, String errorMessagePrefix, List<String> resultingValues, Function<String, Collection<RemotableAttributeError>> customValidator) {
319 List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>();
320 if (enteredValue == null) {
321 return errors;
322 }
323
324
325 List<String> parsedValues = SQLUtils.getCleanedSearchableValues(enteredValue, attributeValue.getAttributeDataType());
326 for (String value: parsedValues) {
327 errors.addAll(validateParsedSearchFieldValue(fieldName, attributeValue, value, errorMessagePrefix, resultingValues, customValidator));
328 }
329 return errors;
330 }
331
332
333
334
335
336
337
338
339
340
341
342 public static Collection<RemotableAttributeError> validateParsedSearchFieldValue(String fieldName, SearchableAttributeValue attributeValue, String parsedValue, String errorMessagePrefix, List<String> resultingValues, Function<String, Collection<RemotableAttributeError>> customValidator) {
343 Collection<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>(1);
344 String value = parsedValue;
345 if (attributeValue.allowsWildcards()) {
346 value = value.replaceAll(KewApiConstants.SearchableAttributeConstants.SEARCH_WILDCARD_CHARACTER_REGEX_ESCAPED, "");
347 }
348
349 if (resultingValues != null) {
350 resultingValues.add(value);
351 }
352
353 if (!attributeValue.isPassesDefaultValidation(value)) {
354 errorMessagePrefix = (StringUtils.isNotBlank(errorMessagePrefix)) ? errorMessagePrefix : "Field";
355 String errorMsg = errorMessagePrefix + " with value '" + value + "' does not conform to standard validation for field type.";
356 LOG.debug("validateSimpleSearchFieldValue: " + errorMsg + " :: field type '" + attributeValue.getAttributeDataType() + "'");
357 errors.add(RemotableAttributeError.Builder.create(fieldName, errorMsg).build());
358 } else if (customValidator != null) {
359 errors.addAll(customValidator.apply(value));
360 }
361
362 return Collections.unmodifiableCollection(errors);
363 }
364
365
366
367
368
369
370 public static DataType convertValueToDataType(String dataTypeValue) {
371 if (StringUtils.isBlank(dataTypeValue)) {
372 return DataType.STRING;
373 } else if (KewApiConstants.SearchableAttributeConstants.DATA_TYPE_STRING.equals(dataTypeValue)) {
374 return DataType.STRING;
375 } else if (KewApiConstants.SearchableAttributeConstants.DATA_TYPE_DATE.equals(dataTypeValue)) {
376 return DataType.DATE;
377 } else if (KewApiConstants.SearchableAttributeConstants.DATA_TYPE_LONG.equals(dataTypeValue)) {
378 return DataType.LONG;
379 } else if (KewApiConstants.SearchableAttributeConstants.DATA_TYPE_FLOAT.equals(dataTypeValue)) {
380 return DataType.FLOAT;
381 }
382 throw new IllegalArgumentException("Invalid dataTypeValue was given: " + dataTypeValue);
383 }
384
385 }