You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by pm...@apache.org on 2017/01/18 20:22:24 UTC

svn commit: r1779376 - in /jmeter/trunk: src/components/org/apache/jmeter/extractor/ src/components/org/apache/jmeter/extractor/gui/ src/core/org/apache/jmeter/util/ test/src/org/apache/jmeter/extractor/ xdocs/ xdocs/usermanual/

Author: pmouawad
Date: Wed Jan 18 20:22:24 2017
New Revision: 1779376

URL: http://svn.apache.org/viewvc?rev=1779376&view=rev
Log:
Bug 60602 - XPath Extractor : Add Match No. to allow extraction randomly, by index or all matches
Bugzilla Id: 60602

Modified:
    jmeter/trunk/src/components/org/apache/jmeter/extractor/XPathExtractor.java
    jmeter/trunk/src/components/org/apache/jmeter/extractor/gui/XPathExtractorGui.java
    jmeter/trunk/src/core/org/apache/jmeter/util/XPathUtil.java
    jmeter/trunk/test/src/org/apache/jmeter/extractor/TestXPathExtractor.java
    jmeter/trunk/xdocs/changes.xml
    jmeter/trunk/xdocs/usermanual/component_reference.xml

Modified: jmeter/trunk/src/components/org/apache/jmeter/extractor/XPathExtractor.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/extractor/XPathExtractor.java?rev=1779376&r1=1779375&r2=1779376&view=diff
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/extractor/XPathExtractor.java (original)
+++ jmeter/trunk/src/components/org/apache/jmeter/extractor/XPathExtractor.java Wed Jan 18 20:22:24 2017
@@ -32,6 +32,7 @@ import org.apache.jmeter.processor.PostP
 import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.testelement.AbstractScopedTestElement;
 import org.apache.jmeter.testelement.property.BooleanProperty;
+import org.apache.jmeter.testelement.property.IntegerProperty;
 import org.apache.jmeter.threads.JMeterContext;
 import org.apache.jmeter.threads.JMeterVariables;
 import org.apache.jmeter.util.TidyException;
@@ -43,7 +44,6 @@ import org.apache.log.Logger;
 import org.w3c.dom.Document;
 import org.xml.sax.SAXException;
 
