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