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