Coverage Report - org.kuali.rice.kew.role.XPathQualifierResolver
 
Classes in this File Line Coverage Branch Coverage Complexity
XPathQualifierResolver
0%
0/71
0%
0/34
3.7
XPathQualifierResolver$ResolverConfig
0%
0/17
0%
0/2
3.7
 
 1  
 /*
 2  
  * Copyright 2007-2008 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.role;
 17  
 
 18  
 import org.apache.commons.lang.StringUtils;
 19  
 import org.kuali.rice.core.api.exception.RiceRuntimeException;
 20  
 import org.kuali.rice.core.util.AttributeSet;
 21  
 import org.kuali.rice.core.util.xml.XmlJotter;
 22  
 import org.kuali.rice.kew.engine.RouteContext;
 23  
 import org.kuali.rice.kew.rule.XmlConfiguredAttribute;
 24  
 import org.kuali.rice.kew.rule.bo.RuleAttribute;
 25  
 import org.kuali.rice.kew.rule.xmlrouting.XPathHelper;
 26  
 import org.w3c.dom.Document;
 27  
 import org.w3c.dom.Element;
 28  
 import org.w3c.dom.Node;
 29  
 import org.w3c.dom.NodeList;
 30  
 import org.xml.sax.InputSource;
 31  
 
 32  
 import javax.xml.xpath.XPath;
 33  
 import javax.xml.xpath.XPathConstants;
 34  
 import javax.xml.xpath.XPathExpressionException;
 35  
 import java.io.StringReader;
 36  
 import java.util.ArrayList;
 37  
 import java.util.HashMap;
 38  
 import java.util.List;
 39  
 import java.util.Map;
 40  
 
 41  
 /**
 42  
  * Resolves qualifiers based on XPath configuration in the resolver's attribute.
 43  
  * 
 44  
  * <p>An example of the xml processed by this attribute follows:
 45  
  * 
 46  
  * <p><pre>
 47  
  * <resolverConfig>
 48  
  *   <baseXPathExpression>/xmlData/chartOrg</baseXPathExpression>
 49  
  *   <qualifier name="chart">
 50  
  *     <xPathExpression>./chart</xPathExpression>
 51  
  *   </qualifier>
 52  
  *   <qualifier name="org">
 53  
  *     <xPathExpression>./org</xPathExpression>
 54  
  *   </qualifier>
 55  
  * </resolverConfig>
 56  
  * </pre>
 57  
  * 
 58  
  * <p>There are 2 different types of qualifier resolvers, those that resolve compound
 59  
  * attribute sets and those that resolve simple attribute sets.  A simple attribute
 60  
  * set is one which includes only a single "qualifier" specification.  The example above
 61  
  * is compound because it includes both chart and org.
 62  
  * 
 63  
  * <p>When dealing with compound attribute sets, the baseXPathExpression is used to
 64  
  * define grouping for these compound sets.  It is therefore required that inside each
 65  
  * resulting element retrieved from the baseXPathExpression, there is only a single instance
 66  
  * of each qualifier.  If this is not the case, an error will be thrown.  For the example
 67  
  * above, the following XML would be evaluated successfully:
 68  
  * 
 69  
  * <p><pre>
 70  
  * <xmlData>
 71  
  *   <chartOrg>
 72  
  *     <chart>BL</chart>
 73  
  *     <org>BUS</org>
 74  
  *   </chartOrg>
 75  
  *   <chartOrg>
 76  
  *     <chart>IN</chart>
 77  
  *     <org>MED</org>
 78  
  *   </chartOrg>
 79  
  * </xmlData>
 80  
  * </pre>
 81  
  * 
 82  
  * <p>This would return 2 attributes sets, each with a chart and org in it.  The following
 83  
  * XML would cause the XPathQualifierResolver to throw an exception during processing.
 84  
  * 
 85  
  * <p><pre>
 86  
  * <xmlData>
 87  
  *   <chartOrg>
 88  
  *     <chart>BL</chart>
 89  
  *     <org>BUS</org>
 90  
  *     <chart>IN</chart>
 91  
  *     <org>MED</org>
 92  
  *   </chartOrg>
 93  
  * </xmlData>
 94  
  * </pre>
 95  
  * 
 96  
  * <p>In this case the resolver has no knowledge of how to group chart and org together.
 97  
  * What follows is an example of a resolver using a simple attribute set:
 98  
  * 
 99  
  * <p><pre>
 100  
  * <resolverConfig>
 101  
  *   <baseXPathExpression>/xmlData/accountNumbers</baseXPathExpression>
 102  
  *   <qualifier name="accountNumber">
 103  
  *     <xPathExpression>./accountNumber</xPathExpression>
 104  
  *   </qualifier>
 105  
  * </resolverConfig>
 106  
  * </pre>
 107  
  * 
 108  
  * <p>In this example, the following XML would return a List containing an AttributeSet
 109  
  * for each account number when resolved.
 110  
  * 
 111  
  * <p><pre>
 112  
  * <xmlData>
 113  
  *   <accountNumbers>
 114  
  *     <accountNumber>12345</accountNumber>
 115  
  *     <accountNumber>54321</accountNumber>
 116  
  *     <accountNumber>102030</accountNumber>
 117  
  *     <accountNumber>302010</accountNumber>
 118  
  *   </accountNumbers>
 119  
  * </xmlData>
 120  
  * 
 121  
  * <p>The baseXPathExpression is optional and defaults to the root of the document if not specified.
 122  
  * 
 123  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 124  
  */
 125  0
 public class XPathQualifierResolver implements QualifierResolver, XmlConfiguredAttribute {
 126  0
     private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(XPathQualifierResolver.class);
 127  
 
 128  
         private RuleAttribute ruleAttribute;
 129  
         
 130  
         public List<AttributeSet> resolve(RouteContext context) {
 131  0
                         ResolverConfig config = parseResolverConfig();
 132  0
                         Document xmlContent = context.getDocumentContent().getDocument();
 133  0
                         XPath xPath = XPathHelper.newXPath();
 134  0
                         boolean isCompoundAttributeSet = config.getExpressionMap().size() > 1;
 135  
                         try {
 136  0
                                 List<AttributeSet> attributeSets = new ArrayList<AttributeSet>();
 137  0
                                 NodeList baseElements = (NodeList)xPath.evaluate(config.getBaseXPathExpression(), xmlContent, XPathConstants.NODESET);
 138  0
                                 if (LOG.isDebugEnabled()) {
 139  0
                                         LOG.debug("Found " + baseElements.getLength() + " baseElements to parse for AttributeSets using document XML:" + XmlJotter.jotDocument(xmlContent));
 140  
                                 }
 141  0
                                 for (int index = 0; index < baseElements.getLength(); index++) {
 142  0
                                         Node baseNode = baseElements.item(index);
 143  0
                                         if (isCompoundAttributeSet) {
 144  0
                                                 handleCompoundAttributeSet(baseNode, attributeSets, config, xPath);
 145  
                                         } else {
 146  0
                                                 handleSimpleAttributeSet(baseNode, attributeSets, config, xPath);
 147  
                                         }
 148  
                                 }
 149  0
                                 return attributeSets;
 150  0
                         } catch (XPathExpressionException e) {
 151  0
                                 throw new RiceRuntimeException("Encountered an issue executing XPath.", e);
 152  
                         }
 153  
         }
 154  
         
 155  
         protected void handleCompoundAttributeSet(Node baseNode, List<AttributeSet> attributeSets, ResolverConfig config, XPath xPath) throws XPathExpressionException {
 156  0
                 AttributeSet attributeSet = new AttributeSet();
 157  0
                 for (String attributeName : config.getExpressionMap().keySet()) {
 158  0
                         String xPathExpression = config.getExpressionMap().get(attributeName);
 159  0
                         NodeList attributes = (NodeList)xPath.evaluate(xPathExpression, baseNode, XPathConstants.NODESET);
 160  0
                         if (attributes.getLength() > 1) {
 161  0
                                 throw new RiceRuntimeException("Found more than more XPath result for an attribute in a compound attribute set for attribute: " + attributeName + " with expression " + xPathExpression);
 162  0
                         } else if (attributes.getLength() != 0) {
 163  0
                                 String attributeValue = ((Element)attributes.item(0)).getTextContent();
 164  0
                                 if (LOG.isDebugEnabled()) {
 165  0
                                         LOG.debug("Adding values to compound AttributeSet: " + attributeName + "::" + attributeValue);
 166  
                                 }
 167  0
                                 attributeSet.put(attributeName, attributeValue);
 168  
                         }
 169  0
                 }
 170  0
                 attributeSets.add(attributeSet);
 171  0
         }
 172  
         
 173  
         protected void handleSimpleAttributeSet(Node baseNode, List<AttributeSet> attributeSets, ResolverConfig config, XPath xPath) throws XPathExpressionException {
 174  0
                 String attributeName = config.getExpressionMap().keySet().iterator().next();
 175  0
                 String xPathExpression = config.getExpressionMap().get(attributeName);
 176  0
                 NodeList attributes = (NodeList)xPath.evaluate(xPathExpression, baseNode, XPathConstants.NODESET);
 177  0
                 for (int index = 0; index < attributes.getLength(); index++) {
 178  0
                         Element attributeElement = (Element)attributes.item(index);
 179  0
                         AttributeSet attributeSet = new AttributeSet();
 180  0
                         String attributeValue = attributeElement.getTextContent();
 181  0
                         if (LOG.isDebugEnabled()) {
 182  0
                                 LOG.debug("Adding values to simple AttributeSet: " + attributeName + "::" + attributeValue);
 183  
                         }
 184  0
                         attributeSet.put(attributeName, attributeValue);
 185  0
                         attributeSets.add(attributeSet);
 186  
                 }
 187  0
         }
 188  
 
 189  
         public void setRuleAttribute(RuleAttribute ruleAttribute) {
 190  0
                 this.ruleAttribute = ruleAttribute;
 191  0
         }
 192  
         
 193  
         protected ResolverConfig parseResolverConfig() {
 194  0
                 if (ruleAttribute == null) {
 195  0
                         throw new RiceRuntimeException("Failed to locate a RuleAttribute for the given XPathQualifierResolver");
 196  
                 }
 197  
                 try {
 198  0
                         ResolverConfig resolverConfig = new ResolverConfig();
 199  0
                         String xmlConfig = ruleAttribute.getXmlConfigData();
 200  0
                         XPath xPath = XPathHelper.newXPath();
 201  0
                         String baseExpression = xPath.evaluate("//resolverConfig/baseXPathExpression", new InputSource(new StringReader(xmlConfig)));
 202  0
                         if (!StringUtils.isEmpty(baseExpression)) {
 203  0
                                 resolverConfig.setBaseXPathExpression(baseExpression);
 204  
                         }
 205  0
                         NodeList qualifiers = (NodeList)xPath.evaluate("//resolverConfig/qualifier", new InputSource(new StringReader(xmlConfig)), XPathConstants.NODESET);
 206  0
                         if (qualifiers == null || qualifiers.getLength() == 0) {
 207  0
                                 throw new RiceRuntimeException("Invalid qualifier resolver configuration.  Must contain at least one qualifier!");
 208  
                         }
 209  0
                         for (int index = 0; index < qualifiers.getLength(); index++) {
 210  0
                                 Element qualifierElement = (Element)qualifiers.item(index);
 211  0
                                 String name = qualifierElement.getAttribute("name");
 212  0
                                 NodeList expressions = qualifierElement.getElementsByTagName("xPathExpression");
 213  0
                                 if (expressions.getLength() != 1) {
 214  0
                                         throw new RiceRuntimeException("There should only be a single xPathExpression per qualifier");
 215  
                                 }
 216  0
                                 Element expressionElement = (Element)expressions.item(0);
 217  0
                                 resolverConfig.getExpressionMap().put(name, expressionElement.getTextContent());
 218  
                         }
 219  0
                         if (LOG.isDebugEnabled()) {
 220  0
                                 LOG.debug("Using Resolver Config Settings: " + resolverConfig.toString());
 221  
                         }
 222  0
                         return resolverConfig;
 223  0
                 } catch (XPathExpressionException e) {
 224  0
                         throw new RiceRuntimeException("Encountered an error parsing resolver config.", e);
 225  
                 }
 226  
         }
 227  
         
 228  0
         class ResolverConfig {
 229  0
                 private String baseXPathExpression = "/";
 230  0
                 private Map<String, String> expressionMap = new HashMap<String, String>();
 231  
                 public String getBaseXPathExpression() {
 232  0
                         return this.baseXPathExpression;
 233  
                 }
 234  
                 public void setBaseXPathExpression(String baseXPathExpression) {
 235  0
                         this.baseXPathExpression = baseXPathExpression;
 236  0
                 }
 237  
                 public Map<String, String> getExpressionMap() {
 238  0
                         return this.expressionMap;
 239  
                 }
 240  
                 public void setExpressionMap(Map<String, String> expressionMap) {
 241  0
                         this.expressionMap = expressionMap;
 242  0
                 }
 243  
                 @Override
 244  
                 public String toString() {
 245  0
                         StringBuffer sb = new StringBuffer();
 246  0
                         sb.append(  '\n' );
 247  0
                         sb.append("ResolverConfig Parameters\n");
 248  0
                         sb.append( "      baseXPathExpression: " + baseXPathExpression + "\n" );
 249  0
                         sb.append( "      expressionMap:\n" );
 250  0
                         for (Map.Entry<String, String> entry : expressionMap.entrySet()) {
 251  0
                                 sb.append( "            " + entry.getKey() + ": " + entry.getValue() + "\n" );
 252  
                         }
 253  0
                         return sb.toString();
 254  
                 }
 255  
         }
 256  
 
 257  
 }