1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.kew.docsearch.xml;
17
18 import java.io.BufferedReader;
19 import java.io.StringReader;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.LinkedHashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28
29 import javax.xml.parsers.DocumentBuilderFactory;
30 import javax.xml.parsers.ParserConfigurationException;
31 import javax.xml.xpath.XPath;
32 import javax.xml.xpath.XPathConstants;
33 import javax.xml.xpath.XPathExpressionException;
34
35 import org.apache.commons.lang.StringUtils;
36 import org.kuali.rice.core.api.data.DataType;
37 import org.kuali.rice.core.api.search.Range;
38 import org.kuali.rice.core.api.search.SearchExpressionUtils;
39 import org.kuali.rice.core.api.uif.RemotableAbstractControl;
40 import org.kuali.rice.core.api.uif.RemotableAttributeError;
41 import org.kuali.rice.core.api.uif.RemotableAttributeField;
42 import org.kuali.rice.core.api.uif.RemotableAttributeLookupSettings;
43 import org.kuali.rice.core.api.uif.RemotableDatepicker;
44 import org.kuali.rice.core.api.uif.RemotableHiddenInput;
45 import org.kuali.rice.core.api.uif.RemotableQuickFinder;
46 import org.kuali.rice.core.api.uif.RemotableRadioButtonGroup;
47 import org.kuali.rice.core.api.uif.RemotableSelect;
48 import org.kuali.rice.core.api.uif.RemotableTextInput;
49 import org.kuali.rice.core.api.util.KeyValue;
50 import org.kuali.rice.core.web.format.Formatter;
51 import org.kuali.rice.kew.api.KewApiConstants;
52 import org.kuali.rice.kew.api.WorkflowRuntimeException;
53 import org.kuali.rice.kew.api.document.DocumentWithContent;
54 import org.kuali.rice.kew.api.document.attribute.DocumentAttribute;
55 import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
56 import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
57 import org.kuali.rice.kew.api.extension.ExtensionDefinition;
58 import org.kuali.rice.kew.docsearch.CaseAwareSearchableAttributeValue;
59 import org.kuali.rice.kew.docsearch.DocumentSearchInternalUtils;
60 import org.kuali.rice.kew.docsearch.SearchableAttributeValue;
61 import org.kuali.rice.kew.framework.document.attribute.SearchableAttribute;
62 import org.kuali.rice.kew.rule.xmlrouting.XPathHelper;
63 import org.kuali.rice.kim.api.group.Group;
64 import org.kuali.rice.kim.api.group.GroupService;
65 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
66 import org.kuali.rice.kns.lookup.LookupUtils;
67 import org.kuali.rice.krad.UserSession;
68 import org.kuali.rice.krad.util.GlobalVariables;
69 import org.w3c.dom.Document;
70 import org.w3c.dom.Element;
71 import org.w3c.dom.Node;
72 import org.w3c.dom.NodeList;
73 import org.xml.sax.InputSource;
74
75 import com.google.common.base.Function;
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135 public class StandardGenericXMLSearchableAttribute implements SearchableAttribute {
136
137 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(StandardGenericXMLSearchableAttribute.class);
138 private static final String FIELD_DEF_E = "fieldDef";
139
140
141
142 private static final boolean PEDANTIC_BOUNDS_VALIDATION = true;
143
144
145 @Override
146 public String generateSearchContent(ExtensionDefinition extensionDefinition, String documentTypeName, WorkflowAttributeDefinition attributeDefinition) {
147 Map<String, String> propertyDefinitionMap = attributeDefinition.getPropertyDefinitionsAsMap();
148 try {
149 XMLSearchableAttributeContent content = new XMLSearchableAttributeContent(getConfigXML(extensionDefinition));
150 return content.generateSearchContent(propertyDefinitionMap);
151 } catch (XPathExpressionException e) {
152 LOG.error("error in getSearchContent ", e);
153 throw new RuntimeException("Error trying to find xml content with xpath expression", e);
154 } catch (Exception e) {
155 LOG.error("error in getSearchContent attempting to find xml search content", e);
156 throw new RuntimeException("Error trying to get xml search content.", e);
157 }
158 }
159
160 @Override
161 public List<DocumentAttribute> extractDocumentAttributes(ExtensionDefinition extensionDefinition, DocumentWithContent documentWithContent) {
162 List<DocumentAttribute> searchStorageValues = new ArrayList<DocumentAttribute>();
163 String fullDocumentContent = documentWithContent.getDocumentContent().getFullContent();
164 if (StringUtils.isBlank(documentWithContent.getDocumentContent().getFullContent())) {
165 LOG.warn("Empty Document Content found for document id: " + documentWithContent.getDocument().getDocumentId());
166 return searchStorageValues;
167 }
168 Document document;
169 try {
170 document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new BufferedReader(new StringReader(fullDocumentContent))));
171 } catch (Exception e){
172 LOG.error("error parsing docContent: "+documentWithContent.getDocumentContent(), e);
173 throw new RuntimeException("Error trying to parse docContent: "+documentWithContent.getDocumentContent(), e);
174 }
175 XMLSearchableAttributeContent content = new XMLSearchableAttributeContent(getConfigXML(extensionDefinition));
176 List<XMLSearchableAttributeContent.FieldDef> fields;
177 try {
178 fields = content.getFieldDefList();
179 } catch (XPathExpressionException xpee) {
180 throw new RuntimeException("Error parsing searchable attribute content", xpee);
181 } catch (ParserConfigurationException pce) {
182 throw new RuntimeException("Error parsing searchable attribute content", pce);
183 }
184 XPath xpath = XPathHelper.newXPath(document);
185 for (XMLSearchableAttributeContent.FieldDef field: fields) {
186 if (StringUtils.isNotEmpty(field.fieldEvaluationExpr)) {
187 List<String> values = new ArrayList<String>();
188 try {
189 LOG.debug("Trying to retrieve node set with expression: '" + field.fieldEvaluationExpr + "'.");
190 NodeList searchValues = (NodeList) xpath.evaluate(field.fieldEvaluationExpr, document.getDocumentElement(), XPathConstants.NODESET);
191
192
193 for (int j = 0; j < searchValues.getLength(); j++) {
194 Node searchValue = searchValues.item(j);
195 if (searchValue.getFirstChild() != null && (StringUtils.isNotEmpty(searchValue.getFirstChild().getNodeValue()))) {
196 values.add(searchValue.getFirstChild().getNodeValue());
197 }
198 }
199 } catch (XPathExpressionException e) {
200 LOG.debug("Could not retrieve node set with expression: '" + field.fieldEvaluationExpr + "'. Trying string return type.");
201
202
203
204 try {
205 String searchValue = (String) xpath.evaluate(field.fieldEvaluationExpr, document.getDocumentElement(), XPathConstants.STRING);
206 if (StringUtils.isNotBlank(searchValue)) {
207 values.add(searchValue);
208 }
209 } catch (XPathExpressionException xpee) {
210 LOG.error("Error retrieving string with expression: '" + field.fieldEvaluationExpr + "'", xpee);
211 throw new RuntimeException("Error retrieving string with expression: '" + field.fieldEvaluationExpr + "'", xpee);
212 }
213 }
214
215
216 values.removeAll(Collections.singleton(null));
217
218
219 if (values.isEmpty()) {
220 values.add(null);
221 }
222 for (String value: values) {
223 DocumentAttribute searchableValue = this.setupSearchableAttributeValue(field.searchDefinition.dataType, field.name, value);
224 if (searchableValue != null) {
225 searchStorageValues.add(searchableValue);
226 }
227 }
228 }
229 }
230 return searchStorageValues;
231 }
232
233 private DocumentAttribute setupSearchableAttributeValue(String dataType, String key, String value) {
234 SearchableAttributeValue attValue = DocumentSearchInternalUtils.getSearchableAttributeValueByDataTypeString(dataType);
235 if (attValue == null) {
236 String errorMsg = "Cannot find a SearchableAttributeValue associated with the data type '" + dataType + "'";
237 LOG.error("setupSearchableAttributeValue() " + errorMsg);
238 throw new RuntimeException(errorMsg);
239 }
240 value = (value != null) ? value.trim() : null;
241 if ( (StringUtils.isNotBlank(value)) && (!attValue.isPassesDefaultValidation(value)) ) {
242 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";
243 LOG.error("setupSearchableAttributeValue() " + errorMsg);
244 throw new RuntimeException(errorMsg);
245 }
246 attValue.setSearchableAttributeKey(key);
247 attValue.setupAttributeValue(value);
248 return attValue.toDocumentAttribute();
249 }
250
251 @Override
252 public List<RemotableAttributeField> getSearchFields(ExtensionDefinition extensionDefinition, String documentTypeName) {
253 List<RemotableAttributeField> searchFields = new ArrayList<RemotableAttributeField>();
254 List<SearchableAttributeValue> searchableAttributeValues = DocumentSearchInternalUtils.getSearchableAttributeValueObjectTypes();
255
256 XMLSearchableAttributeContent content = new XMLSearchableAttributeContent(getConfigXML(extensionDefinition));
257 List<XMLSearchableAttributeContent.FieldDef> fields;
258 try {
259 fields = content.getFieldDefList();
260 } catch (XPathExpressionException xpee) {
261 throw new RuntimeException("Error parsing searchable attribute configuration", xpee);
262 } catch (ParserConfigurationException pce) {
263 throw new RuntimeException("Error parsing searchable attribute configuration", pce);
264 }
265 for (XMLSearchableAttributeContent.FieldDef field: fields) {
266 searchFields.add(convertFieldDef(field, searchableAttributeValues));
267 }
268
269 return searchFields;
270 }
271
272
273
274
275 private RemotableAttributeField convertFieldDef(XMLSearchableAttributeContent.FieldDef field, Collection<SearchableAttributeValue> searchableAttributeValues) {
276 RemotableAttributeField.Builder fieldBuilder = RemotableAttributeField.Builder.create(field.name);
277
278 fieldBuilder.setLongLabel(field.title);
279
280 RemotableAttributeLookupSettings.Builder attributeLookupSettings = RemotableAttributeLookupSettings.Builder.create();
281 fieldBuilder.setAttributeLookupSettings(attributeLookupSettings);
282
283
284 if (field.defaultValue != null) {
285 fieldBuilder.setDefaultValues(Collections.singletonList(field.defaultValue));
286 }
287
288
289 applyVisibility(fieldBuilder, attributeLookupSettings, field);
290
291
292 RemotableAbstractControl.Builder controlBuilder = constructControl(field.display.type, field.display.options);
293 fieldBuilder.setControl(controlBuilder);
294 if ("date".equals(field.display.type)) {
295 fieldBuilder.getWidgets().add(RemotableDatepicker.Builder.create());
296 fieldBuilder.setDataType(DataType.DATE);
297 }
298 if (!field.display.selectedOptions.isEmpty()) {
299 fieldBuilder.setDefaultValues(field.display.selectedOptions);
300 }
301
302
303 attributeLookupSettings.setInResults(field.isDisplayedInSearchResults());
304
305
306
307 DataType dataType = DocumentSearchInternalUtils.convertValueToDataType(field.searchDefinition.dataType);
308 fieldBuilder.setDataType(dataType);
309 if (DataType.DATE == fieldBuilder.getDataType()) {
310 fieldBuilder.getWidgets().add(RemotableDatepicker.Builder.create());
311 }
312
313 boolean isRangeSearchField = isRangeSearchField(searchableAttributeValues, fieldBuilder.getDataType(), field);
314 if (isRangeSearchField) {
315 attributeLookupSettings.setRanged(true);
316
317 attributeLookupSettings.setLowerBoundInclusive(field.searchDefinition.lowerBound.inclusive);
318 attributeLookupSettings.setUpperBoundInclusive(field.searchDefinition.upperBound.inclusive);
319 attributeLookupSettings.setLowerLabel(field.searchDefinition.lowerBound.label);
320 attributeLookupSettings.setUpperLabel(field.searchDefinition.upperBound.label);
321 attributeLookupSettings.setLowerDatePicker(field.searchDefinition.lowerBound.datePicker);
322 attributeLookupSettings.setUpperDatePicker(field.searchDefinition.upperBound.datePicker);
323 }
324
325 Boolean caseSensitive = field.searchDefinition.getRangeBoundOptions().caseSensitive;
326 if (caseSensitive != null) {
327 attributeLookupSettings.setCaseSensitive(caseSensitive);
328 }
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353 String formatter = field.display.formatter == null ? null : field.display.formatter;
354 fieldBuilder.setFormatterName(formatter);
355
356 try {
357
358 if(StringUtils.isNotEmpty(formatter)){
359 Formatter.registerFormatter(Class.forName(formatter), Class.forName(formatter));
360 }
361 } catch (ClassNotFoundException e) {
362 LOG.error("Unable to find formatter class: " + formatter);
363 throw new RuntimeException("Unable to find formatter class: " + formatter);
364 }
365
366
367
368
369 if (field.lookup.dataObjectClass != null) {
370 RemotableQuickFinder.Builder quickFinderBuilder = RemotableQuickFinder.Builder.create(LookupUtils.getBaseLookupUrl(false), field.lookup.dataObjectClass);
371 quickFinderBuilder.setFieldConversions(field.lookup.fieldConversions);
372 fieldBuilder.getWidgets().add(quickFinderBuilder);
373 }
374
375 return fieldBuilder.build();
376 }
377
378
379
380
381
382
383
384
385 private boolean isRangeSearchField(Collection<SearchableAttributeValue> searchableAttributeValues, DataType dataType, XMLSearchableAttributeContent.FieldDef field) {
386 for (SearchableAttributeValue attValue : searchableAttributeValues)
387 {
388 DataType attributeValueDataType = DocumentSearchInternalUtils.convertValueToDataType(attValue.getAttributeDataType());
389 if (attributeValueDataType == dataType) {
390 return isRangeSearchField(attValue, field);
391 }
392 }
393 String errorMsg = "Could not find searchable attribute value for data type '" + dataType + "'";
394 LOG.error("isRangeSearchField(List, String, NamedNodeMap, Node) " + errorMsg);
395 throw new WorkflowRuntimeException(errorMsg);
396 }
397
398 private boolean isRangeSearchField(SearchableAttributeValue searchableAttributeValue, XMLSearchableAttributeContent.FieldDef field) {
399
400
401 boolean allowRangedSearch = searchableAttributeValue.allowsRangeSearches();
402
403
404 return allowRangedSearch && field.searchDefinition.isRangedSearch();
405 }
406
407
408
409
410 private void applyVisibility(RemotableAttributeField.Builder fieldBuilder, RemotableAttributeLookupSettings.Builder attributeLookupSettings, XMLSearchableAttributeContent.FieldDef field) {
411 boolean visible = true;
412
413 if (field.visibility.visible != null) {
414 visible = field.visibility.visible;
415 } else {
416 if (field.visibility.groupName != null) {
417 UserSession session = GlobalVariables.getUserSession();
418 if (session == null) {
419 throw new WorkflowRuntimeException("UserSession is null! Attempted to render the searchable attribute outside of an established session.");
420 }
421 GroupService groupService = KimApiServiceLocator.getGroupService();
422
423 Group group = groupService.getGroupByNamespaceCodeAndName(field.visibility.groupNamespace, field.visibility.groupName);
424 visible = group == null ? false : groupService.isMemberOfGroup(session.getPerson().getPrincipalId(), group.getId());
425 }
426 }
427 String type = field.visibility.type;
428 if ("field".equals(type) || "fieldAndColumn".equals(type)) {
429
430 if (!visible) {
431 fieldBuilder.setControl(RemotableHiddenInput.Builder.create());
432 }
433 }
434 if ("column".equals(type) || "fieldAndColumn".equals(type)) {
435 attributeLookupSettings.setInResults(visible);
436 }
437 }
438
439 private RemotableAbstractControl.Builder constructControl(String type, Collection<KeyValue> options) {
440 RemotableAbstractControl.Builder control = null;
441 Map<String, String> optionMap = new LinkedHashMap<String, String>();
442 for (KeyValue option : options) {
443 optionMap.put(option.getKey(), option.getValue());
444 }
445 if ("text".equals(type) || "date".equals(type)) {
446 control = RemotableTextInput.Builder.create();
447 } else if ("select".equals(type)) {
448 control = RemotableSelect.Builder.create(optionMap);
449 } else if ("radio".equals(type)) {
450 control = RemotableRadioButtonGroup.Builder.create(optionMap);
451 } else if ("hidden".equals(type)) {
452 control = RemotableHiddenInput.Builder.create();
453 } else if ("multibox".equals(type)) {
454 RemotableSelect.Builder builder = RemotableSelect.Builder.create(optionMap);
455 builder.setMultiple(true);
456 control = builder;
457 } else {
458 throw new IllegalArgumentException("Illegal field type found: " + type);
459 }
460 return control;
461 }
462
463 @Override
464 public List<RemotableAttributeError> validateDocumentAttributeCriteria(ExtensionDefinition extensionDefinition, DocumentSearchCriteria documentSearchCriteria) {
465 List<RemotableAttributeError> errors = new ArrayList<RemotableAttributeError>();
466
467 Map<String, List<String>> documentAttributeValues = documentSearchCriteria.getDocumentAttributeValues();
468 if (documentAttributeValues == null || documentAttributeValues.isEmpty()) {
469
470 return errors;
471 }
472
473 XMLSearchableAttributeContent content = new XMLSearchableAttributeContent(getConfigXML(extensionDefinition));
474 List<XMLSearchableAttributeContent.FieldDef> fields;
475 try {
476 fields = content.getFieldDefList();
477 } catch (XPathExpressionException xpee) {
478 throw new RuntimeException("Error parsing searchable attribute configuration", xpee);
479 } catch (ParserConfigurationException pce) {
480 throw new RuntimeException("Error parsing searchable attribute configuration", pce);
481 }
482 if (fields.isEmpty()) {
483 LOG.warn("Could not find any field definitions (<" + FIELD_DEF_E + ">) or possibly a searching configuration (<searchingConfig>) for this XMLSearchAttribute");
484 return errors;
485 }
486
487 for (XMLSearchableAttributeContent.FieldDef field: fields) {
488 String fieldDefName = field.name;
489 String fieldDefTitle = field.title == null ? "" : field.title;
490
491 List<String> testObject = documentAttributeValues.get(fieldDefName);
492
493 if (testObject == null || testObject.isEmpty()) {
494
495
496 continue;
497 }
498
499
500
501 SearchableAttributeValue attributeValue = DocumentSearchInternalUtils.getSearchableAttributeValueByDataTypeString(field.searchDefinition.dataType);
502 if (attributeValue == null) {
503 String errorMsg = "Cannot find SearchableAttributeValue for field data type '" + field.searchDefinition.dataType + "'";
504 LOG.error("validateUserSearchInputs() " + errorMsg);
505 throw new RuntimeException(errorMsg);
506 }
507
508
509
510
511
512 List<String> terminalValues = new ArrayList<String>();
513 List<Range> rangeValues = new ArrayList<Range>();
514
515
516
517
518
519 for (String value: testObject) {
520
521 if (value == null) {
522
523 continue;
524 }
525
526 String[] clauses = SearchExpressionUtils.splitOnClauses(value);
527 for (String clause: clauses) {
528
529 Range r = null;
530 if (StringUtils.isNotEmpty(value)) {
531 r = SearchExpressionUtils.parseRange(value);
532 }
533 if (r != null) {
534
535 boolean errs = false;
536 if (!field.searchDefinition.isRangedSearch()) {
537 errs = true;
538 errors.add(RemotableAttributeError.Builder.create(field.name, "field does not support ranged searches but range search expression detected").build());
539 } else {
540
541
542 if (PEDANTIC_BOUNDS_VALIDATION) {
543
544
545
546
547
548 if (r.getLowerBoundValue() != null && r.isLowerBoundInclusive() != field.searchDefinition.lowerBound.inclusive) {
549 errs = true;
550 errors.add(RemotableAttributeError.Builder.create(field.name, "range expression ('" + value + "') and attribute definition differ on lower bound inclusivity. Range is: " + r.isLowerBoundInclusive() + " Attrib is: " + field.searchDefinition.lowerBound.inclusive).build());
551 }
552 if (r.getUpperBoundValue() != null && r.isUpperBoundInclusive() != field.searchDefinition.upperBound.inclusive) {
553 errs = true;
554 errors.add(RemotableAttributeError.Builder.create(field.name, "range expression ('" + value + "') and attribute definition differ on upper bound inclusivity. Range is: " + r.isUpperBoundInclusive() + " Attrib is: " + field.searchDefinition.upperBound.inclusive).build());
555 }
556 }
557 }
558
559 if (!errs) {
560 rangeValues.add(r);
561 }
562 } else {
563 terminalValues.add(value);
564 }
565 }
566 }
567
568 List<String> parsedValues = new ArrayList<String>();
569
570 for (String value: terminalValues) {
571 errors.addAll(performValidation(attributeValue, field, value, fieldDefTitle, parsedValues));
572 }
573 for (Range range: rangeValues) {
574 List<String> parsedLowerValues = new ArrayList<String>();
575 List<String> parsedUpperValues = new ArrayList<String>();
576 List<RemotableAttributeError> lowerErrors = performValidation(attributeValue, field,
577 range.getLowerBoundValue(), constructRangeFieldErrorPrefix(field.title,
578 field.searchDefinition.lowerBound), parsedLowerValues);
579 errors.addAll(lowerErrors);
580 List<RemotableAttributeError> upperErrors = performValidation(attributeValue, field, range.getUpperBoundValue(),
581 constructRangeFieldErrorPrefix(field.title, field.searchDefinition.upperBound), parsedUpperValues);
582 errors.addAll(upperErrors);
583
584
585 if (lowerErrors.isEmpty() && upperErrors.isEmpty()) {
586
587 String lowerBoundValue = parsedLowerValues.isEmpty() ? null : parsedLowerValues.get(0);
588 String upperBoundValue = parsedUpperValues.isEmpty() ? null : parsedUpperValues.get(0);
589
590 final Boolean rangeValid;
591
592
593 if (KewApiConstants.SearchableAttributeConstants.DATA_TYPE_STRING.equals(field.searchDefinition.dataType)) {
594 boolean caseSensitive = field.searchDefinition.getRangeBoundOptions().caseSensitive == null ? true : field.searchDefinition.getRangeBoundOptions().caseSensitive;
595 rangeValid = ((CaseAwareSearchableAttributeValue) attributeValue).isRangeValid(lowerBoundValue, upperBoundValue, caseSensitive);
596 } else {
597 rangeValid = attributeValue.isRangeValid(lowerBoundValue, upperBoundValue);
598 }
599
600 if (rangeValid != null && !rangeValid) {
601 String errorMsg = "The " + fieldDefTitle + " range is incorrect. The " +
602 (StringUtils.isNotBlank(field.searchDefinition.lowerBound.label) ? field.searchDefinition.lowerBound.label : KewApiConstants.SearchableAttributeConstants.DEFAULT_RANGE_SEARCH_LOWER_BOUND_LABEL)
603 + " value entered must come before the " +
604 (StringUtils.isNotBlank(field.searchDefinition.upperBound.label) ? field.searchDefinition.upperBound.label : KewApiConstants.SearchableAttributeConstants.DEFAULT_RANGE_SEARCH_UPPER_BOUND_LABEL)
605 + " value";
606 LOG.debug("validateUserSearchInputs() " + errorMsg + " :: field type '" + attributeValue.getAttributeDataType() + "'");
607 errors.add(RemotableAttributeError.Builder.create(fieldDefName, errorMsg).build());
608 }
609 }
610 }
611 }
612 return errors;
613 }
614
615 private String constructRangeFieldErrorPrefix(String fieldDefLabel, XMLSearchableAttributeContent.FieldDef.SearchDefinition.RangeBound rangeBound) {
616 if ( StringUtils.isNotBlank(rangeBound.label) && StringUtils.isNotBlank(fieldDefLabel)) {
617 return fieldDefLabel + " " + rangeBound.label + " Field";
618 } else if (StringUtils.isNotBlank(fieldDefLabel)) {
619 return fieldDefLabel + " Range Field";
620 } else if (StringUtils.isNotBlank(rangeBound.label)) {
621 return "Range Field " + rangeBound.label + " Field";
622 }
623 return null;
624 }
625
626
627
628
629
630
631
632
633
634
635 private List<RemotableAttributeError> performValidation(SearchableAttributeValue attributeValue, final XMLSearchableAttributeContent.FieldDef field, String enteredValue, String errorMessagePrefix, List<String> resultingValues) {
636 return DocumentSearchInternalUtils.validateSearchFieldValue(field.name, attributeValue, enteredValue, errorMessagePrefix, resultingValues, new Function<String, Collection<RemotableAttributeError>>() {
637 @Override
638 public Collection<RemotableAttributeError> apply(String value) {
639 if (StringUtils.isNotEmpty(field.validation.regex)) {
640 Pattern pattern = Pattern.compile(field.validation.regex);
641 Matcher matcher = pattern.matcher(value);
642 if (!matcher.matches()) {
643 return Collections.singletonList(RemotableAttributeError.Builder.create(field.name, field.validation.message).build());
644 }
645 }
646 return Collections.emptyList();
647 }
648 });
649 }
650
651
652 protected Element getConfigXML(ExtensionDefinition extensionDefinition) {
653 try {
654 String xmlConfigData = extensionDefinition.getConfiguration().get(KewApiConstants.ATTRIBUTE_XML_CONFIG_DATA);
655 return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new BufferedReader(new StringReader(xmlConfigData)))).getDocumentElement();
656 } catch (Exception e) {
657 String ruleAttrStr = (extensionDefinition == null ? null : extensionDefinition.getName());
658 LOG.error("error parsing xml data from search attribute: " + ruleAttrStr, e);
659 throw new RuntimeException("error parsing xml data from searchable attribute: " + ruleAttrStr, e);
660 }
661 }
662 }