1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.kew.impl.document.search;
17
18 import org.apache.commons.beanutils.PropertyUtils;
19 import org.apache.commons.lang.ObjectUtils;
20 import org.apache.commons.lang.StringUtils;
21 import org.apache.log4j.Logger;
22 import org.joda.time.DateTime;
23 import org.kuali.rice.core.api.search.Range;
24 import org.kuali.rice.core.api.search.SearchExpressionUtils;
25 import org.kuali.rice.core.api.uif.AttributeLookupSettings;
26 import org.kuali.rice.core.api.uif.RemotableAttributeField;
27 import org.kuali.rice.kew.api.KEWPropertyConstants;
28 import org.kuali.rice.kew.api.KewApiConstants;
29 import org.kuali.rice.kew.api.document.DocumentStatus;
30 import org.kuali.rice.kew.api.document.DocumentStatusCategory;
31 import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
32 import org.kuali.rice.kew.api.document.search.DocumentSearchCriteriaContract;
33 import org.kuali.rice.kew.api.document.search.RouteNodeLookupLogic;
34 import org.kuali.rice.kew.docsearch.DocumentSearchInternalUtils;
35 import org.kuali.rice.kew.doctype.bo.DocumentType;
36 import org.kuali.rice.kew.framework.document.search.DocumentSearchCriteriaConfiguration;
37 import org.kuali.rice.kew.impl.document.ApplicationDocumentStatusUtils;
38 import org.kuali.rice.kew.service.KEWServiceLocator;
39 import org.kuali.rice.krad.util.KRADConstants;
40
41 import java.lang.reflect.InvocationTargetException;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Collection;
45 import java.util.HashMap;
46 import java.util.HashSet;
47 import java.util.LinkedHashMap;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Set;
51
52
53
54
55
56
57 public class DocumentSearchCriteriaTranslatorImpl implements DocumentSearchCriteriaTranslator {
58
59 private static final Logger LOG = Logger.getLogger(DocumentSearchCriteriaTranslatorImpl.class);
60
61 private static final String DOCUMENT_STATUSES = "documentStatuses";
62 private static final String ROUTE_NODE_LOOKUP_LOGIC = "routeNodeLookupLogic";
63
64
65
66
67 private static final String[] DIRECT_TRANSLATE_FIELD_NAMES = {
68 "documentId",
69 "applicationDocumentId",
70 "applicationDocumentStatus",
71 "initiatorPrincipalName",
72 "viewerPrincipalName",
73 "groupViewerId",
74 "approverPrincipalName",
75 "routeNodeName",
76 "documentTypeName",
77 "saveName",
78 "title",
79 "isAdvancedSearch"
80 };
81 private static final Set<String> DIRECT_TRANSLATE_FIELD_NAMES_SET =
82 new HashSet<String>(Arrays.asList(DIRECT_TRANSLATE_FIELD_NAMES));
83
84 private static final String[] DATE_RANGE_TRANSLATE_FIELD_NAMES = {
85 "dateCreated",
86 "dateLastModified",
87 "dateApproved",
88 "dateFinalized"
89 };
90 private static final Set<String> DATE_RANGE_TRANSLATE_FIELD_NAMES_SET =
91 new HashSet<String>(Arrays.asList(DATE_RANGE_TRANSLATE_FIELD_NAMES));
92
93 @Override
94 public DocumentSearchCriteria translateFieldsToCriteria(Map<String, String> fieldValues) {
95
96 DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
97 List<String> documentAttributeFields = new ArrayList<String>();
98 for (Map.Entry<String, String> field : fieldValues.entrySet()) {
99 try {
100 if (StringUtils.isNotBlank(field.getValue())) {
101 if (DIRECT_TRANSLATE_FIELD_NAMES_SET.contains(field.getKey())) {
102 PropertyUtils.setNestedProperty(criteria, field.getKey(), field.getValue());
103 } else if (DATE_RANGE_TRANSLATE_FIELD_NAMES_SET.contains(field.getKey())) {
104 applyDateRangeField(criteria, field.getKey(), field.getValue());
105 } else if (field.getKey().startsWith(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX)) {
106 documentAttributeFields.add(field.getKey());
107 }
108
109 }
110 } catch (Exception e) {
111 throw new IllegalStateException("Failed to set document search criteria field: " + field.getKey(), e);
112 }
113 }
114
115 if (!documentAttributeFields.isEmpty()) {
116 translateDocumentAttributeFieldsToCriteria(fieldValues, documentAttributeFields, criteria);
117 }
118
119 String routeNodeLookupLogic = fieldValues.get(ROUTE_NODE_LOOKUP_LOGIC);
120 if (StringUtils.isNotBlank(routeNodeLookupLogic)) {
121 criteria.setRouteNodeLookupLogic(RouteNodeLookupLogic.valueOf(routeNodeLookupLogic));
122 }
123
124 String documentStatusesValue = fieldValues.get(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_STATUS_CODE);
125 if (StringUtils.isNotBlank(documentStatusesValue)) {
126 String[] documentStatuses = documentStatusesValue.split(",");
127 for (String documentStatus : documentStatuses) {
128 if (documentStatus.startsWith("category:")) {
129 String categoryCode = StringUtils.remove(documentStatus, "category:");
130 criteria.getDocumentStatusCategories().add(DocumentStatusCategory.fromCode(categoryCode));
131 } else {
132 criteria.getDocumentStatuses().add(DocumentStatus.fromCode(documentStatus));
133 }
134 }
135 }
136
137 LinkedHashMap<String, List<String>> applicationDocumentStatusGroupings =
138 ApplicationDocumentStatusUtils.getApplicationDocumentStatusCategories(criteria.getDocumentTypeName());
139
140 String applicationDocumentStatusesValue = fieldValues.get(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_DOC_STATUS);
141 if (StringUtils.isNotBlank(applicationDocumentStatusesValue)) {
142 String[] applicationDocumentStatuses = applicationDocumentStatusesValue.split(",");
143 for (String applicationDocumentStatus : applicationDocumentStatuses) {
144
145 if (applicationDocumentStatus.startsWith("category:")) {
146 String categoryCode = StringUtils.remove(applicationDocumentStatus, "category:");
147 if (applicationDocumentStatusGroupings.containsKey(categoryCode)) {
148 criteria.getApplicationDocumentStatuses().addAll(applicationDocumentStatusGroupings.get(categoryCode));
149 }
150 } else {
151 criteria.getApplicationDocumentStatuses().add(applicationDocumentStatus);
152 }
153 }
154 }
155
156
157 criteria.setApplicationDocumentStatus(null);
158
159 return criteria.build();
160 }
161
162
163
164
165
166
167 public Map<String, String[]> translateCriteriaToFields(DocumentSearchCriteria criteria) {
168 Map<String, String[]> values = new HashMap<String, String[]>();
169
170 for (String property: DIRECT_TRANSLATE_FIELD_NAMES) {
171 convertCriteriaPropertyToField(criteria, property, values);
172 }
173
174 for (String property: DATE_RANGE_TRANSLATE_FIELD_NAMES) {
175 convertCriteriaRangeField(criteria, property, values);
176 }
177
178 Map<String, List<String>> docAttrValues = criteria.getDocumentAttributeValues();
179 if (!docAttrValues.isEmpty()) {
180 Map<String, AttributeLookupSettings> attributeLookupSettingsMap = getAttributeLookupSettings(criteria);
181 for (Map.Entry<String, List<String>> entry: docAttrValues.entrySet()) {
182 AttributeLookupSettings lookupSettings = attributeLookupSettingsMap.get(entry.getKey());
183 if (lookupSettings != null && lookupSettings.isRanged()) {
184 convertAttributeRangeField(entry.getKey(), entry.getValue(), values);
185 } else {
186 values.put(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX + entry.getKey(), entry.getValue().toArray(new String[0]));
187 }
188 }
189 }
190
191 RouteNodeLookupLogic lookupLogic = criteria.getRouteNodeLookupLogic();
192 if (lookupLogic != null) {
193 values.put(ROUTE_NODE_LOOKUP_LOGIC, new String[]{lookupLogic.name()});
194 }
195
196 Collection<String> statuses = new ArrayList<String>();
197 for (DocumentStatus status: criteria.getDocumentStatuses()) {
198 statuses.add(status.getCode());
199 }
200 for (DocumentStatusCategory category: criteria.getDocumentStatusCategories()) {
201 statuses.add("category:" + category.getCode());
202 }
203 values.put(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_STATUS_CODE, statuses.toArray(new String[0]));
204
205 return values;
206 }
207
208
209
210
211
212
213
214
215
216
217 protected static void convertAttributeRangeField(String attrKey, List<String> attrValues, Map<String, String[]> values) {
218 String value = "";
219 if (attrValues != null && !attrValues.isEmpty()) {
220 value = attrValues.get(0);
221
222 if (attrValues.size() > 1) {
223 LOG.warn("Encountered multi-valued ranged document search attribute '" + attrKey + "': " + attrValues);
224 }
225 }
226 Range range = SearchExpressionUtils.parseRange(value);
227 String lower;
228 String upper;
229 if (range != null) {
230 lower = range.getLowerBoundValue();
231 upper = range.getUpperBoundValue();
232 } else {
233 lower = null;
234 upper = value;
235 }
236 values.put(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX + KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + attrKey, new String[] { lower });
237 values.put(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX + attrKey, new String[] { upper });
238 }
239
240
241
242
243
244
245
246 protected static void convertCriteriaRangeField(DocumentSearchCriteria criteria, String property, Map<String, String[]> values) {
247 convertCriteriaPropertyToField(criteria, property + "From", KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + property, values);
248 convertCriteriaPropertyToField(criteria, property + "To", property, values);
249 }
250
251
252
253
254
255
256
257 protected static void convertCriteriaPropertyToField(DocumentSearchCriteria criteria, String property, Map<String, String[]> values) {
258 convertCriteriaPropertyToField(criteria, property, property, values);
259 }
260
261
262
263
264
265
266
267
268 protected static void convertCriteriaPropertyToField(DocumentSearchCriteria criteria, String property, String fieldName, Map<String, String[]> values) {
269 try {
270 Object val = PropertyUtils.getProperty(criteria, property);
271 if (val != null) {
272 values.put(fieldName, new String[] { ObjectUtils.toString(val) });
273 }
274 } catch (NoSuchMethodException nsme) {
275 LOG.error("Error reading property '" + property + "' of criteria", nsme);
276 } catch (InvocationTargetException ite) {
277 LOG.error("Error reading property '" + property + "' of criteria", ite);
278 } catch (IllegalAccessException iae) {
279 LOG.error("Error reading property '" + property + "' of criteria", iae);
280
281 }
282 }
283
284 protected void applyDateRangeField(DocumentSearchCriteria.Builder criteria, String fieldName, String fieldValue) throws Exception {
285 DateTime lowerDateTime = DocumentSearchInternalUtils.getLowerDateTimeBound(fieldValue);
286 DateTime upperDateTime = DocumentSearchInternalUtils.getUpperDateTimeBound(fieldValue);
287 if (lowerDateTime != null) {
288 PropertyUtils.setNestedProperty(criteria, fieldName + "From", lowerDateTime);
289 }
290 if (upperDateTime != null) {
291 PropertyUtils.setNestedProperty(criteria, fieldName + "To", upperDateTime);
292 }
293 }
294
295
296
297
298
299
300 protected Map<String, AttributeLookupSettings> getAttributeLookupSettings(DocumentSearchCriteriaContract criteria) {
301 String documentTypeName = criteria.getDocumentTypeName();
302 Map<String, AttributeLookupSettings> attributeLookupSettingsMap = new HashMap<java.lang.String, AttributeLookupSettings>();
303
304 if (StringUtils.isNotEmpty(documentTypeName)) {
305 DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByNameCaseInsensitive(documentTypeName);
306 if (documentType != null) {
307 DocumentSearchCriteriaConfiguration configuration = KEWServiceLocator.getDocumentSearchCustomizationMediator().getDocumentSearchCriteriaConfiguration(
308 documentType);
309 if (configuration != null) {
310 List<RemotableAttributeField> remotableAttributeFields = configuration.getFlattenedSearchAttributeFields();
311 for (RemotableAttributeField raf: remotableAttributeFields) {
312 attributeLookupSettingsMap.put(raf.getName(), raf.getAttributeLookupSettings());
313 }
314 }
315 } else {
316 LOG.error("Searching against unknown document type '" + documentTypeName + "'; searchable attribute ranges will not work.");
317 }
318 }
319
320 return attributeLookupSettingsMap;
321 }
322
323 protected String translateRangePropertyToExpression(Map<String, String> fieldValues, String property, String prefix, AttributeLookupSettings settings) {
324 String lowerBoundValue = fieldValues.get(prefix + KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + property);
325 String upperBoundValue = fieldValues.get(prefix + property);
326
327 Range range = new Range();
328
329 range.setLowerBoundInclusive(settings.isLowerBoundInclusive());
330 range.setUpperBoundInclusive(settings.isUpperBoundInclusive());
331 range.setLowerBoundValue(lowerBoundValue);
332 range.setUpperBoundValue(upperBoundValue);
333
334 String expr = range.toString();
335 if (StringUtils.isEmpty(expr)) {
336 expr = upperBoundValue;
337 }
338 return expr;
339 }
340
341 protected void translateDocumentAttributeFieldsToCriteria(Map<String, String> fieldValues, List<String> fields, DocumentSearchCriteria.Builder criteria) {
342 Map<String, AttributeLookupSettings> attributeLookupSettingsMap = getAttributeLookupSettings(criteria);
343 for (String field: fields) {
344 String documentAttributeName = field.substring(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX.length());
345
346 if (documentAttributeName.startsWith(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX)) {
347 continue;
348 }
349 String value = fieldValues.get(field);
350 AttributeLookupSettings lookupSettings = attributeLookupSettingsMap.get(documentAttributeName);
351 if (lookupSettings != null && lookupSettings.isRanged()) {
352 value = translateRangePropertyToExpression(fieldValues, documentAttributeName, KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX, lookupSettings);
353 }
354 applyDocumentAttribute(criteria, documentAttributeName, value);
355 }
356 }
357
358 protected void applyDocumentAttribute(DocumentSearchCriteria.Builder criteria, String documentAttributeName, String attributeValue) {
359 criteria.addDocumentAttributeValue(documentAttributeName, attributeValue);
360 }
361
362 }