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