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