Author: pmouawad Date: Sun Jun 2 10:51:23 2019 New Revision: 1860513 URL: http://svn.apache.org/viewvc?rev=1860513&view=rev Log: Bug 62787 - Add XPath 2 Assertion
Contributed by UbikLoadPack (https://ubikloadpack.com) This closes #459 Bugzilla Id: 62787 Added: jmeter/trunk/src/components/org/apache/jmeter/assertions/XPath2Assertion.java (with props) jmeter/trunk/src/components/org/apache/jmeter/assertions/gui/XPath2AssertionGui.java (with props) jmeter/trunk/src/components/org/apache/jmeter/assertions/gui/XPath2Panel.java (with props) jmeter/trunk/src/core/org/apache/jmeter/util/PropertiesBasedPrefixResolverForXpath2.java (with props) jmeter/trunk/test/src/org/apache/jmeter/assertions/XPath2AssertionTest.java (with props) Modified: jmeter/trunk/bin/saveservice.properties jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties jmeter/trunk/src/core/org/apache/jmeter/save/SaveService.java jmeter/trunk/src/core/org/apache/jmeter/util/XPathUtil.java jmeter/trunk/test/src/org/apache/jmeter/util/XPathUtilTest.java jmeter/trunk/xdocs/changes.xml jmeter/trunk/xdocs/usermanual/component_reference.xml Modified: jmeter/trunk/bin/saveservice.properties URL: http://svn.apache.org/viewvc/jmeter/trunk/bin/saveservice.properties?rev=1860513&r1=1860512&r2=1860513&view=diff ============================================================================== --- jmeter/trunk/bin/saveservice.properties (original) +++ jmeter/trunk/bin/saveservice.properties Sun Jun 2 10:51:23 2019 @@ -363,6 +363,8 @@ XMLSchemaAssertion=org.apache.jmeter.ass XMLSchemaAssertionGUI=org.apache.jmeter.assertions.gui.XMLSchemaAssertionGUI XPathAssertion=org.apache.jmeter.assertions.XPathAssertion XPathAssertionGui=org.apache.jmeter.assertions.gui.XPathAssertionGui +XPath2Assertion=org.apache.jmeter.assertions.XPath2Assertion +XPath2AssertionGui=org.apache.jmeter.assertions.gui.XPath2AssertionGui XPathExtractor=org.apache.jmeter.extractor.XPathExtractor XPathExtractorGui=org.apache.jmeter.extractor.gui.XPathExtractorGui XPath2Extractor=org.apache.jmeter.extractor.XPath2Extractor @@ -418,4 +420,4 @@ _org.apache.jmeter.save.converters.TestR _org.apache.jmeter.save.ScriptWrapperConverter=mapping # # Remember to update the _version entry -# +# \ No newline at end of file Added: jmeter/trunk/src/components/org/apache/jmeter/assertions/XPath2Assertion.java URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/assertions/XPath2Assertion.java?rev=1860513&view=auto ============================================================================== --- jmeter/trunk/src/components/org/apache/jmeter/assertions/XPath2Assertion.java (added) +++ jmeter/trunk/src/components/org/apache/jmeter/assertions/XPath2Assertion.java Sun Jun 2 10:51:23 2019 @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.jmeter.assertions; + +import java.io.Serializable; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractScopedAssertion; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.util.XPathUtil; + +import net.sf.saxon.s9api.SaxonApiException; + +/** + * Checks if the result is a well-formed XML content and whether it matches an + * XPath + * + */ +public class XPath2Assertion extends AbstractScopedAssertion implements Serializable, Assertion { + private static final long serialVersionUID = 241L; + // + JMX file attributes + private static final String XPATH_KEY = "XPath.xpath"; // $NON-NLS-1$ + private static final String VALIDATE_KEY = "XPath.validate"; // $NON-NLS-1$ + private static final String NEGATE_KEY = "XPath.negate"; // $NON-NLS-1$ + private static final String NAMESPACES = "XPath.namespaces"; // $NON-NLS-1$ + // - JMX file attributes + public static final String DEFAULT_XPATH = "/"; + + /** + * Returns the result of the Assertion. Checks if the result is well-formed XML, + * and that the XPath expression is matched (or not, as the case may be) + */ + @Override + public AssertionResult getResult(SampleResult response) { + // no error as default + AssertionResult result = new AssertionResult(getName()); + result.setFailure(false); + result.setFailureMessage(""); + String responseData = null; + if (isScopeVariable()) { + String inputString = getThreadContext().getVariables().get(getVariableName()); + if (!StringUtils.isEmpty(inputString)) { + responseData = inputString; + } + } else { + responseData = response.getResponseDataAsString(); + } + if (responseData == null) { + return result.setResultForNull(); + } + try { + XPathUtil.computeAssertionResultUsingSaxon(result, responseData, getXPathString(), + getNamespaces(),isNegated()); + } catch (SaxonApiException e) { + result.setError(true); + result.setFailureMessage("SaxonApiException occured computing assertion with XPath:" + getXPathString() + ", error:" + e.getMessage()); + return result; + } + return result; + } + + /** + * Get The XPath String that will be used in matching the document + * + * @return String xpath String + */ + public String getXPathString() { + return getPropertyAsString(XPATH_KEY, DEFAULT_XPATH); + } + + /** + * Set the XPath String this will be used as an xpath + * + * @param xpath String + */ + public void setXPathString(String xpath) { + setProperty(new StringProperty(XPATH_KEY, xpath)); + } + + /** + * Set use validation + * + * @param validate Flag whether validation should be used + */ + public void setValidating(boolean validate) { + setProperty(new BooleanProperty(VALIDATE_KEY, validate)); + } + + public void setNegated(boolean negate) { + setProperty(new BooleanProperty(NEGATE_KEY, negate)); + } + + /** + * Is this validating + * + * @return boolean + */ + public boolean isValidating() { + return getPropertyAsBoolean(VALIDATE_KEY, false); + } + + /** + * Negate the XPath test, that is return true if something is not found. + * + * @return boolean negated + */ + public boolean isNegated() { + return getPropertyAsBoolean(NEGATE_KEY, false); + } + + public void setNamespaces(String namespaces) { + setProperty(NAMESPACES, namespaces); + } + + public String getNamespaces() { + return getPropertyAsString(NAMESPACES); + } +} Propchange: jmeter/trunk/src/components/org/apache/jmeter/assertions/XPath2Assertion.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jmeter/trunk/src/components/org/apache/jmeter/assertions/XPath2Assertion.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: jmeter/trunk/src/components/org/apache/jmeter/assertions/gui/XPath2AssertionGui.java URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/assertions/gui/XPath2AssertionGui.java?rev=1860513&view=auto ============================================================================== --- jmeter/trunk/src/components/org/apache/jmeter/assertions/gui/XPath2AssertionGui.java (added) +++ jmeter/trunk/src/components/org/apache/jmeter/assertions/gui/XPath2AssertionGui.java Sun Jun 2 10:51:23 2019 @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.assertions.gui; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.JPanel; + +import org.apache.jmeter.assertions.XPath2Assertion; +import org.apache.jmeter.gui.GUIMenuSortOrder; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.util.JMeterUtils; + +@GUIMenuSortOrder(50) +public class XPath2AssertionGui extends AbstractAssertionGui { // $NOSONAR + + private static final long serialVersionUID = 240L;// $NON-NLS-1$ + private XPath2Panel xpath; + + + public XPath2AssertionGui() { + super(); + init(); + } + + /** + * Returns the label to be shown within the JTree-Component. + */ + @Override + public String getLabelResource() { + return "xpath2_assertion_title"; //$NON-NLS-1$ + } + + /** + * Create test element + */ + @Override + public TestElement createTestElement() { + XPath2Assertion el = new XPath2Assertion(); + modifyTestElement(el); + return el; + } + + public String getXPathAttributesTitle() { + return JMeterUtils.getResString("xpath2_assertion_test"); //$NON-NLS-1$ + } + + @Override + public void configure(TestElement el) { + super.configure(el); + XPath2Assertion assertion = (XPath2Assertion) el; + showScopeSettings(assertion, true); + xpath.setXPath(assertion.getXPathString()); + xpath.setNegated(assertion.isNegated()); + xpath.setNamespaces(assertion.getNamespaces()); + } + + private void init() { // WARNING: called from ctor so must not be overridden (i.e. must be private or final) + setLayout(new BorderLayout()); + setBorder(makeBorder()); + + Box topBox = Box.createVerticalBox(); + + topBox.add(makeTitlePanel()); + + topBox.add(createScopePanel(true)); + add(topBox, BorderLayout.NORTH); + + // USER_INPUT + JPanel sizePanel = new JPanel(new BorderLayout()); + sizePanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + sizePanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), + getXPathAttributesTitle())); + xpath = new XPath2Panel(); + sizePanel.add(xpath); + add(sizePanel, BorderLayout.CENTER); + } + /** + * Modifies a given TestElement to mirror the data in the gui components. + * + * @see org.apache.jmeter.gui.JMeterGUIComponent#modifyTestElement(TestElement) + */ + @Override + public void modifyTestElement(TestElement el) { + super.configureTestElement(el); + if (el instanceof XPath2Assertion) { + XPath2Assertion assertion = (XPath2Assertion) el; + saveScopeSettings(assertion); + assertion.setNegated(xpath.isNegated()); + assertion.setXPathString(xpath.getXPath()); + assertion.setNamespaces(xpath.getNamespaces()); + } + } + + /** + * Implements JMeterGUIComponent.clearGui + */ + @Override + public void clearGui() { + super.clearGui(); + xpath.setXPath("/"); //$NON-NLS-1$ + xpath.setNegated(false); + xpath.setNamespaces(""); + } + +} Propchange: jmeter/trunk/src/components/org/apache/jmeter/assertions/gui/XPath2AssertionGui.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jmeter/trunk/src/components/org/apache/jmeter/assertions/gui/XPath2AssertionGui.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: jmeter/trunk/src/components/org/apache/jmeter/assertions/gui/XPath2Panel.java URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/assertions/gui/XPath2Panel.java?rev=1860513&view=auto ============================================================================== --- jmeter/trunk/src/components/org/apache/jmeter/assertions/gui/XPath2Panel.java (added) +++ jmeter/trunk/src/components/org/apache/jmeter/assertions/gui/XPath2Panel.java Sun Jun 2 10:51:23 2019 @@ -0,0 +1,260 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.assertions.gui; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; + +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.jmeter.gui.util.JSyntaxTextArea; +import org.apache.jmeter.gui.util.JTextScrollPane; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.XPathUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Gui component for representing a xpath expression + * + */ +public class XPath2Panel extends JPanel { + private static final long serialVersionUID = 241L; + + private static final Logger log = LoggerFactory.getLogger(XPath2Panel.class); + + private JCheckBox negated; + + private JSyntaxTextArea xpath; + + private JButton checkXPath; + + private JSyntaxTextArea namespacesTA; + public String getNamespaces() { + return this.namespacesTA.getText(); + } + + public void setNamespaces(String namespaces) { + this.namespacesTA.setText(namespaces); + } + + /** + * + */ + public XPath2Panel() { + super(); + init(); + } + + private void init() { // WARNING: called from ctor so must not be overridden (i.e. must be private or + // final) + setLayout(new BorderLayout()); + Box topBox = Box.createVerticalBox(); + Box box = Box.createHorizontalBox(); + box.add(getNegatedCheckBox()); + box.add(Box.createHorizontalGlue()); + box.add(getCheckXPathButton()); + box.add(Box.createHorizontalGlue()); + box.add(Box.createHorizontalGlue()); + box.add(Box.createHorizontalGlue()); + topBox.add(box); + topBox.add(makeParameterPanel()); + add(topBox, BorderLayout.NORTH); + add(JTextScrollPane.getInstance(getXPathField()), BorderLayout.CENTER); + setDefaultValues(); + } + private JPanel makeParameterPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + initConstraints(gbc); + panel.add(new JLabel(JMeterUtils.getResString("xpath_extractor_user_namespaces")), gbc.clone()); + gbc.gridx++; + gbc.weightx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + namespacesTA = JSyntaxTextArea.getInstance(5, 80); + panel.add(JTextScrollPane.getInstance(namespacesTA, true), gbc.clone()); + resetContraints(gbc); + + return panel; + } + private void resetContraints(GridBagConstraints gbc) { + gbc.gridx = 0; + gbc.gridy++; + gbc.weightx = 0; + gbc.gridwidth = 1; + gbc.fill = GridBagConstraints.NONE; + } + + private void initConstraints(GridBagConstraints gbc) { + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.NONE; + gbc.gridheight = 1; + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 0; + gbc.weighty = 0; + } + /** + * Set default values on this component + */ + public void setDefaultValues() { + setXPath("/"); //$NON-NLS-1$ + setNegated(false); + } + + /** + * Get the XPath String + * + * @return String + */ + public String getXPath() { + return this.xpath.getText(); + } + + /** + * Set the string that will be used in the xpath evaluation + * + * @param xpath The string representing the xpath expression + */ + public void setXPath(String xpath) { + this.xpath.setInitialText(xpath); + } + + /** + * Does this negate the xpath results + * + * @return boolean + */ + public boolean isNegated() { + return this.negated.isSelected(); + } + + /** + * Set this to true, if you want success when the xpath does not match. + * + * @param negated Flag whether xpath match should be negated + */ + public void setNegated(boolean negated) { + this.negated.setSelected(negated); + } + + /** + * Negated chechbox + * + * @return JCheckBox + */ + public JCheckBox getNegatedCheckBox() { + if (negated == null) { + negated = new JCheckBox(JMeterUtils.getResString("xpath2_assertion_negate"), false); //$NON-NLS-1$ + } + + return negated; + } + + /** + * Check XPath button + * + * @return JButton + */ + public JButton getCheckXPathButton() { + if (checkXPath == null) { + checkXPath = new JButton(JMeterUtils.getResString("xpath2_assertion_button")); //$NON-NLS-1$ + checkXPath.addActionListener(e -> validXPath(xpath.getText(), true, this.getNamespaces())); + } + return checkXPath; + } + + /** + * Returns the current {@link JSyntaxTextArea} for the xpath expression, or + * creates a new one, if none is found. + * + * @return {@link JSyntaxTextArea} for the xpath expression + */ + public JSyntaxTextArea getXPathField() { + if (xpath == null) { + xpath = JSyntaxTextArea.getInstance(20, 80); + xpath.setLanguage("xpath"); //$NON-NLS-1$ + } + return xpath; + } + + /** + * @return Returns the showNegate. + */ + public boolean isShowNegated() { + return this.getNegatedCheckBox().isVisible(); + } + + /** + * @param showNegate + * The showNegate to set. + */ + public void setShowNegated(boolean showNegate) { + getNegatedCheckBox().setVisible(showNegate); + } + + /** + * Test whether an XPath is valid. It seems the Xalan has no easy way to + * check, so this creates a dummy test document, then tries to evaluate the xpath against it. + * + * @param xpathString + * XPath String to validate + * @param showDialog + * weather to show a dialog + * @return returns true if valid, false otherwise. + */ + public static boolean validXPath(String xpathString, boolean showDialog,String namespaces) { + String ret = null; + boolean success = true; + Document testDoc = null; + try { + testDoc = XPathUtil.makeDocumentBuilder(false, false, false, false).newDocument(); + Element el = testDoc.createElement("root"); //$NON-NLS-1$ + testDoc.appendChild(el); + XPathUtil.validateXPath2(testDoc, xpathString, namespaces); + } catch (IllegalArgumentException | ParserConfigurationException | TransformerException e) { + log.warn("Exception while validating XPath.", e); + success = false; + ret = e.getLocalizedMessage(); + } + if (showDialog) { + JOptionPane.showMessageDialog(null, + success ? JMeterUtils.getResString("xpath_assertion_valid") : ret, //$NON-NLS-1$ + success ? JMeterUtils.getResString("xpath_assertion_valid") : //$NON-NLS-1$ + JMeterUtils.getResString("xpath_assertion_failed"), //$NON-NLS-1$ + success ? JOptionPane.INFORMATION_MESSAGE //$NON-NLS-1$ + : JOptionPane.ERROR_MESSAGE); + } + return success; + + } + + +} Propchange: jmeter/trunk/src/components/org/apache/jmeter/assertions/gui/XPath2Panel.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jmeter/trunk/src/components/org/apache/jmeter/assertions/gui/XPath2Panel.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Modified: jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties?rev=1860513&r1=1860512&r2=1860513&view=diff ============================================================================== --- jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties (original) +++ jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties Sun Jun 2 10:51:23 2019 @@ -1472,6 +1472,11 @@ xpath_extractor_user_namespaces=<html>Na xpath_extractor_title=XPath Extractor xpath_file_file_name=XML file to get values from xpath_tester=XPath Tester +xpath2_assertion_button=Validate xpath expression +xpath2_assertion_negate=Invert assertion(will fail if above conditions met) +xpath2_assertion_option=XML Parsing Options +xpath2_assertion_test=XPath2 Assertion +xpath2_assertion_title=XPath2 Assertion xpath2_tester=XPath2 Tester xpath_namespaces=Show namespaces aliases xpath_tester_button_test=Test Modified: jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties?rev=1860513&r1=1860512&r2=1860513&view=diff ============================================================================== --- jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties (original) +++ jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties Sun Jun 2 10:51:23 2019 @@ -1461,6 +1461,11 @@ xpath_extractor_user_namespaces=Liste de xpath_extractor_title=Extracteur XPath xpath_file_file_name=Fichier XML contenant les valeurs xpath_tester=Testeur XPath +xpath2_assertion_button=Valider l'expression xpath +xpath2_assertion_negate=Inverser l'assertion (échouera si les conditions ci-dessus sont remplies) +xpath2_assertion_option=Options d'analyse XML +xpath2_assertion_test=Vérificateur XPath2 +xpath2_assertion_title=Assertion XPath2 xpath2_tester=Testeur XPath2 xpath_namespaces=Voir alias namespaces xpath_tester_button_test=Tester Modified: jmeter/trunk/src/core/org/apache/jmeter/save/SaveService.java URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/save/SaveService.java?rev=1860513&r1=1860512&r2=1860513&view=diff ============================================================================== --- jmeter/trunk/src/core/org/apache/jmeter/save/SaveService.java (original) +++ jmeter/trunk/src/core/org/apache/jmeter/save/SaveService.java Sun Jun 2 10:51:23 2019 @@ -156,7 +156,7 @@ public class SaveService { private static String fileVersion = ""; // computed from saveservice.properties file// $NON-NLS-1$ // Must match the sha1 checksum of the file saveservice.properties (without newline character), // used to ensure saveservice.properties and SaveService are updated simultaneously - static final String FILEVERSION = "e922e4aa06ed9f253d68c13f445d5bfedaccd876"; // Expected value $NON-NLS-1$ + static final String FILEVERSION = "822c168ccf18e482d4be1915456c0b503b6f3d75"; // Expected value $NON-NLS-1$ private static String fileEncoding = ""; // read from properties file// $NON-NLS-1$ Added: jmeter/trunk/src/core/org/apache/jmeter/util/PropertiesBasedPrefixResolverForXpath2.java URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/util/PropertiesBasedPrefixResolverForXpath2.java?rev=1860513&view=auto ============================================================================== --- jmeter/trunk/src/core/org/apache/jmeter/util/PropertiesBasedPrefixResolverForXpath2.java (added) +++ jmeter/trunk/src/core/org/apache/jmeter/util/PropertiesBasedPrefixResolverForXpath2.java Sun Jun 2 10:51:23 2019 @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.jmeter.util; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.xml.utils.PrefixResolver; +import org.apache.xml.utils.PrefixResolverDefault; +import org.w3c.dom.Node; + +/** + * {@link PrefixResolver} implementation that loads prefix configuration from + * jmeter property xpath.namespace.config + */ +public class PropertiesBasedPrefixResolverForXpath2 extends PrefixResolverDefault { + private Map<String, String> namespace_map = new HashMap<>(); + + /** + * @param xpathExpressionContext Node + */ + public PropertiesBasedPrefixResolverForXpath2(Node xpathExpressionContext, String namespace) { + super(xpathExpressionContext); + namespace_map.clear(); + namespace = namespace.trim(); + if (!namespace.isEmpty()) { + for (String n : namespace.split("\\s+")) { + String[] keyandvalue = n.trim().split("="); + namespace_map.put(keyandvalue[0], keyandvalue[1]); + } + } + } + + /** + * Searches prefix in NAMESPACE_MAP, if it fails to find it defaults to parent + * implementation + * + * @param prefix Prefix + * @param namespaceContext Node + */ + @Override + public String getNamespaceForPrefix(String prefix, Node namespaceContext) { + String namespace = namespace_map.get(prefix); + if (namespace == null) { + return super.getNamespaceForPrefix(prefix, namespaceContext); + } else { + return namespace; + } + } +} Propchange: jmeter/trunk/src/core/org/apache/jmeter/util/PropertiesBasedPrefixResolverForXpath2.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jmeter/trunk/src/core/org/apache/jmeter/util/PropertiesBasedPrefixResolverForXpath2.java ------------------------------------------------------------------------------ svn:mime-type = text/plain 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=1860513&r1=1860512&r2=1860513&view=diff ============================================================================== --- jmeter/trunk/src/core/org/apache/jmeter/util/XPathUtil.java (original) +++ jmeter/trunk/src/core/org/apache/jmeter/util/XPathUtil.java Sun Jun 2 10:51:23 2019 @@ -75,7 +75,6 @@ import net.sf.saxon.s9api.XdmValue; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; - /** * This class provides a few utility methods for dealing with XML/XPath. */ @@ -590,6 +589,29 @@ public class XPathUtil { } /** + * + * @param document XML Document + * @return {@link PrefixResolver} + */ + private static PrefixResolver getPrefixResolverForXPath2(Document document,String namespaces) { + return new PropertiesBasedPrefixResolverForXpath2(document.getDocumentElement(),namespaces); + } + /** + * Validate xpathString is a valid XPath expression + * @param document XML Document + * @param xpathString XPATH String + * @throws TransformerException if expression fails to evaluate + */ + public static void validateXPath2(Document document, String xpathString,String namespaces) throws TransformerException { + if (XPathAPI.eval(document, xpathString, getPrefixResolverForXPath2(document,namespaces)) == null) { + // We really should never get here + // because eval will throw an exception + // if xpath is invalid, but whatever, better + // safe + throw new IllegalArgumentException("xpath eval of '" + xpathString + "' was null"); + } + } + /** * Fills result * @param result {@link AssertionResult} * @param doc XML Document @@ -646,6 +668,75 @@ public class XPathUtil { .toString()); } } + + + /*** + * + * @param result The result of xpath2 assertion + * @param xmlFile + * @param xPathQuery + * @param namespaces + * @throws SaxonApiException + * @throws FactoryConfigurationError + */ + public static void computeAssertionResultUsingSaxon(AssertionResult result, String xmlFile, String xPathQuery, + String namespaces, Boolean isNegated) throws SaxonApiException, FactoryConfigurationError { + // generating the cache key + final ImmutablePair<String, String> key = ImmutablePair.of(xPathQuery, namespaces); + // check the cache + XPathExecutable xPathExecutable; + if (StringUtils.isNotEmpty(xPathQuery)) { + xPathExecutable = XPATH_CACHE.get(key); + } else { + log.warn("Error : {}", JMeterUtils.getResString("xpath2_extractor_empty_query")); + return; + } + try (StringReader reader = new StringReader(xmlFile)) { + // We could instanciate it once but might trigger issues in the future + // Sharing of a DocumentBuilder across multiple threads is not recommended. + // However, in the current implementation sharing a DocumentBuilder (once + // initialized) + // will only cause problems if a SchemaValidator is used. + net.sf.saxon.s9api.DocumentBuilder builder = PROCESSOR.newDocumentBuilder(); + XdmNode xdmNode = builder.build(new SAXSource(new InputSource(reader))); + if (xPathExecutable != null) { + XPathSelector selector = null; + try { + Document doc; + doc = XPathUtil.makeDocumentBuilder(false, false, false, false).newDocument(); + XObject xObject = XPathAPI.eval(doc, xPathQuery, getPrefixResolverForXPath2(doc, namespaces)); + selector = xPathExecutable.load(); + selector.setContextItem(xdmNode); + XdmValue nodes = selector.evaluate(); + boolean resultOfEval = true; + int length = nodes.size(); + // In case we need to extract everything + if (length == 0) { + resultOfEval = false; + } else if (xObject.getType() == XObject.CLASS_BOOLEAN) { + resultOfEval = Boolean.valueOf(nodes.itemAt(0).getStringValue()); + } + result.setFailure(isNegated ? resultOfEval : !resultOfEval); + result.setFailureMessage( + isNegated ? "Nodes Matched for " + xPathQuery : "No Nodes Matched for " + xPathQuery); + } catch (ParserConfigurationException | TransformerException e) { + result.setError(true); + result.setFailureMessage(new StringBuilder("Exception: ").append(e.getMessage()).append(" for:") + .append(xPathQuery).toString()); + } finally { + if (selector != null) { + try { + selector.getUnderlyingXPathContext().setContextItem(null); + } catch (Exception e) { // NOSONAR Ignored on purpose + result.setError(true); + result.setFailureMessage(new StringBuilder("Exception: ").append(e.getMessage()) + .append(" for:").append(xPathQuery).toString()); + } + } + } + } + } + } /** * Formats XML Added: jmeter/trunk/test/src/org/apache/jmeter/assertions/XPath2AssertionTest.java URL: http://svn.apache.org/viewvc/jmeter/trunk/test/src/org/apache/jmeter/assertions/XPath2AssertionTest.java?rev=1860513&view=auto ============================================================================== --- jmeter/trunk/test/src/org/apache/jmeter/assertions/XPath2AssertionTest.java (added) +++ jmeter/trunk/test/src/org/apache/jmeter/assertions/XPath2AssertionTest.java Sun Jun 2 10:51:23 2019 @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.assertions; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import javax.xml.parsers.FactoryConfigurationError; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.util.JMeterUtils; +import org.junit.Test; + +public class XPath2AssertionTest { + + final String xmlDoc = JMeterUtils.getResourceFileAsText("XPathUtilTestXml.xml"); + @Test + public void testXPath2AssertionPath1() throws FactoryConfigurationError { + XPath2Assertion assertion = new XPath2Assertion(); + SampleResult response = new SampleResult(); + String namespaces = "age=http://www.w3.org/2003/01/geo/wgs84_pos#"; + String xPathQuery = "//Employees/Employee[1]/age:ag"; + assertion.setNamespaces(namespaces); + assertion.setXPathString(xPathQuery); + response.setResponseData(xmlDoc, "UTF-8"); + AssertionResult res = assertion.getResult(response); + assertFalse("When xpath2 conforms to xml, the result of assertion should be true ",res.isFailure()); + assertFalse("When the format of xpath2 is right, assertion will run correctly ",res.isError()); + } + @Test + public void testXPath2AssertionPath1Negated() throws FactoryConfigurationError { + XPath2Assertion assertion = new XPath2Assertion(); + SampleResult response = new SampleResult(); + String namespaces = "age=http://www.w3.org/2003/01/geo/wgs84_pos#"; + String xPathQuery = "//Employees/Employee[1]/age:ag"; + assertion.setNamespaces(namespaces); + assertion.setXPathString(xPathQuery); + assertion.setNegated(true); + response.setResponseData(xmlDoc, "UTF-8"); + AssertionResult res = assertion.getResult(response); + assertTrue("When xpath2 conforms to xml, the result of assertion should be false ",res.isFailure()); + assertFalse("When the format of xpath2 is right, assertion will run correctly ",res.isError()); + + } + @Test + public void testXPath2AssertionPath2() throws FactoryConfigurationError { + XPath2Assertion assertion = new XPath2Assertion(); + SampleResult response = new SampleResult(); + String namespaces = "age=http://www.w3.org/2003/01/geo/wgs84#"; + String xPathQuery = "//Employees/Employee[1]/age:ag"; + assertion.setNamespaces(namespaces); + assertion.setXPathString(xPathQuery); + response.setResponseData(xmlDoc, "UTF-8"); + AssertionResult res = assertion.getResult(response); + assertTrue("When xpath2 doesn't conform to xml, the result of assertion should be false ", res.isFailure()); + assertFalse("When the format of xpath2 is right, assertion will run correctly ",res.isError()); + } + @Test + public void testXPath2AssertionPath2Negated() throws FactoryConfigurationError { + XPath2Assertion assertion = new XPath2Assertion(); + SampleResult response = new SampleResult(); + String namespaces = "age=http://www.w3.org/2003/01/geo/wgs84#"; + String xPathQuery = "//Employees/Employee[1]/age:ag"; + assertion.setNamespaces(namespaces); + assertion.setXPathString(xPathQuery); + assertion.setNegated(true); + response.setResponseData(xmlDoc, "UTF-8"); + AssertionResult res = assertion.getResult(response); + assertFalse("When xpath2 doesn't conform to xml, the result of assertion should be true ", res.isFailure()); + assertFalse("When the format of xpath2 is right, assertion will run correctly ",res.isError()); + + } + + @Test + public void testXPath2AssertionBool1() throws FactoryConfigurationError { + XPath2Assertion assertion = new XPath2Assertion(); + SampleResult response = new SampleResult(); + String namespaces = "age=http://www.w3.org/2003/01/geo/wgs84_pos#"; + String xPathQuery = "count(//Employee)=4"; + assertion.setNamespaces(namespaces); + assertion.setXPathString(xPathQuery); + response.setResponseData(xmlDoc, "UTF-8"); + AssertionResult res = assertion.getResult(response); + assertFalse("When xpath2 conforms to xml, the result of assertion should be true ",res.isFailure()); + assertFalse("When the format of xpath2 is right, assertion will run correctly ",res.isError()); + } + @Test + public void testXPath2AssertionBool1Negated() throws FactoryConfigurationError { + XPath2Assertion assertion = new XPath2Assertion(); + SampleResult response = new SampleResult(); + String namespaces = "age=http://www.w3.org/2003/01/geo/wgs84_pos#"; + String xPathQuery = "count(//Employee)=4"; + assertion.setNamespaces(namespaces); + assertion.setXPathString(xPathQuery); + assertion.setNegated(true); + response.setResponseData(xmlDoc, "UTF-8"); + AssertionResult res = assertion.getResult(response); + assertTrue("When xpath2 conforms to xml, the result of assertion should be false ",res.isFailure()); + assertFalse("When the format of xpath2 is right, assertion will run correctly ",res.isError()); + } + @Test + public void testXPath2AssertionBool2() throws FactoryConfigurationError { + XPath2Assertion assertion = new XPath2Assertion(); + SampleResult response = new SampleResult(); + String namespaces = "age=http://www.w3.org/2003/01/geo/wgs84_pos#"; + String xPathQuery = "count(//Employee)=3"; //Wrong + assertion.setNamespaces(namespaces); + assertion.setXPathString(xPathQuery); + response.setResponseData(xmlDoc, "UTF-8"); + AssertionResult res = assertion.getResult(response); + assertTrue("When xpath2 doesn't conforms to xml, the result of assertion should be false ",res.isFailure()); + assertFalse("When the format of xpath2 is right, assertion will run correctly ",res.isError()); + } + @Test + public void testXPath2AssertionBool2Negated() throws FactoryConfigurationError { + XPath2Assertion assertion = new XPath2Assertion(); + SampleResult response = new SampleResult(); + String namespaces = "age=http://www.w3.org/2003/01/geo/wgs84_pos#"; + String xPathQuery = "count(//Employee)=3"; //Wrong + assertion.setNamespaces(namespaces); + assertion.setXPathString(xPathQuery); + assertion.setNegated(true); + response.setResponseData(xmlDoc, "UTF-8"); + AssertionResult res = assertion.getResult(response); + assertFalse("When xpath2 doesn't conforms to xml, the result of assertion should be true ",res.isFailure()); + assertFalse("When the format of xpath2 is right, assertion will run correctly ",res.isError()); + } + + +} Propchange: jmeter/trunk/test/src/org/apache/jmeter/assertions/XPath2AssertionTest.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jmeter/trunk/test/src/org/apache/jmeter/assertions/XPath2AssertionTest.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Modified: jmeter/trunk/test/src/org/apache/jmeter/util/XPathUtilTest.java URL: http://svn.apache.org/viewvc/jmeter/trunk/test/src/org/apache/jmeter/util/XPathUtilTest.java?rev=1860513&r1=1860512&r2=1860513&view=diff ============================================================================== --- jmeter/trunk/test/src/org/apache/jmeter/util/XPathUtilTest.java (original) +++ jmeter/trunk/test/src/org/apache/jmeter/util/XPathUtilTest.java Sun Jun 2 10:51:23 2019 @@ -19,20 +19,27 @@ package org.apache.jmeter.util; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import java.io.PrintStream; import java.io.StringReader; import java.util.ArrayList; import java.util.List; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.FactoryConfigurationError; import javax.xml.transform.stream.StreamSource; +import org.apache.jmeter.assertions.XPath2Assertion; +import org.apache.jmeter.assertions.gui.XPath2Panel; import org.hamcrest.CoreMatchers; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; import net.sf.saxon.s9api.Processor; import net.sf.saxon.s9api.SaxonApiException; @@ -47,13 +54,13 @@ public class XPathUtilTest { final String lineSeparator = System.getProperty("line.separator"); final String xmlDoc = JMeterUtils.getResourceFileAsText("XPathUtilTestXml.xml"); - + private XPath2Assertion assertion; @Test public void testBug63033() throws SaxonApiException { Processor p = new Processor(false); XPathCompiler c = p.newXPathCompiler(); c.declareNamespace("age", "http://www.w3.org/2003/01/geo/wgs84_pos#"); - String xPathQuery="//Employees/Employee[1]/age:ag";; + String xPathQuery="//Employees/Employee[1]/age:ag"; XPathExecutable e = c.compile(xPathQuery); XPathSelector selector = e.load(); selector.setContextItem(p.newDocumentBuilder().build(new StreamSource(new StringReader(xmlDoc)))); @@ -165,4 +172,18 @@ public class XPathUtilTest { .is(XPathUtil.formatXml("No well formed xml here"))); System.setErr(origErr); } + + @Test() + public void testValidateXPath2() throws ParserConfigurationException { + Document testDoc=XPathUtil.makeDocumentBuilder(false, false, false, false).newDocument(); + Element el = testDoc.createElement("root"); //$NON-NLS-1$ + testDoc.appendChild(el); + String namespaces = "a=http://www.w3.org/2003/01/geo/wgs84_pos# b=http://www.w3.org/2003/01/geo/wgs85_pos#"; + String xPathQuery = "//Employees/b:Employee[1]/a:ag"; + assertTrue("When the user give namspaces, the result of validation should be true", XPath2Panel.validXPath(xPathQuery, false, namespaces)); + namespaces ="a=http://www.w3.org/2003/01/geo/wgs84_pos#"; + assertFalse("When the user doesn't give namspaces, the result of validation should be false", XPath2Panel.validXPath(xPathQuery, false, namespaces)); +} + + } Modified: jmeter/trunk/xdocs/changes.xml URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/changes.xml?rev=1860513&r1=1860512&r2=1860513&view=diff ============================================================================== --- jmeter/trunk/xdocs/changes.xml [utf-8] (original) +++ jmeter/trunk/xdocs/changes.xml [utf-8] Sun Jun 2 10:51:23 2019 @@ -103,6 +103,7 @@ to view the last major behaviors with th <ul> <li><bug>62863</bug>Enable PKCS11 keystores for usage with KeyStore Manager. Based on patch by Clifford Harms (clifford.harms at gmail.com).</li> <li><pr>457</pr>Slight performance improvement in PoissonRandomTimer by using ThreadLocalRandom. Based on a patch by Xia Li.</li> + <li><bug>62787</bug>Add XPath 2 Assertion. Contributed by Ubik Load Pack (support at ubikloadpack.com)</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=1860513&r1=1860512&r2=1860513&view=diff ============================================================================== --- jmeter/trunk/xdocs/usermanual/component_reference.xml (original) +++ jmeter/trunk/xdocs/usermanual/component_reference.xml Sun Jun 2 10:51:23 2019 @@ -4722,6 +4722,30 @@ prefix2=http\://toto.apache.org </ul> </note> </component> + +<component name="XPath2 Assertion" index="§-num;.5.16" width="871" height="615" screenshot="xpath_assertion.png"> +<description><p>The XPath2 Assertion tests a document for well formedness. Using "<code>/</code>" will match any well-formed +document, and is the default XPath2 Expression. +The assertion also supports boolean expressions, such as "<code>count(//*error)=2</code>". +</p> +Some sample expressions: +<ul> +<li><code>//title[text()='Text to match']</code> - matches <code><title>Text to match</title></code> anywhere in the response</li> +<li><code>/title[text()='Text to match']</code> - matches <code><title>Text to match</title></code> at root level in the response</li> +</ul> +</description> + +<properties> +<property name="Namespaces aliases list" required="No">List of namespaces aliases you want to use to parse the document, one line per declaration. + You must specify them as follow : <code>prefix=namespace</code>. This implementation makes it easier to + use namespaces than with the old XPathExtractor version.</property> +<property name="XPath2 Assertion" required="Yes">XPath to match in the document.</property> +<property name="Invert assertion" required="No">Will fail if xpath expression returns true or matches, succeed otherwise</property> +<property name="Namespace aliases list" required="No">List of namespace aliases prefix=full namespace (one per line)</property> +</properties> +</component> + + <component name="XML Schema Assertion" index="§-num;.5.9" width="472" height="132" screenshot="assertion/XMLSchemaAssertion.png"> <description><p>The XML Schema Assertion allows the user to validate a response against an XML Schema.</p></description>
