View Javadoc

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