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