View Javadoc

1   /*
2    * Copyright 2005-2007 The Kuali Foundation
3    *
4    *
5    * Licensed under the Educational Community License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.opensource.org/licenses/ecl2.php
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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   * implementation of {@link GenericXMLSearchableAttribute}.
67   *
68   * @author Kuali Rice Team (rice.collab@kuali.org)
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  				// Custom doc content in the searchingConfig xml.
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 				// Standard doc content if no doc content is found in the searchingConfig xml.
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                               //being that this is the standard xml attribute we will return the key with an empty value
190                                 // so we can find it from a doc search using this key
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                                 //try for a string being returned from the expression.  This
211                                 //seems like a poor way to determine our expression return type but
212                                 //it's all I can come up with at the moment.
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 				// range search details
275 				Field rangeLowerBoundField = null;
276 				Field rangeUpperBoundField = null;
277 				myField.setUpperCase(true); // this defaults us to case insensitive.
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 //                              this is to allow an empty drop down choice and can probably implemented in a better way
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 						// data type operations
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 							// no data type means we default to String which disallows range search
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 						//if () {
342 						//    myField.setFormatter((Formatter) formatterClass.newInstance());
343 						//}
344 
345 						// figure out if this is a range search
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; // we mimmic the KNS. KNS is case insensitive by default
351 							}
352 							myField.setUpperCase(!caseSensitive);
353 						} else {
354     						// by now we know we have a range that uses the default values at least
355     						// these will be
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 					// we have a ranged search... we need to add the bound fields and NOT the myField object
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         // below methods allow for nullable attribute NamedNodeMaps
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 		//rangeBoundField.setDefaultLookupableName(mainField.getDefaultLookupableName());
468 		rangeFields.add(rangeBoundField);
469 		if (!mainField.getFieldType().equals(Field.HIDDEN)) {
470 			// disabling the additional quickfinder field for now, should be included as a single field in the KNS instead of 2 as it was in KEW
471 //			if (!org.apache.commons.lang.StringUtils.isEmpty(quickfinderService)) {
472 //				rangeFields.add(new Field("", "", Field.QUICKFINDER, "", "", null, quickfinderService));
473 //			}
474 			if (rangeBoundField.isDatePicker()) {
475 				// variable was set on the bound field
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; // we mimmic the KNS. KNS is case insensitive by default
523 		}
524 		boundField.setUpperCase(!caseSensitive);
525 		// TODO: after face-to-face work in december 2008, this was throwing a nullpointerexception for lookups with date pickers
526 		// assuming this code will go away after the document search conversion
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())) { // Found an "isMemberOfGroup" element.
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())) { // Found a deprecated "isMemberOfWorkgroup" element.
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) { // Found one of the "isMemberOf..." elements.
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 					// if it's not visible, coerce this field to a hidden type
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 				// no field definitions is de facto valid
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                     // check for range search members in the parameter map
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                             // check to see if we need to process this field at all
688                             if (!rangeMemberInSearchParams) {
689                                 if (testObject instanceof String) {
690                                     String stringVariable = (String) testObject;
691                                     if (StringUtils.isBlank(stringVariable)) {
692                                         // field is not multi value and is empty... skip it
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                                         // field is multivalue but all values are blank... skip it
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             					// get the data type from the xml
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         					// get the searchable attribute value by using the data type
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 //        				    String findValidation = "//searchingConfig/field[@name='" + fieldAttributes.getNamedItem("name").getNodeValue() + "']/validation";
790 //        				    Node validation = (Node) xpath.evaluate(findValidation, getConfigXML(), XPathConstants.NODE);
791 //        				    if (validation != null) {
792 //        				        NamedNodeMap validationAttributes = validation.getAttributes();
793 //        				        Node required = validationAttributes.getNamedItem("required");
794 //        				        if (required != null && "true".equalsIgnoreCase(required.getNodeValue())) {
795 //        				            errors.add(new WorkflowAttributeValidationError(fieldAttributes.getNamedItem("name").getNodeValue(),fieldAttributes.getNamedItem("title").getNodeValue()+" is required."));
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 }