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