-//@see org.apache.jmeter.extractor.TestXPathExtractor for unit tests
 
 /**
  * Extracts text from (X)HTML response using XPath query language
@@ -60,20 +60,18 @@ import org.xml.sax.SAXException;
  * <dt>//head/text()</dt>
  *     <dd>extracts the text content for head node.</dd>
  * </dl>
- */
- /* This file is inspired by RegexExtractor.
- * author <a href="mailto:hpaluch@gitus.cz">Henryk Paluch</a>
- *            of <a href="http://www.gitus.com">Gitus a.s.</a>
- *
- * See Bugzilla: 37183
+  see org.apache.jmeter.extractor.TestXPathExtractor for unit tests
  */
 public class XPathExtractor extends AbstractScopedTestElement implements
         PostProcessor, Serializable {
     private static final Logger log = LoggingManager.getLoggerForClass();
 
-    private static final long serialVersionUID = 240L;
+    private static final long serialVersionUID = 241L;
+    
+    private static final int DEFAULT_VALUE = -1;
+    public static final String DEFAULT_VALUE_AS_STRING = Integer.toString(DEFAULT_VALUE);
 
-    private static final String MATCH_NR = "matchNr"; // $NON-NLS-1$
+    private static final String REF_MATCH_NR    = "matchNr"; // $NON-NLS-1$
 
     //+ JMX file attributes
     private static final String XPATH_QUERY     = "XPathExtractor.xpathQuery"; // $NON-NLS-1$
@@ -88,6 +86,7 @@ public class XPathExtractor extends Abst
     private static final String WHITESPACE      = "XPathExtractor.whitespace"; // $NON-NLS-1$
     private static final String VALIDATE        = "XPathExtractor.validate"; // $NON-NLS-1$
     private static final String FRAGMENT        = "XPathExtractor.fragment"; // $NON-NLS-1$
+    private static final String MATCH_NUMBER    = "XPathExtractor.matchNumber"; // $NON-NLS-1$
     //- JMX file attributes
 
 
@@ -114,7 +113,7 @@ public class XPathExtractor extends Abst
         JMeterVariables vars = context.getVariables();
         String refName = getRefName();
         vars.put(refName, getDefaultValue());
-        final String matchNR = concat(refName,MATCH_NR);
+        final String matchNR = concat(refName,REF_MATCH_NR);
         int prevCount=0; // number of previous matches
         try {
             prevCount=Integer.parseInt(vars.get(matchNR));
@@ -124,6 +123,7 @@ public class XPathExtractor extends Abst
         vars.put(matchNR, "0"); // In case parse fails // $NON-NLS-1$
         vars.remove(concat(refName,"1")); // In case parse fails // $NON-NLS-1$
 
+        int matchNumber = getMatchNumber();
         List<String> matches = new ArrayList<>();
         try{
             if (isScopeVariable()){
@@ -131,7 +131,7 @@ public class XPathExtractor extends Abst
                 if(inputString != null) {
                     if(inputString.length()>0) {
                         Document d =  parseResponse(inputString);
-                        getValuesForXPath(d,getXPathQuery(),matches);
+                        getValuesForXPath(d,getXPathQuery(), matches, matchNumber);
                     }
                 } else {
                     log.warn("No variable '"+getVariableName()+"' found to process by XPathExtractor '"+getName()+"', skipping processing");
@@ -140,7 +140,7 @@ public class XPathExtractor extends Abst
                 List<SampleResult> samples = getSampleList(previousResult);
                 for (SampleResult res : samples) {
                     Document d = parseResponse(res.getResponseDataAsString());
-                    getValuesForXPath(d,getXPathQuery(),matches);
+                    getValuesForXPath(d,getXPathQuery(), matches, matchNumber);
                 }
             }
             final int matchCount = matches.size();
@@ -308,12 +308,13 @@ public class XPathExtractor extends Abst
      * @param d the document
      * @param query the query to execute
      * @param matchStrings list of matched strings (may include nulls)
+     * @param matchNumber int Match Number
      *
      * @throws TransformerException
      */
-    private void getValuesForXPath(Document d,String query, List<String> matchStrings)
+    private void getValuesForXPath(Document d,String query, List<String> matchStrings, int matchNumber)
         throws TransformerException {
-        XPathUtil.putValuesForXPathInList(d, query, matchStrings, getFragment());
+        XPathUtil.putValuesForXPathInList(d, query, matchStrings, getFragment(), matchNumber);
     }
 
     public void setWhitespace(boolean selected) {
@@ -339,4 +340,44 @@ public class XPathExtractor extends Abst
     public boolean isDownloadDTDs() {
         return getPropertyAsBoolean(DOWNLOAD_DTDS, false);
     }
+    
+    /**
+     * Set which Match to use. This can be any positive number, indicating the
+     * exact match to use, or <code>0</code>, which is interpreted as meaning random.
+     *
+     * @param matchNumber The number of the match to be used
+     */
+    public void setMatchNumber(int matchNumber) {
+        setProperty(new IntegerProperty(MATCH_NUMBER, matchNumber));
+    }
+
+    /**
+     * Set which Match to use. This can be any positive number, indicating the
+     * exact match to use, or <code>0</code>, which is interpreted as meaning random.
+     *
+     * @param matchNumber The number of the match to be used
+     */
+    public void setMatchNumber(String matchNumber) {
+        setProperty(MATCH_NUMBER, matchNumber);
+    }
+
+    /**
+     * Return which Match to use. This can be any positive number, indicating the
+     * exact match to use, or <code>0</code>, which is interpreted as meaning random.
+     *
+     * @return matchNumber The number of the match to be used
+     */
+    public int getMatchNumber() {
+        return getPropertyAsInt(MATCH_NUMBER, DEFAULT_VALUE);
+    }
+
+    /**
+     * Return which Match to use. This can be any positive number, indicating the
+     * exact match to use, or <code>0</code>, which is interpreted as meaning random.
+     *
+     * @return matchNumber The number of the match to be used
+     */
+    public String getMatchNumberAsString() {
+        return getPropertyAsString(MATCH_NUMBER, DEFAULT_VALUE_AS_STRING);
+    }
 }

Modified: jmeter/trunk/src/components/org/apache/jmeter/extractor/gui/XPathExtractorGui.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/extractor/gui/XPathExtractorGui.java?rev=1779376&r1=1779375&r2=1779376&view=diff
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/extractor/gui/XPathExtractorGui.java (original)
+++ jmeter/trunk/src/components/org/apache/jmeter/extractor/gui/XPathExtractorGui.java Wed Jan 18 20:22:24 2017
@@ -37,9 +37,6 @@ import org.apache.jorphan.gui.JLabeledTe
 /**
  * GUI for XPathExtractor class.
  */
- /* This file is inspired by RegexExtractor.
- * See Bugzilla: 37183
- */
 public class XPathExtractorGui extends AbstractPostProcessorGui {
 
     private static final long serialVersionUID = 240L;
@@ -50,6 +47,9 @@ public class XPathExtractorGui extends A
     private final JLabeledTextField xpathQueryField =
         new JLabeledTextField(JMeterUtils.getResString("xpath_extractor_query"));//$NON-NLS-1$
 
+    private final JLabeledTextField matchNumberField =
+            new JLabeledTextField(JMeterUtils.getResString("match_num_field"));//$NON-NLS-1$
+
     private final JLabeledTextField refNameField =
         new JLabeledTextField(JMeterUtils.getResString("ref_name_field"));//$NON-NLS-1$
 
@@ -77,6 +77,7 @@ public class XPathExtractorGui extends A
         xpathQueryField.setText(xpe.getXPathQuery());
         defaultField.setText(xpe.getDefaultValue());
         refNameField.setText(xpe.getRefName());
+        matchNumberField.setText(xpe.getMatchNumberAsString());
         getFragment.setSelected(xpe.getFragment());
         xml.configure(xpe);
     }
@@ -97,6 +98,7 @@ public class XPathExtractorGui extends A
             saveScopeSettings(xpath);
             xpath.setDefaultValue(defaultField.getText());
             xpath.setRefName(refNameField.getText());
+            xpath.setMatchNumber(matchNumberField.getText());
             xpath.setXPathQuery(xpathQueryField.getText());
             xpath.setFragment(getFragment.isSelected());
             xml.modifyTestElement(xpath);
@@ -113,6 +115,7 @@ public class XPathExtractorGui extends A
         xpathQueryField.setText(""); // $NON-NLS-1$
         defaultField.setText(""); // $NON-NLS-1$
         refNameField.setText(""); // $NON-NLS-1$
+        matchNumberField.setText(XPathExtractor.DEFAULT_VALUE_AS_STRING); // $NON-NLS-1$
         xml.setDefaultValues();
     }
 
@@ -140,6 +143,8 @@ public class XPathExtractorGui extends A
         resetContraints(gbc);
         addField(panel, xpathQueryField, gbc);
         resetContraints(gbc);
+        addField(panel, matchNumberField, gbc);
+        resetContraints(gbc);
         gbc.weighty = 1;
         addField(panel, defaultField, gbc);
         return panel;

Modified: jmeter/trunk/src/core/org/apache/jmeter/util/XPathUtil.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/util/XPathUtil.java?rev=1779376&r1=1779375&r2=1779376&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/util/XPathUtil.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/util/XPathUtil.java Wed Jan 18 20:22:24 2017
@@ -309,12 +309,34 @@ public class XPathUtil {
     public static void putValuesForXPathInList(Document document, 
             String xPathQuery,
             List<String> matchStrings, boolean fragment) throws TransformerException {
+        putValuesForXPathInList(document, xPathQuery, matchStrings, fragment, -1);
+    }
+    
+    
+    /**
+     * Put in matchStrings results of evaluation
+     * @param document XML document
+     * @param xPathQuery XPath Query
+     * @param matchStrings List of strings that will be filled
+     * @param fragment return fragment
+     * @param matchNumber match number
+     * @throws TransformerException when the internally used xpath engine fails
+     */
+    public static void putValuesForXPathInList(Document document, 
+            String xPathQuery,
+            List<String> matchStrings, boolean fragment, 
+            int matchNumber) throws TransformerException {
         String val = null;
         XObject xObject = XPathAPI.eval(document, xPathQuery, getPrefixResolver(document));
         final int objectType = xObject.getType();
         if (objectType == XObject.CLASS_NODESET) {
             NodeList matches = xObject.nodelist();
             int length = matches.getLength();
+            int indexToMatch = matchNumber;
+            if(matchNumber == 0 && length>0) {
+                indexToMatch = JMeterUtils.getRandomInt(length)+1;
+            } 
+            boolean storeAllValues = matchNumber < 0;
             for (int i = 0 ; i < length; i++) {
                 Node match = matches.item(i);
                 if ( match instanceof Element){
@@ -332,7 +354,9 @@ public class XPathUtil {
                 } else {
                    val = match.getNodeValue();
                 }
-                matchStrings.add(val);
+                if(storeAllValues || indexToMatch == (i+1)) {
+                    matchStrings.add(val);
+                }
             }
         } else if (objectType == XObject.CLASS_NULL
                 || objectType == XObject.CLASS_UNKNOWN

Modified: jmeter/trunk/test/src/org/apache/jmeter/extractor/TestXPathExtractor.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/test/src/org/apache/jmeter/extractor/TestXPathExtractor.java?rev=1779376&r1=1779375&r2=1779376&view=diff
==============================================================================
--- jmeter/trunk/test/src/org/apache/jmeter/extractor/TestXPathExtractor.java (original)
+++ jmeter/trunk/test/src/org/apache/jmeter/extractor/TestXPathExtractor.java Wed Jan 18 20:22:24 2017
@@ -23,10 +23,13 @@ import static org.junit.Assert.assertEqu
 import static org.junit.Assert.assertNull;
 
 import java.io.UnsupportedEncodingException;
+
+import org.apache.commons.lang3.StringUtils;
 import org.apache.jmeter.samplers.SampleResult;
 import org.apache.jmeter.threads.JMeterContext;
 import org.apache.jmeter.threads.JMeterContextService;
 import org.apache.jmeter.threads.JMeterVariables;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -107,6 +110,29 @@ public class TestXPathExtractor {
             assertEquals("two", vars.get(VAL_NAME+"_2"));
             assertNull(vars.get(VAL_NAME+"_3"));
             
+            // Test match 1
+            extractor.setXPathQuery("/book/page");
+            extractor.setMatchNumber(1);
+            extractor.process();
+            assertEquals("one", vars.get(VAL_NAME));
+            assertEquals("1", vars.get(VAL_NAME_NR));
+            assertEquals("one", vars.get(VAL_NAME+"_1"));
+            assertNull(vars.get(VAL_NAME+"_2"));
+            assertNull(vars.get(VAL_NAME+"_3"));
+            
+            // Test match Random
+            extractor.setXPathQuery("/book/page");
+            extractor.setMatchNumber(0);
+            extractor.process();
+            assertEquals("1", vars.get(VAL_NAME_NR));
+            Assert.assertTrue(StringUtils.isNoneEmpty(vars.get(VAL_NAME)));
+            Assert.assertTrue(StringUtils.isNoneEmpty(vars.get(VAL_NAME+"_1")));
+            assertNull(vars.get(VAL_NAME+"_2"));
+            assertNull(vars.get(VAL_NAME+"_3"));
+            
+            // Put back default value
+            extractor.setMatchNumber(-1);
+            
             extractor.setXPathQuery("/book/page[2]");
             extractor.process();
             assertEquals("two", vars.get(VAL_NAME));
@@ -140,6 +166,25 @@ public class TestXPathExtractor {
             extractor.process();
             assertEquals("Default", vars.get(VAL_NAME));
 
+            // No text all matches
+            extractor.setXPathQuery("//a");
+            extractor.process();
+            extractor.setMatchNumber(-1);
+            assertEquals("Default", vars.get(VAL_NAME));
+
+            // No text match second
+            extractor.setXPathQuery("//a");
+            extractor.process();
+            extractor.setMatchNumber(2);
+            assertEquals("Default", vars.get(VAL_NAME));
+
+            // No text match random
+            extractor.setXPathQuery("//a");
+            extractor.process();
+            extractor.setMatchNumber(0);
+            assertEquals("Default", vars.get(VAL_NAME));
+
+            extractor.setMatchNumber(-1);
             // Test fragment
             extractor.setXPathQuery("/book/page[2]");
             extractor.setFragment(true);

Modified: jmeter/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/changes.xml?rev=1779376&r1=1779375&r2=1779376&view=diff
==============================================================================
--- jmeter/trunk/xdocs/changes.xml [utf-8] (original)
+++ jmeter/trunk/xdocs/changes.xml [utf-8] Wed Jan 18 20:22:24 2017
@@ -134,6 +134,7 @@ JMeter now requires Java 8. Ensure you u
     <li><bug>60154</bug>User Parameters GUI: allow rows to be moved up &amp; down in the list. Contributed by Murdecai777 (https://github.com/Murdecai777).</li>
     <li><bug>60507</bug>Added '<code>Or</code>' Function into ResponseAssertion. Based on a contribution from \u5ffb\u9686 (298015902 at qq.com)</li>
     <li><bug>58943</bug>Create a Better Think Time experience. Contributed by Ubik Load Pack (support at ubikloadpack.com)</li>
+    <li><bug>60602</bug>XPath Extractor : Add Match No. to allow extraction randomly, by index or all matches</li>
 </ul>
 
 <h3>Functions</h3>

Modified: jmeter/trunk/xdocs/usermanual/component_reference.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/usermanual/component_reference.xml?rev=1779376&r1=1779375&r2=1779376&view=diff
==============================================================================
--- jmeter/trunk/xdocs/usermanual/component_reference.xml (original)
+++ jmeter/trunk/xdocs/usermanual/component_reference.xml Wed Jan 18 20:22:24 2017
@@ -5605,7 +5605,7 @@ generate the template string, and store
         <property name="Template" required="Yes">The template used to create a string from the matches found.  This is an arbitrary string
         with special elements to refer to groups within the regular expression.  The syntax to refer to a group is: '<code>$1$</code>' to refer to
         group <code>1</code>, '<code>$2$</code>' to refer to group <code>2</code>, etc. <code>$0$</code> refers to whatever the entire expression matches.</property>
-        <property name="Match No." required="Yes">Indicates which match to use.  The regular expression may match multiple times.  
+        <property name="Match No. (0 for Random)" required="Yes">Indicates which match to use.  The regular expression may match multiple times.  
             <ul>
                 <li>Use a value of zero to indicate JMeter should choose a match at random.</li>
                 <li>A positive number N means to select the n<sup>th</sup> match.</li>
@@ -5719,7 +5719,7 @@ extracting the node as text or attribute
             If empty this is the equivalent of <a href="http://jsoup.org/apidocs/org/jsoup/nodes/Element.html#text%28%29">Element#text()</a> function for JSoup if not value is set for attribute.
             <figure width="825" height="275" image="css_extractor_noattr.png">CSS Extractor with no attribute set</figure>
         </property>
-        <property name="Match No." required="Yes">Indicates which match to use.  The CSS/JQuery selector may match multiple times.  
+        <property name="Match No. (0 for Random)" required="Yes">Indicates which match to use.  The CSS/JQuery selector may match multiple times.  
             <ul>
                 <li>Use a value of zero to indicate JMeter should choose a match at random.</li>
                 <li>A positive number <code>N</code> means to select the n<sup>th</sup> match.</li>
@@ -5815,6 +5815,13 @@ extracting the node as text or attribute
     </property>
     <property name="Reference Name" required="Yes">The name of the JMeter variable in which to store the result.</property>
     <property name="XPath Query" required="Yes">Element query in XPath language. Can return more than one match.</property>
+    <property name="Match No. (0 for Random)" required="No">If the XPath Path query leads to many results, you can choose which one(s) to extract as Variables:
+    <ul>
+        <li><code>0</code> : means random</li>
+        <li><code>-1</code> means extract all results (default value), they will be named as <code><em>&lt;variable name&gt;</em>_N</code> (where <code>N</code> goes from 1 to Number of results)</li>
+        <li><code>X</code> : means extract the X<sup>th</sup> result. If this X<sup>th</sup> is greater than number of matches, then nothing is returned. Default value will be used</li>
+    </ul>
+    </property>
     <property name="Default Value" required="">Default value returned when no match found. 
     It is also returned if the node has no value and the fragment option is not selected.</property>
    </properties>
@@ -6051,7 +6058,7 @@ It will allow you to extract in a very e
     <property name="Variable Names" required="Yes">Semi-colon separated names of variables that will contain the results of JSON-PATH expressions (must match number of JSON-PATH expressions)</property>
     <property name="JSON Path Expressions" required="Yes">Semi-colon separated JSON-PATH expressions (must match number of variables)</property>
     <property name="Default Values" required="No">Semi-colon separated default values if JSON-PATH expressions do not return any result(must match number of variables)</property>
-    <property name="Match Numbers" required="No">If the JSON Path query leads to many results, you can choose which one(s) to extract as Variables:
+    <property name="Match No. (0 for Random)" required="No">If the JSON Path query leads to many results, you can choose which one(s) to extract as Variables:
     <ul>
         <li><code>0</code> : means random (Default Value)</li>
         <li><code>-1</code> means extract all results, they will be named as <code><em>&lt;variable name&gt;</em>_N</code> (where <code>N</code> goes from 1 to Number of results)</li>