001 /**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.kew.rule.xmlrouting;
017
018 import org.apache.commons.lang.StringUtils;
019 import org.kuali.rice.core.api.uif.RemotableAttributeErrorContract;
020 import org.kuali.rice.core.api.uif.RemotableAttributeError;
021 import org.kuali.rice.core.api.util.ConcreteKeyValue;
022 import org.kuali.rice.core.api.util.KeyValue;
023 import org.kuali.rice.core.api.util.xml.XmlJotter;
024 import org.kuali.rice.kew.api.KewApiConstants;
025 import org.kuali.rice.kew.api.WorkflowRuntimeException;
026 import org.kuali.rice.kew.api.extension.ExtensionDefinition;
027 import org.kuali.rice.kew.api.rule.RuleExtension;
028 import org.kuali.rice.kew.attribute.XMLAttributeUtils;
029 import org.kuali.rice.kew.exception.WorkflowServiceError;
030 import org.kuali.rice.kew.exception.WorkflowServiceErrorImpl;
031 import org.kuali.rice.kew.routeheader.DocumentContent;
032 import org.kuali.rice.kew.rule.RuleExtensionBo;
033 import org.kuali.rice.kew.rule.RuleExtensionValue;
034 import org.kuali.rice.kew.rule.WorkflowAttributeXmlValidator;
035 import org.kuali.rice.kns.web.ui.Field;
036 import org.kuali.rice.kns.web.ui.Row;
037 import org.w3c.dom.Element;
038 import org.w3c.dom.NamedNodeMap;
039 import org.w3c.dom.Node;
040 import org.w3c.dom.NodeList;
041 import org.xml.sax.InputSource;
042
043 import javax.xml.parsers.DocumentBuilderFactory;
044 import javax.xml.xpath.XPath;
045 import javax.xml.xpath.XPathConstants;
046 import javax.xml.xpath.XPathExpressionException;
047 import java.io.BufferedReader;
048 import java.io.StringReader;
049 import java.util.ArrayList;
050 import java.util.HashMap;
051 import java.util.Iterator;
052 import java.util.List;
053 import java.util.Map;
054 import java.util.regex.Pattern;
055
056
057 /**
058 * A generic WorkflowAttribute implementation that can be defined completely by XML.
059 * <ol>
060 * <li>This attribute implementation takes "properties" defined on the the {@link org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition}
061 * and maps them to the param map of {@link GenericXMLRuleAttribute}, which relate directly to a set of fields defined by the
062 * XML <code><routingConfig></code> configuration.</li>
063 * <li>Application of the properties defined on the WorkflowAttributeDefinition
064 * to the actual attribute is performed in {@link org.kuali.rice.core.framework.resourceloader.ObjectDefinitionResolver#invokeProperties(Object, java.util.Collection)}</li>
065 * <li>These params are then used to perform one of either EITHER:
066 * <ul>
067 * <li>Replace parameters of the syntax <code>%<i>field name</i>%</code> in the doc content if doc content is
068 * defined in the <code><xmlDocumentContent></code></li>
069 * <li>Generate a generic doc content, containing the parameter key/value pairs, e.g.:
070 * <blockquote>
071 * <code><pre>
072 * <xmlrouting>
073 * <field name="color"><value>red</value></field>
074 * <field name="shape"><value>circle</value></field>
075 * </xmlrouting>
076 * </pre></code>
077 * </blockquote>
078 * </li>
079 * </ul>
080 * Currently, only parameters that match fields configured in the routingConfig are honored (the others are ignored)
081 * (NOTE: to make this even more reusable we might want to consider generating content for all parameters, even those that
082 * do not have corresponding fields)
083 * </li>
084 * <li>The routingConfig element defines a set of <code>fieldDef</code>s, each of which may have an <code>xpathexpression</code> for field evaluation.
085 * This <code>xpathexpression</code> is used to determine whether the attribute's {@link #isMatch(DocumentContent, List)} will
086 * succeed. Each fieldDef may also have a <code>validation</code> element which supplies a regular expression against which
087 * to validate the field value (given by the param map)</li>
088 * </ol>
089 *
090 * @author Kuali Rice Team (rice.collab@kuali.org)
091 */
092 public class StandardGenericXMLRuleAttribute implements GenericXMLRuleAttribute, WorkflowAttributeXmlValidator {
093 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(StandardGenericXMLRuleAttribute.class);
094
095 private static final String FIELD_DEF_E = "fieldDef";
096
097 private boolean evaluateForMissingExtensions = false;
098
099 private NodeList getFields(XPath xpath, Element root, String[] types) throws XPathExpressionException {
100 final String OR = " or ";
101 StringBuffer findField = new StringBuffer("//routingConfig/" + FIELD_DEF_E);
102 if (types != null && types.length > 0) {
103 findField.append("[");
104 for (String type : types) {
105 findField.append("@workflowType='" + type + "'" + OR);
106 // missing workflowType is equivalent ("defaults") to ALL
107 if ("ALL".equals(type)) {
108 findField.append("not(@workflowType)" + OR);
109 }
110 }
111 if (types.length > 0) {
112 // remove trailing " or "
113 findField.setLength(findField.length() - OR.length());
114 }
115 findField.append("]");
116 }
117
118 try {
119 return (NodeList) xpath.evaluate(findField.toString(), root, XPathConstants.NODESET);
120 } catch (XPathExpressionException e) {
121 LOG.error("Error evaluating expression: '" + findField + "'");
122 throw e;
123 }
124 }
125
126 private List<Row> getRows(Element root, String[] types) {
127 List<Row> rows = new ArrayList<Row>();
128 XPath xpath = XPathHelper.newXPath();
129 NodeList fieldNodeList;
130 try {
131 fieldNodeList = getFields(xpath, root, types);
132 } catch (XPathExpressionException e) {
133 LOG.error("Error evaluating fields expression");
134 return rows;
135 }
136 if (fieldNodeList != null) {
137 for (int i = 0; i < fieldNodeList.getLength(); i++) {
138 Node field = fieldNodeList.item(i);
139 NamedNodeMap fieldAttributes = field.getAttributes();
140
141 List<Field> fields = new ArrayList<Field>();
142 Field myField = new Field(fieldAttributes.getNamedItem("title").getNodeValue(), "", "", false, fieldAttributes.getNamedItem("name").getNodeValue(), "", false, false, null, "");
143 String quickfinderService = null;
144 for (int j = 0; j < field.getChildNodes().getLength(); j++) {
145 Node childNode = field.getChildNodes().item(j);
146 if ("value".equals(childNode.getNodeName())) {
147 myField.setPropertyValue(childNode.getFirstChild().getNodeValue());
148 } else if ("display".equals(childNode.getNodeName())) {
149 List<KeyValue> options = new ArrayList<KeyValue>();
150 List<String> selectedOptions = new ArrayList<String>();
151 for (int k = 0; k < childNode.getChildNodes().getLength(); k++) {
152 Node displayChildNode = childNode.getChildNodes().item(k);
153 if ("type".equals(displayChildNode.getNodeName())) {
154 myField.setFieldType(convertTypeToFieldType(displayChildNode.getFirstChild().getNodeValue()));
155 } else if ("meta".equals(displayChildNode.getNodeName())) {
156 // i don't think the rule creation support things in this node.
157 // i don't think the flex Routing report supports things in this node.
158 } else if ("values".equals(displayChildNode.getNodeName())) {
159 NamedNodeMap valuesAttributes = displayChildNode.getAttributes();
160 String optionValue = "";
161 // if element is empty then child will be null
162 Node firstChild = displayChildNode.getFirstChild();
163 if (firstChild != null) {
164 optionValue = firstChild.getNodeValue();
165 }
166 if (valuesAttributes.getNamedItem("selected") != null) {
167 selectedOptions.add(optionValue);
168 }
169 String title = "";
170 Node titleAttribute = valuesAttributes.getNamedItem("title");
171 if (titleAttribute != null) {
172 title = titleAttribute.getNodeValue();
173 }
174 options.add(new ConcreteKeyValue(optionValue, title));
175 }
176 }
177 if (!options.isEmpty()) {
178 myField.setFieldValidValues(options);
179 if (!selectedOptions.isEmpty()) {
180 //if (Field.MULTI_VALUE_FIELD_TYPES.contains(myField.getFieldType())) {
181 // String[] newSelectedOptions = new String[selectedOptions.size()];
182 // int k = 0;
183 // for (Iterator iter = selectedOptions.iterator(); iter.hasNext();) {
184 // String option = (String) iter.next();
185 // newSelectedOptions[k] = option;
186 // k++;
187 // }
188 // myField.setPropertyValues(newSelectedOptions);
189 //} else {
190 //
191 myField.setPropertyValue((String)selectedOptions.get(0));
192 //}
193 }
194 }
195 } else if ("lookup".equals(childNode.getNodeName())) {
196 XMLAttributeUtils.establishFieldLookup(myField, childNode);
197 }
198 }
199 fields.add(myField);
200 rows.add(new Row(fields));
201 }
202 }
203 return rows;
204 }
205
206 private static String convertTypeToFieldType(String type) {
207 if ("text".equals(type)) {
208 return Field.TEXT;
209 } else if ("select".equals(type)) {
210 return Field.DROPDOWN;
211 } else if ("radio".equals(type)) {
212 return Field.RADIO;
213 } else if ("quickfinder".equals(type)) {
214 return Field.QUICKFINDER;
215 }
216 return null;
217 }
218
219 // thin interface for generating the appropriate error type
220 private static interface ErrorGenerator<T> {
221 T generateInvalidFieldError(Node field, String fieldName, String message);
222 T generateMissingFieldError(Node field, String fieldName, String message);
223 }
224
225 private ExtensionDefinition extensionDefinition;
226 private Map paramMap = new HashMap();
227 private List ruleRows = new ArrayList();
228 private List routingDataRows = new ArrayList();
229 private boolean required;
230
231 public StandardGenericXMLRuleAttribute() {
232 }
233
234 public void setExtensionDefinition(ExtensionDefinition extensionDefinition) {
235 this.extensionDefinition = extensionDefinition;
236 }
237
238 // public boolean isMatch(DocumentContent docContent, List ruleExtensions) {
239 // XPath xpath = XPathHelper.newXPath(docContent.getDocument());
240 // WorkflowFunctionResolver resolver = XPathHelper.extractFunctionResolver(xpath);
241 // for (Iterator iter = ruleExtensions.iterator(); iter.hasNext();) {
242 // RuleExtension extension = (RuleExtension) iter.next();
243 // if (extension.getRuleTemplateAttribute().getRuleAttribute().getName().equals(ruleAttribute.getName())) {
244 // List extensions = new ArrayList();
245 // extensions.add(extension);
246 // resolver.setRuleExtensions(extensions);
247 // //xpath.setXPathFunctionResolver(resolver);
248 // for (Iterator iterator = extension.getExtensionValues().iterator(); iterator.hasNext();) {
249 // RuleExtensionValue value = (RuleExtensionValue) iterator.next();
250 // String findXpathExpression = "//routingConfig/" + FIELD_DEF_E + "[@name='" + value.getKey() + "']/fieldEvaluation/xpathexpression";
251 // String xpathExpression = null;
252 // try {
253 // xpathExpression = (String) xpath.evaluate(findXpathExpression, getConfigXML(), XPathConstants.STRING);
254 // LOG.debug("routingConfig XPath expression: " + xpathExpression);
255 // if (!org.apache.commons.lang.StringUtils.isEmpty(xpathExpression)) {
256 // LOG.debug("DocContent: " + docContent.getDocContent());
257 // Boolean match = (Boolean) xpath.evaluate(xpathExpression, docContent.getDocument(), XPathConstants.BOOLEAN);
258 // LOG.debug("routingConfig match? " + match);
259 // if (match != null && !match.booleanValue()) {
260 // return false;
261 // }
262 // }
263 // } catch (XPathExpressionException e) {
264 // LOG.error("error in isMatch ", e);
265 // throw new RuntimeException("Error trying to find xml content with xpath expressions: " + findXpathExpression + " or " + xpathExpression, e);
266 // }
267 // }
268 // resolver.setRuleExtensions(null);
269 // }
270 // }
271 // String findXpathExpression = "//routingConfig/globalEvaluations/xpathexpression";
272 // String xpathExpression = "";
273 // try {
274 // NodeList xpathExpressions = (NodeList) xpath.evaluate(findXpathExpression, getConfigXML(), XPathConstants.NODESET);
275 // for (int i = 0; i < xpathExpressions.getLength(); i++) {
276 // Node xpathNode = xpathExpressions.item(i);
277 // xpathExpression = xpathNode.getFirstChild().getNodeValue();
278 // LOG.debug("global XPath expression: " + xpathExpression);
279 // if (!org.apache.commons.lang.StringUtils.isEmpty(xpathExpression)) {
280 // LOG.debug("DocContent: " + docContent.getDocContent());
281 // Boolean match = (Boolean) xpath.evaluate(xpathExpression, docContent.getDocument(), XPathConstants.BOOLEAN);
282 // LOG.debug("Global match? " + match);
283 // if (match != null && !match.booleanValue()) {
284 // return false;
285 // }
286 // }
287 // }
288 // } catch (XPathExpressionException e) {
289 // LOG.error("error in isMatch ", e);
290 // throw new RuntimeException("Error trying to find xml content with xpath expressions: " + findXpathExpression, e);
291 // }
292 // return true;
293 // }
294
295 public boolean isMatch(DocumentContent docContent, List<RuleExtension> ruleExtensions) {
296 XPath xpath = XPathHelper.newXPath(docContent.getDocument());
297 WorkflowFunctionResolver resolver = XPathHelper.extractFunctionResolver(xpath);
298 resolver.setRuleExtensions(ruleExtensions);
299 List<String> xPathExpressionsToEvaluate = extractExpressionsToEvaluate(xpath, docContent, ruleExtensions);
300 for (String xPathExpressionToEvaluate : xPathExpressionsToEvaluate) {
301 if (LOG.isDebugEnabled()) {
302 LOG.debug("Evaluating xPath expression: " + xPathExpressionToEvaluate);
303 }
304 try {
305 Boolean match = (Boolean) xpath.evaluate(xPathExpressionToEvaluate, docContent.getDocument(), XPathConstants.BOOLEAN);
306 if (LOG.isDebugEnabled()) {
307 LOG.debug("Expression match result: " + match);
308 }
309 if (match != null && !match.booleanValue()) {
310 return false;
311 }
312 } catch (XPathExpressionException e) {
313 LOG.error("Error in isMatch ", e);
314 throw new RuntimeException("Error trying to evalute xml content with xpath expression: " + xPathExpressionToEvaluate, e);
315 }
316 }
317 return true;
318 }
319
320 /**
321 * Extracts the xPath expressions that should be evaluated in order to determine whether or not the rule matches. THis should take
322 * into account the value of evaluateForMissingExtensions.
323 */
324 protected List<String> extractExpressionsToEvaluate(XPath xpath, DocumentContent docContent, List<RuleExtension> ruleExtensions) {
325 List<String> expressionsToEvaluate = new ArrayList<String>(ruleExtensions.size() + 1);
326 Element configXml = getConfigXML();
327 String findFieldExpressions = "//routingConfig/" + FIELD_DEF_E + "/fieldEvaluation/xpathexpression";
328 try {
329 NodeList xPathExpressions = (NodeList) xpath.evaluate(findFieldExpressions, configXml, XPathConstants.NODESET);
330 for (int index = 0; index < xPathExpressions.getLength(); index++) {
331 Element expressionElement = (Element) xPathExpressions.item(index);
332 String expression = expressionElement.getTextContent();
333 if (!isEvaluateForMissingExtensions()) {
334 Node parentNode = expressionElement.getParentNode().getParentNode();
335 Node fieldAttribute = parentNode.getAttributes().getNamedItem("name");
336 if (fieldAttribute == null || StringUtils.isEmpty(fieldAttribute.getNodeValue())) {
337 throw new WorkflowRuntimeException("Could not determine field name defined on fieldDef for xpath expression: " + expression);
338 }
339 String fieldName = fieldAttribute.getNodeValue();
340 boolean foundExtension = false;
341 outer:for (RuleExtension ruleExtension : ruleExtensions) {
342 if (ruleExtension.getRuleTemplateAttribute().getRuleAttribute().getName().equals(extensionDefinition.getName())) {
343 for (String ruleExtensionValueKey : ruleExtension.getExtensionValuesMap().keySet()) {
344 if (fieldName.equals(ruleExtensionValueKey)) {
345 foundExtension = true;
346 break outer;
347 }
348 }
349 }
350 }
351 if (!foundExtension) {
352 // if the rule does not have an extension value for the xpath expression on the corresponding field def, let's skip it
353 continue;
354 }
355 }
356
357 if (!StringUtils.isEmpty(expression)) {
358 if (LOG.isDebugEnabled()) {
359 LOG.debug("Adding routingConfig XPath expression: " + expression);
360 }
361 expressionsToEvaluate.add(expression);
362 }
363 }
364 } catch (XPathExpressionException e) {
365 throw new WorkflowRuntimeException("Failed to evalute XPath expression for fieldDefs: " + findFieldExpressions);
366 }
367 String findGlobalExpressions = "//routingConfig/globalEvaluations/xpathexpression";
368 try {
369 NodeList xPathExpressions = (NodeList) xpath.evaluate(findGlobalExpressions, configXml, XPathConstants.NODESET);
370 for (int index = 0; index < xPathExpressions.getLength(); index++) {
371 Element expressionElement = (Element) xPathExpressions.item(index);
372 //String expression = XmlJotter.jotNode(expressionElement);
373 String expression = expressionElement.getTextContent();
374 if (!StringUtils.isEmpty(expression)) {
375 if (LOG.isDebugEnabled()) {
376 LOG.debug("Adding global XPath expression: " + expression);
377 }
378 expressionsToEvaluate.add(expression);
379 }
380 }
381 } catch (XPathExpressionException e) {
382 throw new WorkflowRuntimeException("Failed to evalute global XPath expression: " + findGlobalExpressions);
383 }
384 return expressionsToEvaluate;
385 }
386
387 public List getRuleRows() {
388 if (ruleRows.isEmpty()) {
389 ruleRows = getRows(getConfigXML(), new String[] { "ALL", "RULE" });
390 }
391 return ruleRows;
392 }
393
394 private String getValidationErrorMessage(XPath xpath, Element root, String fieldName) throws XPathExpressionException {
395 String findErrorMessage = "//routingConfig/" + FIELD_DEF_E + "[@name='" + fieldName + "']/validation/message";
396 return (String) xpath.evaluate(findErrorMessage, root, XPathConstants.STRING);
397 }
398
399 /**
400 * Performs attribute validation producing a list of errors of the parameterized type T generated by the ErrorGenerator<T>
401 * @throws XPathExpressionException
402 */
403 private <T> List<T> validate(Element root, String[] types, Map map, ErrorGenerator<T> errorGenerator) throws XPathExpressionException {
404 List<T> errors = new ArrayList();
405 XPath xpath = XPathHelper.newXPath();
406
407 NodeList nodes = getFields(xpath, root, types);
408 for (int i = 0; i < nodes.getLength(); i++) {
409 Node field = nodes.item(i);
410 NamedNodeMap fieldAttributes = field.getAttributes();
411 String fieldName = fieldAttributes.getNamedItem("name").getNodeValue();
412
413 LOG.debug("evaluating field: " + fieldName);
414 String findValidation = "//routingConfig/" + FIELD_DEF_E + "[@name='" + fieldName + "']/validation";
415
416 Node validationNode = (Node) xpath.evaluate(findValidation, root, XPathConstants.NODE);
417 boolean fieldIsRequired = false;
418 if (validationNode != null) {
419 NamedNodeMap validationAttributes = validationNode.getAttributes();
420 Node reqAttribNode = validationAttributes.getNamedItem("required");
421 fieldIsRequired = reqAttribNode != null && "true".equalsIgnoreCase(reqAttribNode.getNodeValue());
422 }
423
424 String findRegex = "//routingConfig/" + FIELD_DEF_E + "[@name='" + fieldName + "']/validation/regex";
425
426 String regex = null;
427 Node regexNode = (Node) xpath.evaluate(findRegex, root, XPathConstants.NODE);
428
429 if (regexNode != null && regexNode.getFirstChild() != null) {
430 regex = regexNode.getFirstChild().getNodeValue();
431 if (regex == null) {
432 throw new RuntimeException("Null regex text node");
433 }
434 }/* else {
435 if (fieldIsRequired) {
436 fieldIsOnlyRequired = true;
437 LOG.debug("Setting empty regex to .+ as field is required");
438 // NOTE: ok, so technically .+ is not the same as checking merely
439 // for existence, because a field can be extant but "empty"
440 // however this has no relevance to the user as an empty field
441 // is for all intents and purposes non-existent (not-filled-in)
442 // so let's just use this regex to simplify the logic and
443 // pass everything through a regex check
444 regex = ".+";
445 } else {
446 LOG.debug("Setting empty regex to .* as field is NOT required");
447 regex = ".*";
448 }
449 }*/
450
451 LOG.debug("regex for field '" + fieldName + "': '" + regex + "'");
452
453 String fieldValue = null;
454 if (map != null) {
455 fieldValue = (String) map.get(fieldName);
456 }
457
458 LOG.debug("field value: " + fieldValue);
459
460 // fix up non-existent value for regex purposes only
461 if (fieldValue == null) {
462 fieldValue = "";
463 }
464
465 if (regex == null){
466 if (fieldIsRequired) {
467 if (fieldValue.length() == 0) {
468 errors.add(errorGenerator.generateMissingFieldError(field, fieldName, getValidationErrorMessage(xpath, root, fieldName)));
469 }
470 }
471 } else {
472 if (!Pattern.compile(regex).matcher(fieldValue).matches()) {
473 LOG.debug("field value does not match validation regex");
474 errors.add(errorGenerator.generateInvalidFieldError(field, fieldName, getValidationErrorMessage(xpath, root, fieldName)));
475 }
476 }
477 }
478 return errors;
479 }
480
481 public List getRoutingDataRows() {
482 if (routingDataRows.isEmpty()) {
483 routingDataRows = getRows(getConfigXML(), new String[] { "ALL", "REPORT" });
484 }
485 return routingDataRows;
486 }
487
488 public String getDocContent() {
489 XPath xpath = XPathHelper.newXPath();
490 final String findDocContent = "//routingConfig/xmlDocumentContent";
491 try {
492 Node xmlDocumentContent = (Node) xpath.evaluate(findDocContent, getConfigXML(), XPathConstants.NODE);
493
494 NodeList nodes = getFields(xpath, getConfigXML(), new String[] { "ALL", "REPORT", "RULE" });
495 // if (nodes == null || nodes.getLength() == 0) {
496 // return "";
497 // }
498
499 if (xmlDocumentContent != null && xmlDocumentContent.hasChildNodes()) {
500 // Custom doc content in the routingConfig xml.
501 String documentContent = "";
502 NodeList customNodes = xmlDocumentContent.getChildNodes();
503 for (int i = 0; i < customNodes.getLength(); i++) {
504 Node childNode = customNodes.item(i);
505 documentContent += XmlJotter.jotNode(childNode);
506 }
507
508 for (int i = 0; i < nodes.getLength(); i++) {
509 Node field = nodes.item(i);
510 NamedNodeMap fieldAttributes = field.getAttributes();
511 String fieldName = fieldAttributes.getNamedItem("name").getNodeValue();
512 LOG.debug("Replacing field '" + fieldName + "'");
513 Map map = getParamMap();
514 String fieldValue = (String) map.get(fieldName);
515 if (map != null && !org.apache.commons.lang.StringUtils.isEmpty(fieldValue)) {
516 LOG.debug("Replacing %" + fieldName + "% with field value: '" + fieldValue + "'");
517 documentContent = documentContent.replaceAll("%" + fieldName + "%", fieldValue);
518 } else {
519 LOG.debug("Field map is null or fieldValue is empty");
520 }
521 }
522 return documentContent;
523 } else {
524 // Standard doc content if no doc content is found in the routingConfig xml.
525 StringBuffer documentContent = new StringBuffer("<xmlRouting>");
526 for (int i = 0; i < nodes.getLength(); i++) {
527 Node field = nodes.item(i);
528 NamedNodeMap fieldAttributes = field.getAttributes();
529 String fieldName = fieldAttributes.getNamedItem("name").getNodeValue();
530 Map map = getParamMap();
531 if (map != null && !org.apache.commons.lang.StringUtils.isEmpty((String) map.get(fieldName))) {
532 documentContent.append("<field name=\"");
533 documentContent.append(fieldName);
534 documentContent.append("\"><value>");
535 documentContent.append((String) map.get(fieldName));
536 documentContent.append("</value></field>");
537 }
538 }
539 documentContent.append("</xmlRouting>");
540 return documentContent.toString();
541 }
542 } catch (XPathExpressionException e) {
543 LOG.error("error in getDocContent ", e);
544 throw new RuntimeException("Error trying to find xml content with xpath expression", e);
545 } catch (Exception e) {
546 LOG.error("error in getDocContent attempting to find xml doc content", e);
547 throw new RuntimeException("Error trying to get xml doc content.", e);
548 }
549 }
550
551 public List getRuleExtensionValues() {
552 List extensionValues = new ArrayList();
553
554 XPath xpath = XPathHelper.newXPath();
555 try {
556 NodeList nodes = getFields(xpath, getConfigXML(), new String[] { "ALL", "RULE" });
557 for (int i = 0; i < nodes.getLength(); i++) {
558 Node field = nodes.item(i);
559 NamedNodeMap fieldAttributes = field.getAttributes();
560 String fieldName = fieldAttributes.getNamedItem("name").getNodeValue();
561 Map map = getParamMap();
562 if (map != null && !org.apache.commons.lang.StringUtils.isEmpty((String) map.get(fieldName))) {
563 RuleExtensionValue value = new RuleExtensionValue();
564 value.setKey(fieldName);
565 value.setValue((String) map.get(fieldName));
566 extensionValues.add(value);
567 }
568 }
569 } catch (XPathExpressionException e) {
570 LOG.error("error in getRuleExtensionValues ", e);
571 throw new RuntimeException("Error trying to find xml content with xpath expression", e);
572 }
573 return extensionValues;
574 }
575
576 public List<RemotableAttributeError> validateRoutingData(Map paramMap) {
577 this.paramMap = paramMap;
578 try {
579 return validate(getConfigXML(), new String[] { "ALL", "REPORT" }, paramMap, new ErrorGenerator<RemotableAttributeError>() {
580 public RemotableAttributeError generateInvalidFieldError(Node field, String fieldName, String message) {
581 return RemotableAttributeError.Builder.create("routetemplate.xmlattribute.error", message).build();
582 }
583 public RemotableAttributeError generateMissingFieldError(Node field, String fieldName, String message) {
584 return RemotableAttributeError.Builder.create("routetemplate.xmlattribute.required.error", field.getAttributes().getNamedItem("title").getNodeValue()).build();
585 }
586 });
587 } catch (XPathExpressionException e) {
588 LOG.error("error in validateRoutingData ", e);
589 throw new RuntimeException("Error trying to find xml content with xpath expression", e);
590 }
591 }
592
593 public List<WorkflowServiceError> validateRuleData(Map paramMap) {
594 this.paramMap = paramMap;
595 try {
596 return validate(getConfigXML(), new String[] { "ALL", "RULE" }, paramMap, new ErrorGenerator<WorkflowServiceError>() {
597 public WorkflowServiceError generateInvalidFieldError(Node field, String fieldName, String message) {
598 return new WorkflowServiceErrorImpl("Xml attribute error.", "routetemplate.xmlattribute.error", message);
599 }
600 public WorkflowServiceError generateMissingFieldError(Node field, String fieldName, String message) {
601 return new WorkflowServiceErrorImpl("Xml attribute error.", "routetemplate.xmlattribute.required.error", field.getAttributes().getNamedItem("title").getNodeValue());
602 }
603 });
604 } catch (XPathExpressionException e) {
605 LOG.error("error in validateRoutingData ", e);
606 throw new RuntimeException("Error trying to find xml content with xpath expression", e);
607 }
608 }
609
610 public void setRequired(boolean required) {
611 this.required = required;
612 }
613
614 public boolean isRequired() {
615 return required;
616 }
617
618 public Element getConfigXML() {
619 try {
620 return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new BufferedReader(
621 new StringReader(extensionDefinition.getConfiguration().get(
622 KewApiConstants.ATTRIBUTE_XML_CONFIG_DATA))))).getDocumentElement();
623 } catch (Exception e) {
624 String str = extensionDefinition == null ? "null" : extensionDefinition.getName();
625 LOG.error("error parsing xml data from rule attribute: " + str, e);
626 throw new RuntimeException("error parsing xml data from rule attribute: " + str, e);
627 }
628 }
629
630 // TODO: possibly simplify even further by unifying AttributeError and WorkflowServiceError
631 public List<? extends RemotableAttributeErrorContract> validateClientRoutingData() {
632 LOG.debug("validating client routing data");
633 try {
634 return validate(getConfigXML(), new String[] { "ALL", "RULE" }, getParamMap(), new ErrorGenerator<RemotableAttributeError>() {
635 public RemotableAttributeError generateInvalidFieldError(Node field, String fieldName, String message) {
636 if (org.apache.commons.lang.StringUtils.isEmpty(message)) {
637 message = "invalid field value";
638 } else {
639 LOG.info("Message: '" + message + "'");
640 }
641 return RemotableAttributeError.Builder.create(fieldName, message).build();
642 }
643 public RemotableAttributeError generateMissingFieldError(Node field, String fieldName, String message) {
644 return RemotableAttributeError.Builder.create(fieldName, "Attribute is required; " + message).build();
645 }
646 });
647 } catch (XPathExpressionException e) {
648 LOG.error("error in validateClientRoutingData ", e);
649 throw new RuntimeException("Error trying to find xml content with xpath expression", e);
650 }
651 }
652
653 public Map getParamMap() {
654 return paramMap;
655 }
656
657 public void setParamMap(Map paramMap) {
658 this.paramMap = paramMap;
659 }
660
661 /**
662 * @return the evaluateForMissingExtensions
663 */
664 public boolean isEvaluateForMissingExtensions() {
665 return this.evaluateForMissingExtensions;
666 }
667
668 /**
669 * Sets whether or not to evaluate expressions if the extension corresponding to that expressions is not present on the rule.
670 * The correspondence is made by comparing the name of the field declared on the fieldDef element and the name of the
671 * rule extension key. If this value is set to true then all xpath expressions defined on all fieldDefs will be evaluated
672 * regardless of whether or not the rule has a corresponding extension value.
673 *
674 * <p>By default this is false to preserve backward compatible behavior.
675 */
676 public void setEvaluateForMissingExtensions(boolean evaluateForMissingExtensions) {
677 this.evaluateForMissingExtensions = evaluateForMissingExtensions;
678 }
679
680
681 }