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 }