1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.kuali.rice.kew.docsearch.xml;
18
19 import org.apache.commons.lang.StringUtils;
20 import org.kuali.rice.core.api.impex.xml.XmlConstants;
21 import org.kuali.rice.core.util.ConcreteKeyValue;
22 import org.kuali.rice.core.util.KeyValue;
23 import org.kuali.rice.core.util.xml.XmlJotter;
24 import org.kuali.rice.core.web.format.Formatter;
25 import org.kuali.rice.kew.api.WorkflowRuntimeException;
26 import org.kuali.rice.kew.attribute.XMLAttributeUtils;
27 import org.kuali.rice.kew.docsearch.DocSearchUtils;
28 import org.kuali.rice.kew.docsearch.DocumentSearchContext;
29 import org.kuali.rice.kew.docsearch.SearchableAttributeValue;
30 import org.kuali.rice.kew.rule.WorkflowAttributeValidationError;
31 import org.kuali.rice.kew.rule.bo.RuleAttribute;
32 import org.kuali.rice.kew.rule.xmlrouting.XPathHelper;
33 import org.kuali.rice.kew.util.KEWConstants;
34 import org.kuali.rice.kew.util.Utilities;
35 import org.kuali.rice.kim.api.group.Group;
36 import org.kuali.rice.kim.api.group.GroupService;
37 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
38 import org.kuali.rice.kns.web.ui.Field;
39 import org.kuali.rice.kns.web.ui.Row;
40 import org.kuali.rice.krad.UserSession;
41 import org.kuali.rice.krad.util.GlobalVariables;
42 import org.w3c.dom.Document;
43 import org.w3c.dom.Element;
44 import org.w3c.dom.NamedNodeMap;
45 import org.w3c.dom.Node;
46 import org.w3c.dom.NodeList;
47 import org.xml.sax.InputSource;
48
49 import javax.xml.parsers.DocumentBuilderFactory;
50 import javax.xml.xpath.XPath;
51 import javax.xml.xpath.XPathConstants;
52 import javax.xml.xpath.XPathExpressionException;
53 import java.io.BufferedReader;
54 import java.io.StringReader;
55 import java.util.ArrayList;
56 import java.util.Collection;
57 import java.util.HashMap;
58 import java.util.Iterator;
59 import java.util.List;
60 import java.util.Map;
61 import java.util.regex.Matcher;
62 import java.util.regex.Pattern;
63
64
65
66
67
68
69
70 public class StandardGenericXMLSearchableAttribute implements GenericXMLSearchableAttribute {
71 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(StandardGenericXMLSearchableAttribute.class);
72
73 private static final String FIELD_DEF_E = "fieldDef";
74
75 private Map paramMap = new HashMap();
76 private RuleAttribute ruleAttribute;
77 private List<Row> searchRows = new ArrayList<Row>();
78
79 public void setRuleAttribute(RuleAttribute ruleAttribute) {
80 this.ruleAttribute = ruleAttribute;
81 }
82
83 public void setParamMap(Map paramMap) {
84 this.paramMap = paramMap;
85 }
86
87 public Map getParamMap() {
88 return paramMap;
89 }
90
91 public String getSearchContent(DocumentSearchContext documentSearchContext) {
92 XPath xpath = XPathHelper.newXPath();
93 String findDocContent = "//searchingConfig/xmlSearchContent";
94 try {
95 Node xmlDocumentContent = (Node) xpath.evaluate(findDocContent, getConfigXML(), XPathConstants.NODE);
96 if (xmlDocumentContent != null && xmlDocumentContent.hasChildNodes()) {
97
98 String docContent = "";
99 NodeList customNodes = xmlDocumentContent.getChildNodes();
100 for (int i = 0; i < customNodes.getLength(); i++) {
101 Node childNode = customNodes.item(i);
102 docContent += XmlJotter.jotNode(childNode);
103 }
104 String findField = "//searchingConfig/" + FIELD_DEF_E;
105 NodeList nodes = (NodeList) xpath.evaluate(findField, getConfigXML(), XPathConstants.NODESET);
106 if (nodes == null || nodes.getLength() == 0) {
107 return "";
108 }
109 for (int i = 0; i < nodes.getLength(); i++) {
110 Node field = nodes.item(i);
111 NamedNodeMap fieldAttributes = field.getAttributes();
112 if (getParamMap() != null && !org.apache.commons.lang.StringUtils.isEmpty((String) getParamMap().get(fieldAttributes.getNamedItem("name").getNodeValue()))) {
113 docContent = docContent.replaceAll("%" + fieldAttributes.getNamedItem("name").getNodeValue() + "%", (String) getParamMap().get(fieldAttributes.getNamedItem("name").getNodeValue()));
114 }
115 }
116 return docContent;
117 } else {
118
119 StringBuffer documentContent = new StringBuffer("<xmlRouting>");
120 String findField = "//searchingConfig/" + FIELD_DEF_E;
121 NodeList nodes = (NodeList) xpath.evaluate(findField, getConfigXML(), XPathConstants.NODESET);
122 if (nodes == null || nodes.getLength() == 0) {
123 return "";
124 }
125 for (int i = 0; i < nodes.getLength(); i++) {
126 Node field = nodes.item(i);
127 NamedNodeMap fieldAttributes = field.getAttributes();
128 if (getParamMap() != null && !org.apache.commons.lang.StringUtils.isEmpty((String) getParamMap().get(fieldAttributes.getNamedItem("name").getNodeValue()))) {
129 documentContent.append("<field name=\"");
130 documentContent.append(fieldAttributes.getNamedItem("name").getNodeValue());
131 documentContent.append("\"><value>");
132 documentContent.append((String) getParamMap().get(fieldAttributes.getNamedItem("name").getNodeValue()));
133 documentContent.append("</value></field>");
134 }
135 }
136 documentContent.append("</xmlRouting>");
137 return documentContent.toString();
138 }
139 } catch (XPathExpressionException e) {
140 LOG.error("error in getSearchContent ", e);
141 throw new RuntimeException("Error trying to find xml content with xpath expression", e);
142 } catch (Exception e) {
143 LOG.error("error in getSearchContent attempting to find xml search content", e);
144 throw new RuntimeException("Error trying to get xml search content.", e);
145 }
146 }
147
148 public List getSearchStorageValues(DocumentSearchContext documentSearchContext) {
149 List<SearchableAttributeValue> searchStorageValues = new ArrayList<SearchableAttributeValue>();
150 Document document;
151 if (StringUtils.isBlank(documentSearchContext.getDocumentContent())) {
152 LOG.warn("Empty Document Content found '" + documentSearchContext.getDocumentContent() + "'");
153 return searchStorageValues;
154 }
155 try {
156 document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
157 new InputSource(new BufferedReader(new StringReader(documentSearchContext.getDocumentContent()))));
158 } catch (Exception e){
159 LOG.error("error parsing docContent: "+documentSearchContext.getDocumentContent(), e);
160 throw new RuntimeException("Error trying to parse docContent: "+documentSearchContext.getDocumentContent(), e);
161 }
162 XPath xpath = XPathHelper.newXPath(document);
163 String findField = "//searchingConfig/" + FIELD_DEF_E;
164 try {
165 NodeList nodes = (NodeList) xpath.evaluate(findField, getConfigXML(), XPathConstants.NODESET);
166 if (nodes == null) {
167 LOG.error("Could not find searching configuration (<searchingConfig>) for this XMLSearchAttribute");
168 } else {
169
170 for (int i = 0; i < nodes.getLength(); i++) {
171 Node field = nodes.item(i);
172 NamedNodeMap fieldAttributes = field.getAttributes();
173
174 String findXpathExpressionPrefix = "//searchingConfig/" + FIELD_DEF_E + "[@name='" + fieldAttributes.getNamedItem("name").getNodeValue() + "']";
175 String findDataTypeXpathExpression = findXpathExpressionPrefix + "/searchDefinition/@dataType";
176 String findXpathExpression = findXpathExpressionPrefix + "/fieldEvaluation/xpathexpression";
177 String fieldDataType = null;
178 String xpathExpression = null;
179 try {
180 fieldDataType = (String) xpath.evaluate(findDataTypeXpathExpression, getConfigXML(), XPathConstants.STRING);
181 if (org.apache.commons.lang.StringUtils.isEmpty(fieldDataType)) {
182 fieldDataType = KEWConstants.SearchableAttributeConstants.DEFAULT_SEARCHABLE_ATTRIBUTE_TYPE_NAME;
183 }
184 xpathExpression = (String) xpath.evaluate(findXpathExpression, getConfigXML(), XPathConstants.STRING);
185 if (!org.apache.commons.lang.StringUtils.isEmpty(xpathExpression)) {
186
187 try {
188 NodeList searchValues = (NodeList) xpath.evaluate(xpathExpression, document.getDocumentElement(), XPathConstants.NODESET);
189
190
191 if (searchValues.getLength() == 0) {
192 SearchableAttributeValue searchableValue = this.setupSearchableAttributeValue(fieldDataType, fieldAttributes.getNamedItem("name").getNodeValue(), null);
193 if (searchableValue != null) {
194 searchStorageValues.add(searchableValue);
195 }
196 } else {
197 for (int j = 0; j < searchValues.getLength(); j++) {
198 Node searchValue = searchValues.item(j);
199 String value = null;
200 if (searchValue.getFirstChild() != null && (!StringUtils.isEmpty(searchValue.getFirstChild().getNodeValue()))) {
201 value = searchValue.getFirstChild().getNodeValue();
202 }
203 SearchableAttributeValue searchableValue = this.setupSearchableAttributeValue(fieldDataType, fieldAttributes.getNamedItem("name").getNodeValue(), value);
204 if (searchableValue != null) {
205 searchStorageValues.add(searchableValue);
206 }
207 }
208 }
209 } catch (XPathExpressionException e) {
210
211
212
213 String searchValue = (String) xpath.evaluate(xpathExpression, DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
214 new InputSource(new BufferedReader(new StringReader(documentSearchContext.getDocumentContent())))).getDocumentElement(), XPathConstants.STRING);
215 String value = null;
216 if (StringUtils.isNotBlank(searchValue)) {
217 value = searchValue;
218 }
219 SearchableAttributeValue searchableValue = this.setupSearchableAttributeValue(fieldDataType, fieldAttributes.getNamedItem("name").getNodeValue(), value);
220 if (searchableValue != null) {
221 searchStorageValues.add(searchableValue);
222 }
223 }
224 }
225 } catch (XPathExpressionException e) {
226 LOG.error("error in isMatch ", e);
227 throw new RuntimeException("Error trying to find xml content with xpath expressions: " + findXpathExpression + " or " + xpathExpression, e);
228 } catch (Exception e){
229 LOG.error("error parsing docContent: "+documentSearchContext.getDocumentContent(), e);
230 throw new RuntimeException("Error trying to parse docContent: "+documentSearchContext.getDocumentContent(), e);
231 }
232 }
233 }
234 } catch (XPathExpressionException e) {
235 LOG.error("error in getSearchStorageValues ", e);
236 throw new RuntimeException("Error trying to find xml content with xpath expression: " + findField, e);
237 }
238 return searchStorageValues;
239 }
240
241 private SearchableAttributeValue setupSearchableAttributeValue(String dataType,String key,String value) {
242 SearchableAttributeValue attValue = DocSearchUtils.getSearchableAttributeValueByDataTypeString(dataType);
243 if (attValue == null) {
244 String errorMsg = "Cannot find a SearchableAttributeValue associated with the data type '" + dataType + "'";
245 LOG.error("setupSearchableAttributeValue() " + errorMsg);
246 throw new RuntimeException(errorMsg);
247 }
248 value = (value != null) ? value.trim() : null;
249 if ( (StringUtils.isNotBlank(value)) && (!attValue.isPassesDefaultValidation(value)) ) {
250 String errorMsg = "SearchableAttributeValue with the data type '" + dataType + "', key '" + key + "', and value '" + value + "' does not pass default validation and cannot be saved to the database";
251 LOG.error("setupSearchableAttributeValue() " + errorMsg);
252 throw new RuntimeException(errorMsg);
253 }
254 attValue.setSearchableAttributeKey(key);
255 attValue.setupAttributeValue(value);
256 return attValue;
257 }
258
259 public List<Row> getSearchingRows(DocumentSearchContext documentSearchContext) {
260 if (searchRows.isEmpty()) {
261 List<SearchableAttributeValue> searchableAttributeValues = DocSearchUtils.getSearchableAttributeValueObjectTypes();
262 List<Row> rows = new ArrayList<Row>();
263 NodeList fieldNodeList = getConfigXML().getElementsByTagName(FIELD_DEF_E);
264 for (int i = 0; i < fieldNodeList.getLength(); i++) {
265 Node field = fieldNodeList.item(i);
266 NamedNodeMap fieldAttributes = field.getAttributes();
267
268 List<Field> fields = new ArrayList<Field>();
269 boolean isColumnVisible = true;
270 boolean hasXPathExpression = false;
271 Field myField = new Field(fieldAttributes.getNamedItem("name").getNodeValue(), fieldAttributes.getNamedItem("title").getNodeValue());
272
273 String quickfinderService = null;
274
275 Field rangeLowerBoundField = null;
276 Field rangeUpperBoundField = null;
277 myField.setUpperCase(true);
278 for (int j = 0; j < field.getChildNodes().getLength(); j++) {
279 Node childNode = field.getChildNodes().item(j);
280 if ("value".equals(childNode.getNodeName())) {
281 myField.setPropertyValue(childNode.getFirstChild().getNodeValue());
282 } else if ("display".equals(childNode.getNodeName())) {
283 List<KeyValue> options = new ArrayList<KeyValue>();
284 List<String> selectedOptions = new ArrayList<String>();
285 for (int k = 0; k < childNode.getChildNodes().getLength(); k++) {
286 Node displayChildNode = childNode.getChildNodes().item(k);
287 if ("type".equals(displayChildNode.getNodeName())) {
288 String typeValue = displayChildNode.getFirstChild().getNodeValue();
289 myField.setFieldType(convertTypeToFieldType(typeValue));
290 if ("date".equals(typeValue)) {
291 myField.setDatePicker(Boolean.TRUE);
292 myField.setFieldDataType(KEWConstants.SearchableAttributeConstants.DATA_TYPE_DATE);
293 }
294 } else if ("meta".equals(displayChildNode.getNodeName())) {
295
296 } else if ("values".equals(displayChildNode.getNodeName())) {
297 NamedNodeMap valuesAttributes = displayChildNode.getAttributes();
298
299 if (displayChildNode.getFirstChild() != null) {
300 options.add(new ConcreteKeyValue(displayChildNode.getFirstChild().getNodeValue(), valuesAttributes.getNamedItem("title").getNodeValue()));
301 if (valuesAttributes.getNamedItem("selected") != null) {
302 selectedOptions.add(displayChildNode.getFirstChild().getNodeValue());
303 }
304 } else {
305 options.add(new ConcreteKeyValue("", valuesAttributes.getNamedItem("title").getNodeValue()));
306 }
307 }
308 }
309 if (!options.isEmpty()) {
310 myField.setFieldValidValues(options);
311 if (!selectedOptions.isEmpty()) {
312 if (Field.MULTI_VALUE_FIELD_TYPES.contains(myField.getFieldType())) {
313 String[] newSelectedOptions = new String[selectedOptions.size()];
314 int k = 0;
315 for (String option : selectedOptions)
316 {
317 newSelectedOptions[k] = option;
318 k++;
319 }
320 myField.setPropertyValues(newSelectedOptions);
321 } else {
322 myField.setPropertyValue((String)selectedOptions.get(0));
323 }
324 }
325 }
326 } else if ("visibility".equals(childNode.getNodeName())) {
327 parseVisibility(myField, (Element)childNode);
328 } else if ("searchDefinition".equals(childNode.getNodeName())) {
329 NamedNodeMap searchDefAttributes = childNode.getAttributes();
330
331 String dataType = (searchDefAttributes.getNamedItem("dataType") == null) ? null : searchDefAttributes.getNamedItem("dataType").getNodeValue();
332 if (!org.apache.commons.lang.StringUtils.isEmpty(dataType)) {
333 myField.setFieldDataType(dataType);
334 } else {
335
336 myField.setFieldDataType(KEWConstants.SearchableAttributeConstants.DEFAULT_SEARCHABLE_ATTRIBUTE_TYPE_NAME);
337 }
338 if (KEWConstants.SearchableAttributeConstants.DATA_TYPE_DATE.equalsIgnoreCase(myField.getFieldDataType())) {
339 myField.setDatePicker(Boolean.TRUE);
340 }
341
342
343
344
345
346 myField.setMemberOfRange(isRangeSearchField(searchableAttributeValues, myField.getFieldDataType(), searchDefAttributes, childNode));
347 if (!myField.isMemberOfRange()) {
348 Boolean caseSensitive = getBooleanValue(searchDefAttributes, "caseSensitive");
349 if (caseSensitive == null) {
350 caseSensitive = false;
351 }
352 myField.setUpperCase(!caseSensitive);
353 } else {
354
355
356 rangeLowerBoundField = new Field("", KEWConstants.SearchableAttributeConstants.DEFAULT_RANGE_SEARCH_LOWER_BOUND_LABEL);
357 rangeLowerBoundField.setMemberOfRange(true);
358 rangeUpperBoundField = new Field("", KEWConstants.SearchableAttributeConstants.DEFAULT_RANGE_SEARCH_UPPER_BOUND_LABEL);
359 rangeUpperBoundField.setMemberOfRange(true);
360 setupBoundFields(childNode, rangeLowerBoundField, rangeUpperBoundField);
361 }
362
363 String formatterClass = (searchDefAttributes.getNamedItem("formatterClass") == null) ? null : searchDefAttributes.getNamedItem("formatterClass").getNodeValue();
364 if (!StringUtils.isEmpty(formatterClass)) {
365 try {
366 myField.setFormatter((Formatter)Class.forName(formatterClass).newInstance());
367 } catch (InstantiationException e) {
368 LOG.error("Unable to get new instance of formatter class: " + formatterClass);
369 throw new RuntimeException("Unable to get new instance of formatter class: " + formatterClass);
370 }
371 catch (IllegalAccessException e) {
372 LOG.error("Unable to get new instance of formatter class: " + formatterClass);
373 throw new RuntimeException("Unable to get new instance of formatter class: " + formatterClass);
374 } catch (ClassNotFoundException e) {
375 LOG.error("Unable to find formatter class: " + formatterClass);
376 throw new RuntimeException("Unable to find formatter class: " + formatterClass);
377 }
378 }
379
380 } else if ("resultColumn".equals(childNode.getNodeName())) {
381 NamedNodeMap columnAttributes = childNode.getAttributes();
382 Node showNode = columnAttributes.getNamedItem("show");
383 if (showNode != null && showNode.getNodeValue() != null) {
384 isColumnVisible = Boolean.valueOf(showNode.getNodeValue());
385 }
386 myField.setColumnVisible(isColumnVisible);
387 } else if ("fieldEvaluation".equals(childNode.getNodeName())) {
388 for (int k = 0; k < childNode.getChildNodes().getLength(); k++) {
389 Node displayChildNode = childNode.getChildNodes().item(k);
390 if ("xpathexpression".equals(displayChildNode.getNodeName())) {
391 hasXPathExpression = true;
392 break;
393 }
394 }
395 } else if ("lookup".equals(childNode.getNodeName())) {
396 XMLAttributeUtils.establishFieldLookup(myField, childNode);
397 }
398 }
399 myField.setIndexedForSearch(hasXPathExpression);
400
401 if (myField.isMemberOfRange()) {
402
403 addRangeFields(KEWConstants.SearchableAttributeConstants.RANGE_LOWER_BOUND_PROPERTY_PREFIX, rangeLowerBoundField, myField, rows, quickfinderService);
404 addRangeFields(KEWConstants.SearchableAttributeConstants.RANGE_UPPER_BOUND_PROPERTY_PREFIX, rangeUpperBoundField, myField, rows, quickfinderService);
405 } else {
406 fields.add(myField);
407 if (!myField.getFieldType().equals(Field.HIDDEN)) {
408 if (myField.isDatePicker()) {
409 addDatePickerField(fields, myField.getPropertyName());
410 }
411 }
412 rows.add(new Row(fields));
413 }
414 }
415 searchRows = rows;
416 }
417 return searchRows;
418 }
419
420 private boolean isRangeSearchField(List<SearchableAttributeValue> searchableAttributeValues, String dataType, NamedNodeMap searchDefAttributes, Node searchDefNode) {
421 for (SearchableAttributeValue attValue : searchableAttributeValues)
422 {
423 if (dataType.equalsIgnoreCase(attValue.getAttributeDataType()))
424 {
425 return isRangeSearchField(attValue, dataType, searchDefAttributes, searchDefNode);
426 }
427 }
428 String errorMsg = "Could not find searchable attribute value for data type '" + dataType + "'";
429 LOG.error("isRangeSearchField(List, String, NamedNodeMap, Node) " + errorMsg);
430 throw new RuntimeException(errorMsg);
431 }
432
433 private boolean isRangeSearchField(SearchableAttributeValue searchableAttributeValue, String dataType, NamedNodeMap searchDefAttributes, Node searchDefNode) {
434 boolean allowRangedSearch = searchableAttributeValue.allowsRangeSearches();
435 Boolean rangeSearchBoolean = getBooleanValue(searchDefAttributes, "rangeSearch");
436 boolean rangeSearch = (rangeSearchBoolean != null) && rangeSearchBoolean;
437 Node rangeDefinition = getPotentialChildNode(searchDefNode, "rangeDefinition");
438 return ( (allowRangedSearch) && ((rangeDefinition != null) || (rangeSearch)) );
439 }
440
441 private void setupBoundFields(Node searchDefinitionNode, Field lowerBoundField, Field upperBoundField) {
442 NamedNodeMap searchDefAttributes = searchDefinitionNode.getAttributes();
443 Node rangeDefinitionNode = getPotentialChildNode(searchDefinitionNode, "rangeDefinition");
444 NamedNodeMap rangeDefinitionAttributes = null;
445 NamedNodeMap lowerBoundNodeAttributes = null;
446 NamedNodeMap upperBoundNodeAttributes = null;
447 if (rangeDefinitionNode != null) {
448 rangeDefinitionAttributes = rangeDefinitionNode.getAttributes();
449 lowerBoundNodeAttributes = getAttributesForPotentialChildNode(rangeDefinitionNode, "lower");
450 upperBoundNodeAttributes = getAttributesForPotentialChildNode(rangeDefinitionNode, "upper");
451 }
452
453 setupRangeBoundFieldOverridableSettings(searchDefAttributes, rangeDefinitionAttributes, lowerBoundNodeAttributes, lowerBoundField);
454 setupRangeBoundFieldOverridableSettings(searchDefAttributes, rangeDefinitionAttributes, upperBoundNodeAttributes, upperBoundField);
455 }
456
457 private void addRangeFields(String propertyPrefix, Field rangeBoundField,Field mainField, List<Row> rows,String quickfinderService) {
458 List<Field> rangeFields = new ArrayList<Field>();
459 rangeBoundField.setColumnVisible(mainField.isColumnVisible());
460 rangeBoundField.setFieldDataType(mainField.getFieldDataType());
461 rangeBoundField.setFieldHelpUrl(mainField.getFieldHelpUrl());
462 rangeBoundField.setFieldType(mainField.getFieldType());
463 rangeBoundField.setMainFieldLabel(mainField.getFieldLabel());
464 rangeBoundField.setFieldValidValues(mainField.getFieldValidValues());
465 rangeBoundField.setPropertyName(propertyPrefix + mainField.getPropertyName());
466 rangeBoundField.setQuickFinderClassNameImpl(mainField.getQuickFinderClassNameImpl());
467
468 rangeFields.add(rangeBoundField);
469 if (!mainField.getFieldType().equals(Field.HIDDEN)) {
470
471
472
473
474 if (rangeBoundField.isDatePicker()) {
475
476 if (rangeBoundField.isDatePicker()) {
477 addDatePickerField(rangeFields, rangeBoundField.getPropertyName());
478 }
479 } else {
480 if (mainField.isDatePicker()) {
481 addDatePickerField(rangeFields, rangeBoundField.getPropertyName());
482 }
483 }
484 }
485 rows.add(new Row(rangeFields));
486 }
487
488 private void addDatePickerField(List<Field> fields,String propertyName) {
489 Field Field = new Field(propertyName,"");
490 Field.setDatePicker(true);
491 fields.add(Field);
492 }
493
494 private NamedNodeMap getAttributesForPotentialChildNode(Node node, String potentialChildNodeName) {
495 Node testNode = getPotentialChildNode(node, potentialChildNodeName);
496 return (testNode != null) ? testNode.getAttributes() : null;
497 }
498
499 private Node getPotentialChildNode(Node node, String childNodeName) {
500 if (node != null) {
501 for (int k = 0; k < node.getChildNodes().getLength(); k++) {
502 Node testNode = node.getChildNodes().item(k);
503 if (testNode.getNodeName().equals(childNodeName)) {
504 return testNode;
505 }
506 }
507 }
508 return null;
509 }
510
511 private void setupRangeBoundFieldOverridableSettings(NamedNodeMap searchDefinitionAttributes,NamedNodeMap rangeDefinitionAttributes,NamedNodeMap rangeBoundAttributes,Field boundField) {
512 String potentialLabel = getPotentialRangeBoundLabelFromAttributes(rangeBoundAttributes);
513 if (StringUtils.isNotBlank(potentialLabel)) {
514 boundField.setFieldLabel(potentialLabel);
515 }
516 ArrayList<NamedNodeMap> namedNodeMapsByImportance = new ArrayList<NamedNodeMap>();
517 namedNodeMapsByImportance.add(rangeBoundAttributes);
518 namedNodeMapsByImportance.add(rangeDefinitionAttributes);
519 namedNodeMapsByImportance.add(searchDefinitionAttributes);
520 Boolean caseSensitive = getBooleanWithPotentialOverrides(namedNodeMapsByImportance, "caseSensitive");
521 if (caseSensitive == null) {
522 caseSensitive = false;
523 }
524 boundField.setUpperCase(!caseSensitive);
525
526
527 Boolean datePickerBoolean = getBooleanWithPotentialOverrides(namedNodeMapsByImportance, "datePicker");
528 if (datePickerBoolean == null) {
529 datePickerBoolean = false;
530 }
531 boundField.setDatePicker(datePickerBoolean);
532 boundField.setRangeFieldInclusive(getBooleanWithPotentialOverrides(namedNodeMapsByImportance, "inclusive"));
533
534 }
535
536 private String getPotentialRangeBoundLabelFromAttributes(NamedNodeMap rangeBoundAttributes) {
537 if (rangeBoundAttributes != null) {
538 String boundLabel = (rangeBoundAttributes.getNamedItem("label") == null) ? null : rangeBoundAttributes.getNamedItem("label").getNodeValue();
539 if (!org.apache.commons.lang.StringUtils.isEmpty(boundLabel)) {
540 return boundLabel;
541 }
542 }
543 return null;
544 }
545
546 private Boolean getBooleanWithPotentialOverrides(String attributeName,NamedNodeMap searchDefinitionAttributes,NamedNodeMap rangeDefinitionAttributes,NamedNodeMap rangeBoundAttributes) {
547 ArrayList<NamedNodeMap> namedNodeMapsByImportance = new ArrayList<NamedNodeMap>();
548 namedNodeMapsByImportance.add(rangeBoundAttributes);
549 namedNodeMapsByImportance.add(rangeDefinitionAttributes);
550 namedNodeMapsByImportance.add(searchDefinitionAttributes);
551 return getBooleanWithPotentialOverrides(namedNodeMapsByImportance, attributeName);
552 }
553
554 private Boolean getBooleanWithPotentialOverrides(ArrayList<NamedNodeMap> namedNodeMapsByImportance, String attributeName) {
555 for (NamedNodeMap aNamedNodeMapsByImportance : namedNodeMapsByImportance)
556 {
557 NamedNodeMap nodeMap = (NamedNodeMap) aNamedNodeMapsByImportance;
558 Boolean booleanValue = getBooleanValue(nodeMap, attributeName);
559 if (booleanValue != null)
560 {
561 return booleanValue;
562 }
563 }
564 return null;
565 }
566
567 private Boolean getBooleanValue(NamedNodeMap nodeMap, String attributeName) {
568 String nodeValue = getStringValue(nodeMap, attributeName);
569 if (nodeValue != null) {
570 return Boolean.valueOf(nodeValue);
571 }
572 return null;
573 }
574
575 private String getStringValue(NamedNodeMap nodeMap, String attributeName) {
576 return ( (nodeMap == null) || (nodeMap.getNamedItem(attributeName) == null) || (org.apache.commons.lang.StringUtils.isEmpty(nodeMap.getNamedItem(attributeName).getNodeValue())) ) ? null : nodeMap.getNamedItem(attributeName).getNodeValue();
577 }
578
579 private void parseVisibility(Field field, Element visibilityElement) {
580 for (int vIndex = 0; vIndex < visibilityElement.getChildNodes().getLength(); vIndex++) {
581 Node visibilityChildNode = visibilityElement.getChildNodes().item(vIndex);
582 if (visibilityChildNode.getNodeType() == Node.ELEMENT_NODE) {
583 boolean visible = true;
584 NamedNodeMap visibilityAttributes = visibilityChildNode.getAttributes();
585 Node visibleNode = visibilityAttributes.getNamedItem("visible");
586 if (visibleNode != null && visibleNode.getNodeValue() != null) {
587 visible = Boolean.valueOf(visibleNode.getNodeValue());
588 } else {
589 NodeList visibilityDecls = visibilityChildNode.getChildNodes();
590 for (int vdIndex = 0; vdIndex < visibilityDecls.getLength(); vdIndex++) {
591 Node visibilityDecl = visibilityDecls.item(vdIndex);
592 if (visibilityDecl.getNodeType() == Node.ELEMENT_NODE) {
593 boolean hasIsMemberOfGroupElement = false;
594 String groupName = null;
595 String groupNamespace = null;
596 if (XmlConstants.IS_MEMBER_OF_GROUP.equals(visibilityDecl.getNodeName())) {
597 hasIsMemberOfGroupElement = true;
598 groupName = Utilities.substituteConfigParameters(visibilityDecl.getTextContent()).trim();
599 groupNamespace = Utilities.substituteConfigParameters(((Element)visibilityDecl).getAttribute(XmlConstants.NAMESPACE)).trim();
600 }
601 else if (XmlConstants.IS_MEMBER_OF_WORKGROUP.equals(visibilityDecl.getNodeName())) {
602 LOG.warn((new StringBuilder()).append("Rule Attribute XML is using deprecated element '").append(
603 XmlConstants.IS_MEMBER_OF_WORKGROUP).append("', please use '").append(XmlConstants.IS_MEMBER_OF_GROUP).append(
604 "' instead.").toString());
605 hasIsMemberOfGroupElement = true;
606 String workgroupName = Utilities.substituteConfigParameters(visibilityDecl.getFirstChild().getNodeValue());
607 groupNamespace = Utilities.parseGroupNamespaceCode(workgroupName);
608 groupName = Utilities.parseGroupName(workgroupName);
609 }
610 if (hasIsMemberOfGroupElement) {
611 UserSession session = GlobalVariables.getUserSession();
612 if (session == null) {
613 throw new WorkflowRuntimeException("UserSession is null! Attempted to render the searchable attribute outside of an established session.");
614 }
615 GroupService groupService = KimApiServiceLocator.getGroupService();
616
617 Group group = groupService.getGroupByName(groupNamespace, groupName);
618 visible = group == null ? false : groupService.isMemberOfGroup(session.getPerson().getPrincipalId(), group.getId());
619 }
620 }
621 }
622 }
623 String type = visibilityChildNode.getNodeName();
624 if ("field".equals(type) || "fieldAndColumn".equals(type)) {
625
626 if (!visible) {
627 field.setFieldType(Field.HIDDEN);
628 }
629 }
630 if ("column".equals(type) || "fieldAndColumn".equals(type)) {
631 field.setColumnVisible(visible);
632 }
633 }
634
635 }
636 }
637
638 private String convertTypeToFieldType(String type) {
639 if ("text".equals(type)) {
640 return Field.TEXT;
641 } else if ("select".equals(type)) {
642 return Field.DROPDOWN;
643 } else if ("radio".equals(type)) {
644 return Field.RADIO;
645 } else if ("quickfinder".equals(type)) {
646 return Field.QUICKFINDER;
647 } else if ("hidden".equals(type)) {
648 return Field.HIDDEN;
649 } else if ("date".equals(type)) {
650 return Field.TEXT;
651 } else if ("multibox".equals(type)) {
652 return Field.MULTIBOX;
653 }
654 throw new IllegalArgumentException("Illegal field type found: " + type);
655 }
656
657 public List validateUserSearchInputs(Map paramMap, DocumentSearchContext documentSearchContext) {
658 this.paramMap = paramMap;
659 List<WorkflowAttributeValidationError> errors = new ArrayList<WorkflowAttributeValidationError>();
660
661 XPath xpath = XPathHelper.newXPath();
662 String findField = "//searchingConfig/" + FIELD_DEF_E;
663 try {
664 NodeList nodes = (NodeList) xpath.evaluate(findField, getConfigXML(), XPathConstants.NODESET);
665 if (nodes == null) {
666
667 LOG.warn("Could not find any field definitions (<" + FIELD_DEF_E + ">) or possibly a searching configuration (<searchingConfig>) for this XMLSearchAttribute");
668 } else {
669 for (int i = 0; i < nodes.getLength(); i++) {
670 Node field = nodes.item(i);
671 NamedNodeMap fieldAttributes = field.getAttributes();
672 String fieldDefName = fieldAttributes.getNamedItem("name").getNodeValue();
673 String fieldDefTitle = ((fieldAttributes.getNamedItem("title")) != null) ? fieldAttributes.getNamedItem("title").getNodeValue() : "";
674
675 boolean rangeMemberInSearchParams = false;
676 if (getParamMap() != null) {
677 Object lowerObj = getParamMap().get(KEWConstants.SearchableAttributeConstants.RANGE_LOWER_BOUND_PROPERTY_PREFIX + fieldDefName);
678 if ( (lowerObj != null) && (lowerObj instanceof String) ) {
679 rangeMemberInSearchParams |= StringUtils.isNotBlank((String) lowerObj);
680 }
681 Object upperObj = getParamMap().get(KEWConstants.SearchableAttributeConstants.RANGE_UPPER_BOUND_PROPERTY_PREFIX + fieldDefName);
682 if ( (upperObj != null) && (upperObj instanceof String) ) {
683 rangeMemberInSearchParams |= StringUtils.isNotBlank((String) upperObj);
684 }
685 Object testObject = getParamMap().get(fieldDefName);
686 if ( (testObject != null) || rangeMemberInSearchParams ) {
687
688 if (!rangeMemberInSearchParams) {
689 if (testObject instanceof String) {
690 String stringVariable = (String) testObject;
691 if (StringUtils.isBlank(stringVariable)) {
692
693 continue;
694 }
695 } else if (testObject instanceof Collection) {
696 Collection stringVariables = (Collection<String>)testObject;
697 boolean allAreBlank = true;
698 for (Iterator iter = stringVariables.iterator(); iter.hasNext();) {
699 String testString = (String) iter.next();
700 if (StringUtils.isNotBlank(testString)) {
701 allAreBlank = false;
702 break;
703 }
704 }
705 if (allAreBlank) {
706
707 continue;
708 }
709 } else {
710 String errorMessage = "Only String or String[] objects should come from entered parameters of an attribute. Current parameter is '" + testObject.getClass() + "'";
711 LOG.error(errorMessage);
712 throw new RuntimeException(errorMessage);
713 }
714 }
715 String findXpathExpressionPrefix = "//searchingConfig/" + FIELD_DEF_E + "[@name='" + fieldDefName + "']";
716 Node searchDefNode = (Node) xpath.evaluate(findXpathExpressionPrefix + "/searchDefinition", getConfigXML(), XPathConstants.NODE);
717 NamedNodeMap searchDefAttributes = null;
718 String fieldDataType = null;
719 if (searchDefNode != null) {
720
721 searchDefAttributes = searchDefNode.getAttributes();
722 if (searchDefAttributes.getNamedItem("dataType") != null) {
723 fieldDataType = searchDefAttributes.getNamedItem("dataType").getNodeValue();
724 }
725
726 }
727 if (org.apache.commons.lang.StringUtils.isEmpty(fieldDataType)) {
728 fieldDataType = KEWConstants.SearchableAttributeConstants.DEFAULT_SEARCHABLE_ATTRIBUTE_TYPE_NAME;
729 }
730
731 SearchableAttributeValue attributeValue = DocSearchUtils.getSearchableAttributeValueByDataTypeString(fieldDataType);
732 if (attributeValue == null) {
733 String errorMsg = "Cannot find SearchableAttributeValue for field data type '" + fieldDataType + "'";
734 LOG.error("validateUserSearchInputs() " + errorMsg);
735 throw new RuntimeException(errorMsg);
736 }
737
738 if (rangeMemberInSearchParams) {
739 String lowerBoundFieldDefName = KEWConstants.SearchableAttributeConstants.RANGE_LOWER_BOUND_PROPERTY_PREFIX + fieldDefName;
740 String upperBoundFieldDefName = KEWConstants.SearchableAttributeConstants.RANGE_UPPER_BOUND_PROPERTY_PREFIX + fieldDefName;
741 String lowerBoundEnteredValue = null;
742 String upperBoundEnteredValue = null;
743 NamedNodeMap lowerBoundRangeAttributes = null;
744 NamedNodeMap upperBoundRangeAttributes = null;
745 Node rangeDefinitionNode = getPotentialChildNode(searchDefNode, "rangeDefinition");
746 NamedNodeMap rangeDefinitionAttributes = (rangeDefinitionNode != null) ? rangeDefinitionNode.getAttributes() : null;
747 lowerBoundEnteredValue = (String) getParamMap().get(lowerBoundFieldDefName);
748 upperBoundEnteredValue = (String) getParamMap().get(upperBoundFieldDefName);
749 if (!org.apache.commons.lang.StringUtils.isEmpty(lowerBoundEnteredValue)) {
750 lowerBoundRangeAttributes = getAttributesForPotentialChildNode(rangeDefinitionNode, "lower");
751 errors.addAll(performValidation(attributeValue,
752 lowerBoundFieldDefName, lowerBoundEnteredValue, constructRangeFieldErrorPrefix(fieldDefTitle,lowerBoundRangeAttributes), findXpathExpressionPrefix));
753 }
754 if (!org.apache.commons.lang.StringUtils.isEmpty(upperBoundEnteredValue)) {
755 upperBoundRangeAttributes = getAttributesForPotentialChildNode(rangeDefinitionNode, "upper");
756 errors.addAll(performValidation(attributeValue,
757 upperBoundFieldDefName, upperBoundEnteredValue, constructRangeFieldErrorPrefix(fieldDefTitle,upperBoundRangeAttributes), findXpathExpressionPrefix));
758 }
759 if (errors.isEmpty()) {
760 Boolean rangeValid = attributeValue.isRangeValid(lowerBoundEnteredValue, upperBoundEnteredValue);
761 if ( (rangeValid != null) && (!rangeValid) ) {
762 String lowerLabel = getPotentialRangeBoundLabelFromAttributes(lowerBoundRangeAttributes);
763 String upperLabel = getPotentialRangeBoundLabelFromAttributes(upperBoundRangeAttributes);
764 String errorMsg = "The " + fieldDefTitle + " range is incorrect. The " + (StringUtils.isNotBlank(lowerLabel) ? lowerLabel : KEWConstants.SearchableAttributeConstants.DEFAULT_RANGE_SEARCH_LOWER_BOUND_LABEL) + " value entered must come before the " + (StringUtils.isNotBlank(upperLabel) ? upperLabel : KEWConstants.SearchableAttributeConstants.DEFAULT_RANGE_SEARCH_UPPER_BOUND_LABEL) + " value";
765 LOG.debug("validateUserSearchInputs() " + errorMsg + " :: field type '" + attributeValue.getAttributeDataType() + "'");
766 errors.add(new WorkflowAttributeValidationError(fieldDefName, errorMsg));
767 }
768 }
769
770 } else {
771 Object enteredValue = getParamMap().get(fieldDefName);
772 if (enteredValue instanceof String) {
773 String stringVariable = (String) enteredValue;
774 errors.addAll(performValidation(attributeValue, fieldDefName, stringVariable, fieldDefTitle, findXpathExpressionPrefix));
775 } else if (enteredValue instanceof Collection) {
776 Collection stringVariables = (Collection<String>)enteredValue;
777 for (Iterator iter = stringVariables.iterator(); iter.hasNext();) {
778 String stringVariable = (String) iter.next();
779 errors.addAll(performValidation(attributeValue, fieldDefName, stringVariable, "One value for " + fieldDefTitle, findXpathExpressionPrefix));
780 }
781
782 } else {
783 String errorMessage = "Only String or String[] objects should come from entered parameters of an attribute.";
784 LOG.error(errorMessage);
785 throw new RuntimeException(errorMessage);
786 }
787 }
788 } else {
789
790
791
792
793
794
795
796
797
798 }
799 }
800 }
801 }
802 } catch (XPathExpressionException e) {
803 LOG.error("error in validateUserSearchInputs ", e);
804 throw new RuntimeException("Error trying to find xml content with xpath expression: " + findField, e);
805 }
806 return errors;
807 }
808
809 private String constructRangeFieldErrorPrefix(String fieldDefLabel, NamedNodeMap rangeBoundAttributes) {
810 String potentialLabel = getPotentialRangeBoundLabelFromAttributes(rangeBoundAttributes);
811 if ( (StringUtils.isNotBlank(potentialLabel)) && (StringUtils.isNotBlank(fieldDefLabel)) ) {
812 return fieldDefLabel + " " + potentialLabel + " Field";
813 } else if (StringUtils.isNotBlank(fieldDefLabel)) {
814 return fieldDefLabel + " Range Field";
815 } else if (StringUtils.isNotBlank(potentialLabel)) {
816 return "Range Field " + potentialLabel + " Field";
817 }
818 return null;
819 }
820
821 private List<WorkflowAttributeValidationError> performValidation(SearchableAttributeValue attributeValue, String fieldDefName, String enteredValue, String errorMessagePrefix, String findXpathExpressionPrefix) throws XPathExpressionException {
822 List<WorkflowAttributeValidationError> errors = new ArrayList<WorkflowAttributeValidationError>();
823 XPath xpath = XPathHelper.newXPath();
824 if ( attributeValue.allowsWildcards()) {
825 enteredValue = enteredValue.replaceAll(KEWConstants.SearchableAttributeConstants.SEARCH_WILDCARD_CHARACTER_REGEX_ESCAPED, "");
826 }
827 if (!attributeValue.isPassesDefaultValidation(enteredValue)) {
828 errorMessagePrefix = (StringUtils.isNotBlank(errorMessagePrefix)) ? errorMessagePrefix : "Field";
829 String errorMsg = errorMessagePrefix + " with value '" + enteredValue + "' does not conform to standard validation for field type.";
830 LOG.debug("validateUserSearchInputs() " + errorMsg + " :: field type '" + attributeValue.getAttributeDataType() + "'");
831 errors.add(new WorkflowAttributeValidationError(fieldDefName, errorMsg));
832 } else {
833 String findValidation = findXpathExpressionPrefix + "/validation/regex";
834 String regex = (String) xpath.evaluate(findValidation, getConfigXML(), XPathConstants.STRING);
835 if (!org.apache.commons.lang.StringUtils.isEmpty(regex)) {
836 Pattern pattern = Pattern.compile(regex);
837 Matcher matcher = pattern.matcher(enteredValue);
838 if (!matcher.matches()) {
839 String findErrorMessage = findXpathExpressionPrefix + "/validation/message";
840 String message = (String) xpath.evaluate(findErrorMessage, getConfigXML(), XPathConstants.STRING);
841 errors.add(new WorkflowAttributeValidationError(fieldDefName, message));
842 }
843 }
844 }
845 return errors;
846 }
847
848 public Element getConfigXML() {
849 try {
850 return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new BufferedReader(new StringReader(ruleAttribute.getXmlConfigData())))).getDocumentElement();
851 } catch (Exception e) {
852 String ruleAttrStr = (ruleAttribute == null ? null : ruleAttribute.getName());
853 LOG.error("error parsing xml data from search attribute: " + ruleAttrStr, e);
854 throw new RuntimeException("error parsing xml data from searchable attribute: " + ruleAttrStr, e);
855 }
856 }
857 }