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 = null; 297 String xPathCacheKey = "xPath" + docContent.getRouteContext().getNodeInstance().getRouteNodeInstanceId() + "-" + docContent.getRouteContext().getNodeInstance().getName(); 298 if(docContent.getRouteContext().getParameters().containsKey(xPathCacheKey)) { 299 xpath = (XPath)docContent.getRouteContext().getParameters().get(xPathCacheKey); 300 } else { 301 xpath = XPathHelper.newXPath(docContent.getDocument()); 302 docContent.getRouteContext().getParameters().put(xPathCacheKey, xpath); 303 } 304 WorkflowFunctionResolver resolver = XPathHelper.extractFunctionResolver(xpath); 305 resolver.setRuleExtensions(ruleExtensions); 306 List<String> xPathExpressionsToEvaluate = extractExpressionsToEvaluate(xpath, docContent, ruleExtensions); 307 for (String xPathExpressionToEvaluate : xPathExpressionsToEvaluate) { 308 if (LOG.isDebugEnabled()) { 309 LOG.debug("Evaluating xPath expression: " + xPathExpressionToEvaluate); 310 } 311 try { 312 Boolean match = (Boolean) xpath.evaluate(xPathExpressionToEvaluate, docContent.getDocument(), XPathConstants.BOOLEAN); 313 if (LOG.isDebugEnabled()) { 314 LOG.debug("Expression match result: " + match); 315 } 316 if (match != null && !match.booleanValue()) { 317 return false; 318 } 319 } catch (XPathExpressionException e) { 320 LOG.error("Error in isMatch ", e); 321 throw new RuntimeException("Error trying to evalute xml content with xpath expression: " + xPathExpressionToEvaluate, e); 322 } 323 } 324 return true; 325 } 326 327 /** 328 * Extracts the xPath expressions that should be evaluated in order to determine whether or not the rule matches. THis should take 329 * into account the value of evaluateForMissingExtensions. 330 */ 331 protected List<String> extractExpressionsToEvaluate(XPath xpath, DocumentContent docContent, List<RuleExtension> ruleExtensions) { 332 List<String> expressionsToEvaluate = new ArrayList<String>(ruleExtensions.size() + 1); 333 Element configXml = getConfigXML(); 334 String findFieldExpressions = "//routingConfig/" + FIELD_DEF_E + "/fieldEvaluation/xpathexpression"; 335 try { 336 NodeList xPathExpressions = (NodeList) xpath.evaluate(findFieldExpressions, configXml, XPathConstants.NODESET); 337 for (int index = 0; index < xPathExpressions.getLength(); index++) { 338 Element expressionElement = (Element) xPathExpressions.item(index); 339 String expression = expressionElement.getTextContent(); 340 if (!isEvaluateForMissingExtensions()) { 341 Node parentNode = expressionElement.getParentNode().getParentNode(); 342 Node fieldAttribute = parentNode.getAttributes().getNamedItem("name"); 343 if (fieldAttribute == null || StringUtils.isEmpty(fieldAttribute.getNodeValue())) { 344 throw new WorkflowRuntimeException("Could not determine field name defined on fieldDef for xpath expression: " + expression); 345 } 346 String fieldName = fieldAttribute.getNodeValue(); 347 boolean foundExtension = false; 348 outer:for (RuleExtension ruleExtension : ruleExtensions) { 349 if (ruleExtension.getRuleTemplateAttribute().getRuleAttribute().getName().equals(extensionDefinition.getName())) { 350 for (String ruleExtensionValueKey : ruleExtension.getExtensionValuesMap().keySet()) { 351 if (fieldName.equals(ruleExtensionValueKey)) { 352 foundExtension = true; 353 break outer; 354 } 355 } 356 } 357 } 358 if (!foundExtension) { 359 // if the rule does not have an extension value for the xpath expression on the corresponding field def, let's skip it 360 continue; 361 } 362 } 363 364 if (!StringUtils.isEmpty(expression)) { 365 if (LOG.isDebugEnabled()) { 366 LOG.debug("Adding routingConfig XPath expression: " + expression); 367 } 368 expressionsToEvaluate.add(expression); 369 } 370 } 371 } catch (XPathExpressionException e) { 372 throw new WorkflowRuntimeException("Failed to evalute XPath expression for fieldDefs: " + findFieldExpressions); 373 } 374 String findGlobalExpressions = "//routingConfig/globalEvaluations/xpathexpression"; 375 try { 376 NodeList xPathExpressions = (NodeList) xpath.evaluate(findGlobalExpressions, configXml, XPathConstants.NODESET); 377 for (int index = 0; index < xPathExpressions.getLength(); index++) { 378 Element expressionElement = (Element) xPathExpressions.item(index); 379 //String expression = XmlJotter.jotNode(expressionElement); 380 String expression = expressionElement.getTextContent(); 381 if (!StringUtils.isEmpty(expression)) { 382 if (LOG.isDebugEnabled()) { 383 LOG.debug("Adding global XPath expression: " + expression); 384 } 385 expressionsToEvaluate.add(expression); 386 } 387 } 388 } catch (XPathExpressionException e) { 389 throw new WorkflowRuntimeException("Failed to evalute global XPath expression: " + findGlobalExpressions); 390 } 391 return expressionsToEvaluate; 392 } 393 394 public List getRuleRows() { 395 if (ruleRows.isEmpty()) { 396 ruleRows = getRows(getConfigXML(), new String[] { "ALL", "RULE" }); 397 } 398 return ruleRows; 399 } 400 401 private String getValidationErrorMessage(XPath xpath, Element root, String fieldName) throws XPathExpressionException { 402 String findErrorMessage = "//routingConfig/" + FIELD_DEF_E + "[@name='" + fieldName + "']/validation/message"; 403 return (String) xpath.evaluate(findErrorMessage, root, XPathConstants.STRING); 404 } 405 406 /** 407 * Performs attribute validation producing a list of errors of the parameterized type T generated by the ErrorGenerator<T> 408 * @throws XPathExpressionException 409 */ 410 private <T> List<T> validate(Element root, String[] types, Map map, ErrorGenerator<T> errorGenerator) throws XPathExpressionException { 411 List<T> errors = new ArrayList(); 412 XPath xpath = XPathHelper.newXPath(); 413 414 NodeList nodes = getFields(xpath, root, types); 415 for (int i = 0; i < nodes.getLength(); i++) { 416 Node field = nodes.item(i); 417 NamedNodeMap fieldAttributes = field.getAttributes(); 418 String fieldName = fieldAttributes.getNamedItem("name").getNodeValue(); 419 420 LOG.debug("evaluating field: " + fieldName); 421 String findValidation = "//routingConfig/" + FIELD_DEF_E + "[@name='" + fieldName + "']/validation"; 422 423 Node validationNode = (Node) xpath.evaluate(findValidation, root, XPathConstants.NODE); 424 boolean fieldIsRequired = false; 425 if (validationNode != null) { 426 NamedNodeMap validationAttributes = validationNode.getAttributes(); 427 Node reqAttribNode = validationAttributes.getNamedItem("required"); 428 fieldIsRequired = reqAttribNode != null && "true".equalsIgnoreCase(reqAttribNode.getNodeValue()); 429 } 430 431 String findRegex = "//routingConfig/" + FIELD_DEF_E + "[@name='" + fieldName + "']/validation/regex"; 432 433 String regex = null; 434 Node regexNode = (Node) xpath.evaluate(findRegex, root, XPathConstants.NODE); 435 436 if (regexNode != null && regexNode.getFirstChild() != null) { 437 regex = regexNode.getFirstChild().getNodeValue(); 438 if (regex == null) { 439 throw new RuntimeException("Null regex text node"); 440 } 441 }/* else { 442 if (fieldIsRequired) { 443 fieldIsOnlyRequired = true; 444 LOG.debug("Setting empty regex to .+ as field is required"); 445 // NOTE: ok, so technically .+ is not the same as checking merely 446 // for existence, because a field can be extant but "empty" 447 // however this has no relevance to the user as an empty field 448 // is for all intents and purposes non-existent (not-filled-in) 449 // so let's just use this regex to simplify the logic and 450 // pass everything through a regex check 451 regex = ".+"; 452 } else { 453 LOG.debug("Setting empty regex to .* as field is NOT required"); 454 regex = ".*"; 455 } 456 }*/ 457 458 LOG.debug("regex for field '" + fieldName + "': '" + regex + "'"); 459 460 String fieldValue = null; 461 if (map != null) { 462 fieldValue = (String) map.get(fieldName); 463 } 464 465 LOG.debug("field value: " + fieldValue); 466 467 // fix up non-existent value for regex purposes only 468 if (fieldValue == null) { 469 fieldValue = ""; 470 } 471 472 if (regex == null){ 473 if (fieldIsRequired) { 474 if (fieldValue.length() == 0) { 475 errors.add(errorGenerator.generateMissingFieldError(field, fieldName, getValidationErrorMessage(xpath, root, fieldName))); 476 } 477 } 478 } else { 479 if (!Pattern.compile(regex).matcher(fieldValue).matches()) { 480 LOG.debug("field value does not match validation regex"); 481 errors.add(errorGenerator.generateInvalidFieldError(field, fieldName, getValidationErrorMessage(xpath, root, fieldName))); 482 } 483 } 484 } 485 return errors; 486 } 487 488 public List getRoutingDataRows() { 489 if (routingDataRows.isEmpty()) { 490 routingDataRows = getRows(getConfigXML(), new String[] { "ALL", "REPORT" }); 491 } 492 return routingDataRows; 493 } 494 495 public String getDocContent() { 496 XPath xpath = XPathHelper.newXPath(); 497 final String findDocContent = "//routingConfig/xmlDocumentContent"; 498 try { 499 Node xmlDocumentContent = (Node) xpath.evaluate(findDocContent, getConfigXML(), XPathConstants.NODE); 500 501 NodeList nodes = getFields(xpath, getConfigXML(), new String[] { "ALL", "REPORT", "RULE" }); 502 // if (nodes == null || nodes.getLength() == 0) { 503 // return ""; 504 // } 505 506 if (xmlDocumentContent != null && xmlDocumentContent.hasChildNodes()) { 507 // Custom doc content in the routingConfig xml. 508 String documentContent = ""; 509 NodeList customNodes = xmlDocumentContent.getChildNodes(); 510 for (int i = 0; i < customNodes.getLength(); i++) { 511 Node childNode = customNodes.item(i); 512 documentContent += XmlJotter.jotNode(childNode); 513 } 514 515 for (int i = 0; i < nodes.getLength(); i++) { 516 Node field = nodes.item(i); 517 NamedNodeMap fieldAttributes = field.getAttributes(); 518 String fieldName = fieldAttributes.getNamedItem("name").getNodeValue(); 519 LOG.debug("Replacing field '" + fieldName + "'"); 520 Map map = getParamMap(); 521 String fieldValue = (String) map.get(fieldName); 522 if (map != null && !org.apache.commons.lang.StringUtils.isEmpty(fieldValue)) { 523 LOG.debug("Replacing %" + fieldName + "% with field value: '" + fieldValue + "'"); 524 documentContent = documentContent.replaceAll("%" + fieldName + "%", fieldValue); 525 } else { 526 LOG.debug("Field map is null or fieldValue is empty"); 527 } 528 } 529 return documentContent; 530 } else { 531 // Standard doc content if no doc content is found in the routingConfig xml. 532 StringBuffer documentContent = new StringBuffer("<xmlRouting>"); 533 for (int i = 0; i < nodes.getLength(); i++) { 534 Node field = nodes.item(i); 535 NamedNodeMap fieldAttributes = field.getAttributes(); 536 String fieldName = fieldAttributes.getNamedItem("name").getNodeValue(); 537 Map map = getParamMap(); 538 if (map != null && !org.apache.commons.lang.StringUtils.isEmpty((String) map.get(fieldName))) { 539 documentContent.append("<field name=\""); 540 documentContent.append(fieldName); 541 documentContent.append("\"><value>"); 542 documentContent.append((String) map.get(fieldName)); 543 documentContent.append("</value></field>"); 544 } 545 } 546 documentContent.append("</xmlRouting>"); 547 return documentContent.toString(); 548 } 549 } catch (XPathExpressionException e) { 550 LOG.error("error in getDocContent ", e); 551 throw new RuntimeException("Error trying to find xml content with xpath expression", e); 552 } catch (Exception e) { 553 LOG.error("error in getDocContent attempting to find xml doc content", e); 554 throw new RuntimeException("Error trying to get xml doc content.", e); 555 } 556 } 557 558 public List getRuleExtensionValues() { 559 List extensionValues = new ArrayList(); 560 561 XPath xpath = XPathHelper.newXPath(); 562 try { 563 NodeList nodes = getFields(xpath, getConfigXML(), new String[] { "ALL", "RULE" }); 564 for (int i = 0; i < nodes.getLength(); i++) { 565 Node field = nodes.item(i); 566 NamedNodeMap fieldAttributes = field.getAttributes(); 567 String fieldName = fieldAttributes.getNamedItem("name").getNodeValue(); 568 Map map = getParamMap(); 569 if (map != null && !org.apache.commons.lang.StringUtils.isEmpty((String) map.get(fieldName))) { 570 RuleExtensionValue value = new RuleExtensionValue(); 571 value.setKey(fieldName); 572 value.setValue((String) map.get(fieldName)); 573 extensionValues.add(value); 574 } 575 } 576 } catch (XPathExpressionException e) { 577 LOG.error("error in getRuleExtensionValues ", e); 578 throw new RuntimeException("Error trying to find xml content with xpath expression", e); 579 } 580 return extensionValues; 581 } 582 583 public List<RemotableAttributeError> validateRoutingData(Map paramMap) { 584 this.paramMap = paramMap; 585 try { 586 return validate(getConfigXML(), new String[] { "ALL", "REPORT" }, paramMap, new ErrorGenerator<RemotableAttributeError>() { 587 public RemotableAttributeError generateInvalidFieldError(Node field, String fieldName, String message) { 588 return RemotableAttributeError.Builder.create("routetemplate.xmlattribute.error", message).build(); 589 } 590 public RemotableAttributeError generateMissingFieldError(Node field, String fieldName, String message) { 591 return RemotableAttributeError.Builder.create("routetemplate.xmlattribute.required.error", field.getAttributes().getNamedItem("title").getNodeValue()).build(); 592 } 593 }); 594 } catch (XPathExpressionException e) { 595 LOG.error("error in validateRoutingData ", e); 596 throw new RuntimeException("Error trying to find xml content with xpath expression", e); 597 } 598 } 599 600 public List<RemotableAttributeError> validateRuleData(Map paramMap) { 601 this.paramMap = paramMap; 602 try { 603 return validate(getConfigXML(), new String[] { "ALL", "RULE" }, paramMap, new ErrorGenerator<RemotableAttributeError>() { 604 public RemotableAttributeError generateInvalidFieldError(Node field, String fieldName, String message) { 605 return RemotableAttributeError.Builder.create("routetemplate.xmlattribute.error", message).build(); 606 } 607 public RemotableAttributeError generateMissingFieldError(Node field, String fieldName, String message) { 608 return RemotableAttributeError.Builder.create("routetemplate.xmlattribute.required.error", field.getAttributes().getNamedItem("title").getNodeValue()).build(); 609 } 610 }); 611 } catch (XPathExpressionException e) { 612 LOG.error("error in validateRoutingData ", e); 613 throw new RuntimeException("Error trying to find xml content with xpath expression", e); 614 } 615 } 616 617 public void setRequired(boolean required) { 618 this.required = required; 619 } 620 621 public boolean isRequired() { 622 return required; 623 } 624 625 public Element getConfigXML() { 626 try { 627 return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new BufferedReader( 628 new StringReader(extensionDefinition.getConfiguration().get( 629 KewApiConstants.ATTRIBUTE_XML_CONFIG_DATA))))).getDocumentElement(); 630 } catch (Exception e) { 631 String str = extensionDefinition == null ? "null" : extensionDefinition.getName(); 632 LOG.error("error parsing xml data from rule attribute: " + str, e); 633 throw new RuntimeException("error parsing xml data from rule attribute: " + str, e); 634 } 635 } 636 637 // TODO: possibly simplify even further by unifying AttributeError and WorkflowServiceError 638 public List<RemotableAttributeError> validateClientRoutingData() { 639 LOG.debug("validating client routing data"); 640 try { 641 return validate(getConfigXML(), new String[] { "ALL", "RULE" }, getParamMap(), new ErrorGenerator<RemotableAttributeError>() { 642 public RemotableAttributeError generateInvalidFieldError(Node field, String fieldName, String message) { 643 if (org.apache.commons.lang.StringUtils.isEmpty(message)) { 644 message = "invalid field value"; 645 } else { 646 LOG.info("Message: '" + message + "'"); 647 } 648 return RemotableAttributeError.Builder.create(fieldName, message).build(); 649 } 650 public RemotableAttributeError generateMissingFieldError(Node field, String fieldName, String message) { 651 return RemotableAttributeError.Builder.create(fieldName, "Attribute is required; " + message).build(); 652 } 653 }); 654 } catch (XPathExpressionException e) { 655 LOG.error("error in validateClientRoutingData ", e); 656 throw new RuntimeException("Error trying to find xml content with xpath expression", e); 657 } 658 } 659 660 public Map getParamMap() { 661 return paramMap; 662 } 663 664 public void setParamMap(Map paramMap) { 665 this.paramMap = paramMap; 666 } 667 668 /** 669 * @return the evaluateForMissingExtensions 670 */ 671 public boolean isEvaluateForMissingExtensions() { 672 return this.evaluateForMissingExtensions; 673 } 674 675 /** 676 * Sets whether or not to evaluate expressions if the extension corresponding to that expressions is not present on the rule. 677 * The correspondence is made by comparing the name of the field declared on the fieldDef element and the name of the 678 * rule extension key. If this value is set to true then all xpath expressions defined on all fieldDefs will be evaluated 679 * regardless of whether or not the rule has a corresponding extension value. 680 * 681 * <p>By default this is false to preserve backward compatible behavior. 682 */ 683 public void setEvaluateForMissingExtensions(boolean evaluateForMissingExtensions) { 684 this.evaluateForMissingExtensions = evaluateForMissingExtensions; 685 } 686 687 688 }