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-2007 The Kuali Foundation
 3  
  *
 4  
  *
 5  
  * Licensed under the Educational Community License, Version 2.0 (the "License");
 6  
  * you may not use this file except in compliance with the License.
 7  
  * You may obtain a copy of the License at
 8  
  *
 9  
  * http://www.opensource.org/licenses/ecl2.php
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.kuali.rice.kew.rule.xmlrouting;
 18  
 
 19  
 import org.apache.commons.lang.StringUtils;
 20  
 import org.kuali.rice.core.api.util.ConcreteKeyValue;
 21  
 import org.kuali.rice.core.api.util.KeyValue;
 22  
 import org.kuali.rice.core.api.util.xml.XmlJotter;
 23  
 import org.kuali.rice.kew.api.WorkflowRuntimeException;
 24  
 import org.kuali.rice.kew.attribute.XMLAttributeUtils;
 25  
 import org.kuali.rice.kew.exception.WorkflowServiceErrorImpl;
 26  
 import org.kuali.rice.kew.routeheader.DocumentContent;
 27  
 import org.kuali.rice.kew.rule.RuleExtension;
 28  
 import org.kuali.rice.kew.rule.RuleExtensionValue;
 29  
 import org.kuali.rice.kew.rule.WorkflowAttributeValidationError;
 30  
 import org.kuali.rice.kew.rule.WorkflowAttributeXmlValidator;
 31  
 import org.kuali.rice.kew.rule.bo.RuleAttribute;
 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.dto.WorkflowAttributeDefinitionDTO}
 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 RuleAttribute ruleAttribute;
 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 setRuleAttribute(RuleAttribute ruleAttribute) {
 231  0
         this.ruleAttribute = ruleAttribute;
 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
                         RuleExtension ruleExtension = (RuleExtension) iterator.next();
 339  0
                         if (ruleExtension.getRuleTemplateAttribute().getRuleAttribute().getName().equals(ruleAttribute.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  0
                 String expression = XmlJotter.jotNode(expressionElement);
 370  0
                 if (!StringUtils.isEmpty(expression)) {
 371  0
                     if (LOG.isDebugEnabled()) {
 372  0
                         LOG.debug("Adding global XPath expression: " + expression);
 373  
                     }
 374  0
                     expressionsToEvaluate.add(expression);
 375  
                 }
 376  
             }
 377  0
         } catch (XPathExpressionException e) {
 378  0
             throw new WorkflowRuntimeException("Failed to evalute global XPath expression: " + findGlobalExpressions);
 379  0
         }
 380  0
         return expressionsToEvaluate;
 381  
     }
 382  
 
 383  
     public List getRuleRows() {
 384  0
         if (ruleRows.isEmpty()) {
 385  0
             ruleRows = getRows(getConfigXML(), new String[] { "ALL", "RULE" });
 386  
         }
 387  0
         return ruleRows;
 388  
     }
 389  
 
 390  
     private String getValidationErrorMessage(XPath xpath, Element root, String fieldName) throws XPathExpressionException {
 391  0
         String findErrorMessage = "//routingConfig/" + FIELD_DEF_E + "[@name='" + fieldName + "']/validation/message";
 392  0
         return (String) xpath.evaluate(findErrorMessage, root, XPathConstants.STRING);
 393  
     }
 394  
 
 395  
     private List validate(Element root, String[] types, Map map, ErrorGenerator errorGenerator) throws XPathExpressionException {
 396  0
         List errors = new ArrayList();
 397  0
         XPath xpath = XPathHelper.newXPath();
 398  
 
 399  0
         NodeList nodes = getFields(xpath, root, types);
 400  0
         for (int i = 0; i < nodes.getLength(); i++) {
 401  0
             Node field = nodes.item(i);
 402  0
             NamedNodeMap fieldAttributes = field.getAttributes();
 403  0
             String fieldName = fieldAttributes.getNamedItem("name").getNodeValue();
 404  
 
 405  0
             LOG.debug("evaluating field: " + fieldName);
 406  0
             String findValidation = "//routingConfig/" + FIELD_DEF_E + "[@name='" + fieldName + "']/validation";
 407  
 
 408  0
             Node validationNode = (Node) xpath.evaluate(findValidation, root, XPathConstants.NODE);
 409  0
             boolean fieldIsRequired = false;
 410  0
             if (validationNode != null) {
 411  0
                 NamedNodeMap validationAttributes = validationNode.getAttributes();
 412  0
                 Node reqAttribNode = validationAttributes.getNamedItem("required");
 413  0
                 fieldIsRequired = reqAttribNode != null && "true".equalsIgnoreCase(reqAttribNode.getNodeValue());
 414  
             }
 415  
 
 416  0
             String findRegex = "//routingConfig/" + FIELD_DEF_E + "[@name='" + fieldName + "']/validation/regex";
 417  
 
 418  0
             String regex = null;
 419  0
             Node regexNode = (Node) xpath.evaluate(findRegex, root, XPathConstants.NODE);
 420  
 
 421  0
             if (regexNode != null && regexNode.getFirstChild() != null) {
 422  0
                 regex = regexNode.getFirstChild().getNodeValue();
 423  0
                 if (regex == null) {
 424  0
                     throw new RuntimeException("Null regex text node");
 425  
                 }
 426  
             }/* else {
 427  
                 if (fieldIsRequired) {
 428  
                     fieldIsOnlyRequired = true;
 429  
                     LOG.debug("Setting empty regex to .+ as field is required");
 430  
                     // NOTE: ok, so technically .+ is not the same as checking merely
 431  
                     // for existence, because a field can be extant but "empty"
 432  
                     // however this has no relevance to the user as an empty field
 433  
                     // is for all intents and purposes non-existent (not-filled-in)
 434  
                     // so let's just use this regex to simplify the logic and
 435  
                     // pass everything through a regex check
 436  
                     regex = ".+";
 437  
                 } else {
 438  
                     LOG.debug("Setting empty regex to .* as field is NOT required");
 439  
                     regex = ".*";
 440  
                 }
 441  
             }*/
 442  
 
 443  0
             LOG.debug("regex for field '" + fieldName + "': '" + regex + "'");
 444  
 
 445  0
             String fieldValue = null;
 446  0
             if (map != null) {
 447  0
                 fieldValue = (String) map.get(fieldName);
 448  
             }
 449  
 
 450  0
             LOG.debug("field value: " + fieldValue);
 451  
 
 452  
             // fix up non-existent value for regex purposes only
 453  0
             if (fieldValue == null) {
 454  0
                 fieldValue = "";
 455  
             }
 456  
 
 457  0
             if (regex == null){
 458  0
                 if (fieldIsRequired) {
 459  0
                     if (fieldValue.length() == 0) {
 460  0
                         errors.add(errorGenerator.generateMissingFieldError(field, fieldName, getValidationErrorMessage(xpath, root, fieldName)));
 461  
                     }
 462  
                 }
 463  
             } else {
 464  0
                 if (!Pattern.compile(regex).matcher(fieldValue).matches()) {
 465  0
                     LOG.debug("field value does not match validation regex");
 466  0
                     errors.add(errorGenerator.generateInvalidFieldError(field, fieldName, getValidationErrorMessage(xpath, root, fieldName)));
 467  
                 }
 468  
             }
 469  
         }
 470  0
         return errors;
 471  
     }
 472  
 
 473  
     public List getRoutingDataRows() {
 474  0
         if (routingDataRows.isEmpty()) {
 475  0
             routingDataRows = getRows(getConfigXML(), new String[] { "ALL", "REPORT" });
 476  
         }
 477  0
         return routingDataRows;
 478  
     }
 479  
 
 480  
     public String getDocContent() {
 481  0
         XPath xpath = XPathHelper.newXPath();
 482  0
         final String findDocContent = "//routingConfig/xmlDocumentContent";
 483  
         try {
 484  0
             Node xmlDocumentContent = (Node) xpath.evaluate(findDocContent, getConfigXML(), XPathConstants.NODE);
 485  
 
 486  0
             NodeList nodes = getFields(xpath, getConfigXML(), new String[] { "ALL", "REPORT", "RULE" });
 487  
 //            if (nodes == null || nodes.getLength() == 0) {
 488  
 //                return "";
 489  
 //            }
 490  
 
 491  0
             if (xmlDocumentContent != null && xmlDocumentContent.hasChildNodes()) {
 492  
                 // Custom doc content in the routingConfig xml.
 493  0
                 String documentContent = "";
 494  0
                 NodeList customNodes = xmlDocumentContent.getChildNodes();
 495  0
                 for (int i = 0; i < customNodes.getLength(); i++) {
 496  0
                     Node childNode = customNodes.item(i);
 497  0
                     documentContent += XmlJotter.jotNode(childNode);
 498  
                 }
 499  
 
 500  0
                 for (int i = 0; i < nodes.getLength(); i++) {
 501  0
                     Node field = nodes.item(i);
 502  0
                     NamedNodeMap fieldAttributes = field.getAttributes();
 503  0
                     String fieldName = fieldAttributes.getNamedItem("name").getNodeValue();
 504  0
                     LOG.debug("Replacing field '" + fieldName + "'");
 505  0
                     Map map = getParamMap();
 506  0
                     String fieldValue = (String) map.get(fieldName);
 507  0
                     if (map != null && !org.apache.commons.lang.StringUtils.isEmpty(fieldValue)) {
 508  0
                         LOG.debug("Replacing %" + fieldName + "% with field value: '" + fieldValue + "'");
 509  0
                         documentContent = documentContent.replaceAll("%" + fieldName + "%", fieldValue);
 510  
                     } else {
 511  0
                         LOG.debug("Field map is null or fieldValue is empty");
 512  
                     }
 513  
                 }
 514  0
                 return documentContent;
 515  
             } else {
 516  
                 // Standard doc content if no doc content is found in the routingConfig xml.
 517  0
                 StringBuffer documentContent = new StringBuffer("<xmlRouting>");
 518  0
                 for (int i = 0; i < nodes.getLength(); i++) {
 519  0
                     Node field = nodes.item(i);
 520  0
                     NamedNodeMap fieldAttributes = field.getAttributes();
 521  0
                     String fieldName = fieldAttributes.getNamedItem("name").getNodeValue();
 522  0
                     Map map = getParamMap();
 523  0
                     if (map != null && !org.apache.commons.lang.StringUtils.isEmpty((String) map.get(fieldName))) {
 524  0
                         documentContent.append("<field name=\"");
 525  0
                         documentContent.append(fieldName);
 526  0
                         documentContent.append("\"><value>");
 527  0
                         documentContent.append((String) map.get(fieldName));
 528  0
                         documentContent.append("</value></field>");
 529  
                     }
 530  
                 }
 531  0
                 documentContent.append("</xmlRouting>");
 532  0
                 return documentContent.toString();
 533  
             }
 534  0
         } catch (XPathExpressionException e) {
 535  0
             LOG.error("error in getDocContent ", e);
 536  0
             throw new RuntimeException("Error trying to find xml content with xpath expression", e);
 537  0
         } catch (Exception e) {
 538  0
             LOG.error("error in getDocContent attempting to find xml doc content", e);
 539  0
             throw new RuntimeException("Error trying to get xml doc content.", e);
 540  
         }
 541  
     }
 542  
 
 543  
     public List getRuleExtensionValues() {
 544  0
         List extensionValues = new ArrayList();
 545  
 
 546  0
         XPath xpath = XPathHelper.newXPath();
 547  
         try {
 548  0
             NodeList nodes = getFields(xpath, getConfigXML(), new String[] { "ALL", "RULE" });
 549  0
             for (int i = 0; i < nodes.getLength(); i++) {
 550  0
                 Node field = nodes.item(i);
 551  0
                 NamedNodeMap fieldAttributes = field.getAttributes();
 552  0
                 String fieldName = fieldAttributes.getNamedItem("name").getNodeValue();
 553  0
                 Map map = getParamMap();
 554  0
                 if (map != null && !org.apache.commons.lang.StringUtils.isEmpty((String) map.get(fieldName))) {
 555  0
                     RuleExtensionValue value = new RuleExtensionValue();
 556  0
                     value.setKey(fieldName);
 557  0
                     value.setValue((String) map.get(fieldName));
 558  0
                     extensionValues.add(value);
 559  
                 }
 560  
             }
 561  0
         } catch (XPathExpressionException e) {
 562  0
             LOG.error("error in getRuleExtensionValues ", e);
 563  0
             throw new RuntimeException("Error trying to find xml content with xpath expression", e);
 564  0
         }
 565  0
         return extensionValues;
 566  
     }
 567  
 
 568  
     public List validateRoutingData(Map paramMap) {
 569  0
         this.paramMap = paramMap;
 570  
         try {
 571  0
             return validate(getConfigXML(), new String[] { "ALL", "REPORT" }, paramMap, new ErrorGenerator() {
 572  
                 public Object generateInvalidFieldError(Node field, String fieldName, String message) {
 573  0
                     return new WorkflowAttributeValidationError("routetemplate.xmlattribute.error", message);
 574  
                 }
 575  
                 public Object generateMissingFieldError(Node field, String fieldName, String message) {
 576  0
                     return new WorkflowAttributeValidationError("routetemplate.xmlattribute.required.error", field.getAttributes().getNamedItem("title").getNodeValue());
 577  
                 }
 578  
             });
 579  0
         } catch (XPathExpressionException e) {
 580  0
             LOG.error("error in validateRoutingData ", e);
 581  0
             throw new RuntimeException("Error trying to find xml content with xpath expression", e);
 582  
         }
 583  
     }
 584  
 
 585  
     public List validateRuleData(Map paramMap) {
 586  0
         this.paramMap = paramMap;
 587  
         try {
 588  0
             return validate(getConfigXML(), new String[] { "ALL", "RULE" }, paramMap, new ErrorGenerator() {
 589  
                 public Object generateInvalidFieldError(Node field, String fieldName, String message) {
 590  0
                     return new WorkflowServiceErrorImpl("Xml attribute error.", "routetemplate.xmlattribute.error", message);
 591  
                 }
 592  
                 public Object generateMissingFieldError(Node field, String fieldName, String message) {
 593  0
                     return new WorkflowServiceErrorImpl("Xml attribute error.", "routetemplate.xmlattribute.required.error", field.getAttributes().getNamedItem("title").getNodeValue());
 594  
                 }
 595  
             });
 596  0
         } catch (XPathExpressionException e) {
 597  0
             LOG.error("error in validateRoutingData ", e);
 598  0
             throw new RuntimeException("Error trying to find xml content with xpath expression", e);
 599  
         }
 600  
     }
 601  
 
 602  
     public void setRequired(boolean required) {
 603  0
         this.required = required;
 604  0
     }
 605  
 
 606  
     public boolean isRequired() {
 607  0
         return required;
 608  
     }
 609  
 
 610  
     public Element getConfigXML() {
 611  
         try {
 612  0
             return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new BufferedReader(new StringReader(ruleAttribute.getXmlConfigData())))).getDocumentElement();
 613  0
         } catch (Exception e) {
 614  0
             String str = ruleAttribute == null ? "null" : ruleAttribute.getName();
 615  0
             LOG.error("error parsing xml data from rule attribute: " + str, e);
 616  0
             throw new RuntimeException("error parsing xml data from rule attribute: " + str, e);
 617  
         }
 618  
     }
 619  
 
 620  
     // TODO: possibly simplify this even further by making WorkflowAttributeValidationError a WorkflowServiceError, and
 621  
     // dispense with custom error generators...
 622  
     public List validateClientRoutingData() {
 623  0
         LOG.debug("validating client routing data");
 624  
         try {
 625  0
             return validate(getConfigXML(), new String[] { "ALL", "RULE" }, getParamMap(), new ErrorGenerator() {
 626  
                 public Object generateInvalidFieldError(Node field, String fieldName, String message) {
 627  0
                     if (org.apache.commons.lang.StringUtils.isEmpty(message)) {
 628  0
                         message = "invalid field value";
 629  
                     } else {
 630  0
                         LOG.info("Message: '" + message + "'");
 631  
                     }
 632  0
                     return new WorkflowAttributeValidationError(fieldName, message);
 633  
                 }
 634  
                 public Object generateMissingFieldError(Node field, String fieldName, String message) {
 635  0
                     return new WorkflowAttributeValidationError(fieldName, "Attribute is required; " + message);
 636  
                 }
 637  
             });
 638  0
         } catch (XPathExpressionException e) {
 639  0
             LOG.error("error in validateClientRoutingData ", e);
 640  0
             throw new RuntimeException("Error trying to find xml content with xpath expression", e);
 641  
         }
 642  
     }
 643  
 
 644  
     public Map getParamMap() {
 645  0
         return paramMap;
 646  
     }
 647  
 
 648  
     public void setParamMap(Map paramMap) {
 649  0
         this.paramMap = paramMap;
 650  0
     }
 651  
 
 652  
     /**
 653  
      * @return the evaluateForMissingExtensions
 654  
      */
 655  
     public boolean isEvaluateForMissingExtensions() {
 656  0
         return this.evaluateForMissingExtensions;
 657  
     }
 658  
 
 659  
     /**
 660  
      * Sets whether or not to evaluate expressions if the extension corresponding to that expressions is not present on the rule.
 661  
      * The correspondence is made by comparing the name of the field declared on the fieldDef element and the name of the
 662  
      * rule extension key.  If this value is set to true then all xpath expressions defined on all fieldDefs will be evaluated
 663  
      * regardless of whether or not the rule has a corresponding extension value.
 664  
      *
 665  
      * <p>By default this is false to preserve backward compatible behavior.
 666  
      */
 667  
     public void setEvaluateForMissingExtensions(boolean evaluateForMissingExtensions) {
 668  0
         this.evaluateForMissingExtensions = evaluateForMissingExtensions;
 669  0
     }
 670  
 
 671  
 
 672  
 }