Coverage Report - org.apache.commons.latka.validators.XPathValidator
 
Classes in this File Line Coverage Branch Coverage Complexity
XPathValidator
0%
0/110
0%
0/42
4.083
 
 1  
 /*
 2  
  * Copyright 1999-2002,2004 The Apache Software Foundation.
 3  
  * 
 4  
  * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
 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  
 
 17  
 package org.apache.commons.latka.validators;
 18  
 
 19  
 import java.io.IOException;
 20  
 
 21  
 import org.apache.commons.latka.ValidationException;
 22  
 import org.apache.commons.latka.http.Response;
 23  
 
 24  
 import org.jdom.Document;
 25  
 import org.jdom.Element;
 26  
 import org.jdom.JDOMException;
 27  
 import org.jdom.input.SAXBuilder;
 28  
 import org.jaxen.jdom.JDOMXPath;
 29  
 import org.jaxen.JaxenException;
 30  
 
 31  
 /**
 32  
  * An XPath validator.
 33  
  *
 34  
  * <p>Use is of the form:</p>
 35  
  * <p>&lt;xpath select="..." [ value="..." ] [ cond="true | false" ] /&gt;</p>
 36  
  * <p>Where :</p>
 37  
  * <ul>
 38  
  *  <li><code>select</code> is an XPath expression, designed to match a node in
 39  
  *  the XML body of the response.</li>
 40  
  *  <li><code>value</code> is an option value which the string value of the
 41  
  *  selected node should match.</li>
 42  
  *  <li><code>cond</code> is an optional boolean value, indicating
 43  
  *  whether the test logic is to be inverted. Defaults to
 44  
  *  <code>true</code>.</li>
 45  
  * </ul>
 46  
  *
 47  
  * </p>
 48  
  * <p>
 49  
  * If the user has specified a {@link #setValue value}, then the XPath
 50  
  * expression is expected to evaluate to a text or attribute node (or other
 51  
  * textual 'leaf' nodes), in which case the selected value must match that
 52  
  * specified.
 53  
  * </p>
 54  
  * <p>
 55  
  * If no value is specified, then the XPath expression will be used to check for
 56  
  * the <em>existence</em> of a node.
 57  
  * </p>
 58  
  * <p>
 59  
  * If the XPath expression evaluates to a boolean condition (eg
 60  
  * &lt;xpath select="foo/bar='baz'"/&gt;), then the condition will be evaluated and will
 61  
  * result in the test passing or failing. Equivalently, an expression may be
 62  
  * specified, and <code>value</code> used to require a specific value (eg
 63  
  * &lt;xpath select="/foo/bar" value="baz"/&gt;).
 64  
  * </p>
 65  
  * <p>
 66  
  * Finally, setting <code>cond="false"</code> negates the sense of the
 67  
  * test, allowing one to test for the <em>nonexistence</em> or
 68  
  * <em>inequality</em> of nodes and values.
 69  
  * </p>
 70  
  *
 71  
  * @author <a href="mailto:jefft@apache.org">Jeff Turner</a>
 72  
  * @author dIon Gillard
 73  
  * @since 6 January, 2001
 74  
  * @version $Id: XPathValidator.java 155424 2005-02-26 13:09:29Z dirkv $
 75  
  */
 76  
 public class XPathValidator extends BaseConditionalValidator {
 77  
 
 78  
     // General notes:
 79  
     // - It started out simple, honest.. =)
 80  
     // --------------------------------------------------------------- Attributes
 81  
 
 82  0
     protected String _select = null;
 83  0
     protected String _value = null;
 84  
     // need the last XPath result for exception generation
 85  0
     protected Object _lastSelected = null;
 86  
 
 87  
     // ------------------------------------------------------------- Constructors
 88  
 
 89  
     public XPathValidator() {
 90  0
         this(null,null,true,null);
 91  0
     }
 92  
 
 93  
     public XPathValidator(String label) {
 94  0
         this(label,null,true,null);
 95  0
     }
 96  
 
 97  
     public XPathValidator(String label, String select, boolean cond, String value) {
 98  0
         super(label,cond);
 99  0
         _select = select;
 100  0
         _value = value;
 101  0
     }
 102  
 
 103  
     // ------------------------------------------------------------------ Public Methods
 104  
 
 105  
     public void setSelect(String select) {
 106  0
         _select = select;
 107  0
     }
 108  
 
 109  
     public void setValue(String value) {
 110  0
         _value = value;
 111  0
     }
 112  
 
 113  
     public boolean assertTrue(Response response)
 114  
     throws ValidationException {
 115  
 
 116  0
         JDOMXPath xpath = getJDOMXPath(_select); // compile the XPath expression
 117  0
         Document doc = getDocument(response); // retrieve the XML Document to process
 118  0
         Object selected = getSelectedNode(xpath, doc); // Apply the XPath to retrieve a node
 119  0
         _lastSelected = selected;
 120  
 
 121  0
         if (selected == null) {
 122  0
             return false;
 123  
         }
 124  
 
 125  
         // Now the fun begins, where we see if our selected node meets the criteria.
 126  
         // There are two factors:
 127  
         // 
 128  
         // 1) What type of object did the XPath expression return?
 129  
         // 2) If _value is specified, ie if we're testing for _value_ or _existence_
 130  
 
 131  0
         if (selected instanceof Boolean) {
 132  
             // Eg, we had an expression /foo = 'bar'
 133  0
             _log.debug("Boolean XPath expression evaluated to "+selected);
 134  0
             if (_value != null) {
 135  0
                 _log.warn("Ignoring unused value '"+_value+"'.");
 136  
             }
 137  0
             boolean matched = ((Boolean)selected).booleanValue();
 138  
 
 139  0
             return matched;
 140  
 
 141  0
         } else if (selected instanceof String) {
 142  0
             _log.debug("XPath selected string '"+selected+"'.");
 143  0
             if (_value != null) {
 144  0
                 boolean matched = selected.equals(_value);
 145  
 
 146  0
                 return matched;
 147  
             } else {
 148  
                 // otherwise we only test if the node is meant to exist
 149  0
                 return true;
 150  
             }
 151  0
         } else if (selected instanceof Element) {
 152  0
             if (_log.isDebugEnabled()) {
 153  0
                 _log.debug("XPath matched element: ");
 154  0
                 _log.debug(printElement((Element)selected));
 155  
             }
 156  0
             if (_value != null) {
 157  0
                 _log.warn("Ignoring unused value '"+_value+"'.");
 158  
             }
 159  
 
 160  
             // otherwise we only test if the node is meant to exist
 161  0
             return true;
 162  
         } else {
 163  
             // Otherwise Jaxen is returning something odd
 164  0
             if (_value != null) {
 165  
                 // Hope that .equals() does a sensible comparison
 166  0
                 boolean matched = selected.equals(_value);
 167  
 
 168  0
                 return matched;
 169  
 
 170  
             } else {
 171  0
                 _log.warn("Selected unknown type "+selected.getClass().getName());
 172  
                 // only test if the node (whatever it is) is meant to exist
 173  0
                 return true;
 174  
             }
 175  
         }
 176  
     }
 177  
 
 178  
     public String generateBareExceptionMessage() {
 179  
 
 180  0
         if (_lastSelected == null) {
 181  0
             return " THAT BOOLEAN XPATH '"+_select+"' WOULD SELECT SOME NODE.";
 182  
         }
 183  
 
 184  0
         if (_lastSelected instanceof Boolean) {
 185  0
             return " THAT BOOLEAN XPATH '"+_select+"' WOULD RETURN '" + getCondition() + "'.";
 186  0
         } else if (_lastSelected instanceof String) {
 187  0
             if (_value != null) {
 188  0
                 StringBuffer buf = new StringBuffer();
 189  0
                 buf.append(" THAT XPATH '");
 190  0
                 buf.append(_select);
 191  0
                 buf.append("' WOULD SELECT '");
 192  0
                 buf.append(_value);
 193  0
                 buf.append("', RECEIVED '");
 194  0
                 buf.append(_lastSelected);
 195  0
                 buf.append("'.");
 196  0
                 return buf.toString();
 197  
             } else {
 198  
                 // otherwise we only test if the node is meant to exist
 199  0
                 return " THAT XPATH '" + _select + "' WOULD SELECT SOMETHING.";
 200  
             }
 201  0
         } else if (_lastSelected instanceof Element) {
 202  
                 // otherwise we only test if the node is meant to exist
 203  0
                 return " THAT XPATH '" + _select + "' WOULD SELECT SOMETHING.";
 204  
         } else {
 205  
             // Otherwise Jaxen is returning something odd
 206  0
             if (_value != null) {
 207  0
                 return " THAT XPATH EXPRESSION '"+_select+"' WOULD RETURN '" + _value +
 208  
                        "', RETURNED UNKNOWN TYPE "+_lastSelected.getClass().getName()+ ".";
 209  
             } else {
 210  0
                 _log.warn("Selected unknown type "+_lastSelected.getClass().getName());
 211  
                 // only test if the node (whatever it is) is meant to exist
 212  0
                 return " THAT XPATH '" + _select + "' WOULD SELECT SOMETHING.";
 213  
             }
 214  
         }
 215  
     }
 216  
 
 217  
     // ------------------------------------------------------------------ Private Methods
 218  
 
 219  
     /**
 220  
      * Creates a Jaxen <code>JDOMXPath</code> for a given XPath expression.
 221  
      * @param xpathExpr The XPath expression
 222  
      * @return A non-null Jaxen <code>JDOMXPath</code> object.
 223  
      * @throws ValidationException if <code>xpathExpr</code> was invalid.
 224  
      */
 225  
     private JDOMXPath getJDOMXPath(final String xpathExpr)
 226  
     throws ValidationException
 227  
     {
 228  0
         JDOMXPath xpath = null;
 229  
         try {
 230  0
             xpath = new JDOMXPath(xpathExpr);
 231  0
         } catch (JaxenException e) {
 232  0
             fail("Couldn't compile JDOMXPath xpathExpr "+xpathExpr+": "+e.toString());
 233  0
         }
 234  
 
 235  0
         if (xpath == null) { // this should never happen
 236  0
             fail("Null compiled XPath object");
 237  
         }
 238  
 
 239  0
         if (_log.isDebugEnabled()) {
 240  0
             _log.debug("Using XPath expression: "+xpathExpr);
 241  
         }
 242  0
         return xpath;
 243  
     }
 244  
 
 245  
     /**
 246  
      * Creates a <code>Document</code> from the Response.
 247  
      * @param response The (usu. HTTP) Reponse object presumably containing an XML
 248  
      * response body.
 249  
      * @return A non-null <code>Document</code> representing the response body.
 250  
      * @throws ValidationException if the Response object's body did not contain
 251  
      * well-formed XML.
 252  
      */
 253  
     private Document getDocument(Response response)
 254  
     throws ValidationException
 255  
     {
 256  0
         Document doc = null;
 257  0
         SAXBuilder builder = new SAXBuilder();
 258  
         try {
 259  0
             doc = builder.build(response.getStream());
 260  0
         } catch (Exception e) {
 261  0
             if (e instanceof IOException || e instanceof JDOMException) {
 262  0
                 fail(e.toString());
 263  
             } else {
 264  0
                 fail("Unknown exception caught: " + e.toString());
 265  
             }
 266  0
         }
 267  0
         if (doc == null) { // this should never happen
 268  0
             fail("Null document");
 269  
         }
 270  0
         if (_log.isDebugEnabled()) {
 271  0
             _log.debug("Processing doc: "+printDoc(doc));
 272  
         }
 273  0
         return doc;
 274  
     }
 275  
 
 276  
     /**
 277  
      * Apply a compiled XPath expression to an XML Document, and return the
 278  
      * selected node. 
 279  
      * @param xpath The compiled Jaxen <code>XPath</code> object
 280  
      * @param doc The <code>Document</code> object containing the XML.
 281  
      * @return An object returned from Jaxen, or null if there was no match. This may be:
 282  
      * <ul>
 283  
      *  <li>A String, if the expression selected an element with a text node child</li>
 284  
      *  <li>An <code>Element</code></li>
 285  
      *  <li>A <code>java.lang.Boolean</code>, if the XPath expression is a
 286  
      *  statement (eg /foo/bar='content')</li>
 287  
      *  <li>Anything else the Jaxen author deemed useful; ie don't assume anything</li>
 288  
      * </ul>
 289  
      */
 290  
     private Object getSelectedNode(JDOMXPath xpath, Document doc)
 291  
     throws ValidationException
 292  
     {
 293  0
         Object selected = null;
 294  
         try {
 295  0
             selected = xpath.selectSingleNode(doc);
 296  0
         } catch (JaxenException e) {
 297  0
             fail("XPath expression '"+_select+"' didn't match any node. "+e.toString());
 298  0
         }
 299  
 
 300  0
         return selected;
 301  
     }
 302  
 
 303  
     /**
 304  
      * Utility method for returning an XML rendition of a <code>Document</code>.
 305  
      * @param doc The Document to print
 306  
      * @return A String of XML representing the document.
 307  
      */
 308  
     private String printDoc(final Document doc) {
 309  0
         java.io.StringWriter sw = new java.io.StringWriter();
 310  
         try {
 311  0
             new org.jdom.output.XMLOutputter().output(doc, sw);
 312  0
         } catch (java.io.IOException ioe) {
 313  0
             _log.error("Could not print XML document.", ioe);
 314  0
         }
 315  0
         return sw.toString();
 316  
     }
 317  
 
 318  
     /**
 319  
      * Utility method for returning an XML rendition of a <code>Element</code>.
 320  
      * @param elem an <code>Element</code> to print.
 321  
      * @return A String of XML representing the element.
 322  
      */
 323  
     private String printElement(final Element elem) {
 324  0
         java.io.StringWriter sw = new java.io.StringWriter();
 325  0
         Element clone = (Element)((Element)elem).clone();
 326  0
         org.jdom.output.XMLOutputter xmlOut = new org.jdom.output.XMLOutputter();
 327  
         try {
 328  0
             xmlOut.output(new org.jdom.Document(clone), sw);
 329  0
         } catch (java.io.IOException ioe) {
 330  0
             _log.error("Could not print XML element.", ioe);
 331  0
         }
 332  0
         return sw.toString();
 333  
     }
 334  
 }