diff --git a/extras/build.xml b/extras/build.xml
index 6d950371100..8f739ca7edd 100644
--- a/extras/build.xml
+++ b/extras/build.xml
@@ -105,20 +105,13 @@
-
-
-
-
-
-
Report generated at ${report.datestamp}
-
+
-
-
-
-
-
-
-
-
-
-
- Cannot find all xalan and/or serialiser jars
- The XSLT formatting may not work correctly.
- Check you have xalan and serializer jars in ${lib.dir}
-
-
-
diff --git a/gradle.properties b/gradle.properties
index 874c65a5204..c66221b8add 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -134,7 +134,6 @@ spock-core.version=2.1-groovy-3.0
springframework.version=4.3.17.RELEASE
svgSalamander.version=1.1.2.4
tika.version=1.28.3
-xalan.version=2.7.2
xercesImpl.version=2.12.2
xml-apis.version=1.4.01
xmlgraphics-commons.version=2.7
diff --git a/lib/aareadme.txt b/lib/aareadme.txt
index ee01ff8812d..4db7c7f48e1 100644
--- a/lib/aareadme.txt
+++ b/lib/aareadme.txt
@@ -237,11 +237,6 @@ rsyntaxtextarea-3.0.4
http://fifesoft.com/rsyntaxtextarea/
- syntax coloration
-serialiser-2.7.1
-----------------
-http://www.apache.org/dyn/closer.cgi/xml/xalan-j
-- xalan
-
slf4j-api-1.7.28
----------------
http://www.slf4j.org/
@@ -258,7 +253,7 @@ commons-dbcp2-2.5.0 (org.apache.commons.dbcp2)
--------------------------
- DataSourceElement (JDBC)
-Saxon-HE-9.9.1-5 (net.sf.saxon)
+Saxon-HE-11.3 (net.sf.saxon)
--------------------------
- XPath2Extractor (XML)
@@ -267,11 +262,6 @@ velocity-1.7
http://velocity.apache.org/download.cgi
- Anakia (create documentation) Not used by JMeter runtime
-xalan_2.7.1
------------
-http://www.apache.org/dyn/closer.cgi/xml/xalan-j
-+org.apache.xalan|xml|xpath
-
xercesImpl-2.12.0
----------------
http://xerces.apache.org/xerces2-j/download.cgi
diff --git a/src/bom/build.gradle.kts b/src/bom/build.gradle.kts
index 79e4e4c548f..23fe0860155 100644
--- a/src/bom/build.gradle.kts
+++ b/src/bom/build.gradle.kts
@@ -162,8 +162,6 @@ dependencies {
apiv("org.slf4j:slf4j-api", "slf4j")
apiv("org.spockframework:spock-core")
apiv("oro:oro")
- apiv("xalan:serializer", "xalan")
- apiv("xalan:xalan", "xalan")
apiv("xerces:xercesImpl")
apiv("xml-apis:xml-apis")
apiv("xmlpull:xmlpull")
diff --git a/src/components/src/main/java/org/apache/jmeter/extractor/XPath2Extractor.java b/src/components/src/main/java/org/apache/jmeter/extractor/XPath2Extractor.java
index 77683d147b5..cf68c314912 100644
--- a/src/components/src/main/java/org/apache/jmeter/extractor/XPath2Extractor.java
+++ b/src/components/src/main/java/org/apache/jmeter/extractor/XPath2Extractor.java
@@ -22,6 +22,7 @@
import java.util.List;
import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.transform.TransformerException;
import org.apache.jmeter.assertions.AssertionResult;
import org.apache.jmeter.processor.PostProcessor;
@@ -223,7 +224,7 @@ public void setFragment(boolean selected) {
* @throws FactoryConfigurationError
*/
private void getValuesForXPath(String query, List matchStrings, int matchNumber, String responseData)
- throws SaxonApiException, FactoryConfigurationError {
+ throws SaxonApiException, TransformerException, FactoryConfigurationError {
XPathUtil.putValuesForXPathInListUsingSaxon(responseData, query, matchStrings, getFragment(), matchNumber, getNamespaces());
}
diff --git a/src/components/src/test/java/org/apache/jmeter/assertions/XPathAssertionTest.java b/src/components/src/test/java/org/apache/jmeter/assertions/XPathAssertionTest.java
index b98f32a4b3a..048f79a04cb 100644
--- a/src/components/src/test/java/org/apache/jmeter/assertions/XPathAssertionTest.java
+++ b/src/components/src/test/java/org/apache/jmeter/assertions/XPathAssertionTest.java
@@ -240,7 +240,8 @@ public void testAssertionNumber() throws Exception {
testLog.debug("isError() {} isFailure() {}", res.isError(), res.isFailure());
testLog.debug("failure message: {}", res.getFailureMessage());
assertFalse("Should not be an error", res.isError());
- assertTrue("Should be a failure",res.isFailure());
+ //this used to result in isFailure()=true but is now fixed (after switching from Xalan to Saxon for XPath support)
+ assertFalse("Should not be a failure", res.isFailure());
}
@Test
diff --git a/src/components/src/test/java/org/apache/jmeter/extractor/TestXPathExtractor.java b/src/components/src/test/java/org/apache/jmeter/extractor/TestXPathExtractor.java
index 6138fb1dbf7..8ba118c9df5 100644
--- a/src/components/src/test/java/org/apache/jmeter/extractor/TestXPathExtractor.java
+++ b/src/components/src/test/java/org/apache/jmeter/extractor/TestXPathExtractor.java
@@ -32,6 +32,7 @@
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.threads.JMeterVariables;
+import org.apache.jmeter.util.XPathUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -195,7 +196,7 @@ public void testVariableExtraction() throws Exception {
// No text, but using fragment mode
extractor.setXPathQuery("//a");
extractor.process();
- assertEquals("", vars.get(VAL_NAME));
+ assertEquals(XPathUtil.formatXml(""), XPathUtil.formatXml(vars.get(VAL_NAME)));
}
@Test
@@ -283,7 +284,7 @@ public void testInvalidXpath() throws Exception {
if (Locale.getDefault().getLanguage().startsWith(Locale.ENGLISH.getLanguage())) {
assertThat(
firstResult.getFailureMessage(),
- containsString("A location path was expected, but the following token was encountered")
+ containsString("Unexpected token \"<\" at start of expression")
);
}
assertEquals("Default", vars.get(VAL_NAME));
diff --git a/src/core/build.gradle.kts b/src/core/build.gradle.kts
index db221daf451..b957dcaa8d9 100644
--- a/src/core/build.gradle.kts
+++ b/src/core/build.gradle.kts
@@ -50,11 +50,6 @@ dependencies {
api("oro:oro") {
because("Perl5Matcher org.apache.jmeter.util.JMeterUtils.getMatcher()")
}
- api("xalan:xalan") {
- because("PropertiesBasedPrefixResolver extends PrefixResolverDefault")
- }
- // Note: Saxon should go AFTER xalan so xalan XSLT is used
- // org.apache.jmeter.util.XPathUtilTest.testFormatXmlSimple assumes xalan transformer
api("net.sf.saxon:Saxon-HE") {
because("XPathUtil: throws SaxonApiException")
}
diff --git a/src/core/src/main/java/org/apache/jmeter/util/PrefixResolverDefault.java b/src/core/src/main/java/org/apache/jmeter/util/PrefixResolverDefault.java
new file mode 100644
index 00000000000..f760a7b2417
--- /dev/null
+++ b/src/core/src/main/java/org/apache/jmeter/util/PrefixResolverDefault.java
@@ -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.util;
+
+import javax.xml.XMLConstants;
+
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+/**
+ * This class implements a generic PrefixResolver that
+ * can be used to perform prefix-to-namespace lookup
+ * for the XPath object.
+ */
+public class PrefixResolverDefault
+{
+
+ /**
+ * The context to resolve the prefix from, if the context
+ * is not given.
+ */
+ Node m_context;
+
+ /**
+ * Construct a PrefixResolverDefault object.
+ * @param xpathExpressionContext The context from
+ * which XPath expression prefixes will be resolved.
+ * Warning: This will not work correctly if xpathExpressionContext
+ * is an attribute node.
+ */
+ public PrefixResolverDefault(Node xpathExpressionContext)
+ {
+ m_context = xpathExpressionContext;
+ }
+
+ /**
+ * Given a namespace, get the corrisponding prefix. This assumes that
+ * the PrevixResolver hold's it's own namespace context, or is a namespace
+ * context itself.
+ * @param prefix Prefix to resolve.
+ * @return Namespace that prefix resolves to, or null if prefix
+ * is not bound.
+ */
+ public String getNamespaceForPrefix(String prefix)
+ {
+ return getNamespaceForPrefix(prefix, m_context);
+ }
+
+ /**
+ * Given a namespace, get the corrisponding prefix.
+ * Warning: This will not work correctly if namespaceContext
+ * is an attribute node.
+ * @param prefix Prefix to resolve.
+ * @param namespaceContext Node from which to start searching for a
+ * xmlns attribute that binds a prefix to a namespace.
+ * @return Namespace that prefix resolves to, or null if prefix
+ * is not bound.
+ */
+ public String getNamespaceForPrefix(String prefix,
+ org.w3c.dom.Node namespaceContext)
+ {
+
+ Node parent = namespaceContext;
+ String namespace = null;
+
+ if (prefix.equals("xml"))
+ {
+ namespace = XMLConstants.XML_NS_URI;
+ }
+ else
+ {
+ int type;
+
+ while ((null != parent) && (null == namespace)
+ && (((type = parent.getNodeType()) == Node.ELEMENT_NODE)
+ || (type == Node.ENTITY_REFERENCE_NODE)))
+ {
+ if (type == Node.ELEMENT_NODE)
+ {
+ if (parent.getNodeName().indexOf(prefix+":") == 0)
+ {
+ return parent.getNamespaceURI();
+ }
+ NamedNodeMap nnm = parent.getAttributes();
+
+ for (int i = 0; i < nnm.getLength(); i++)
+ {
+ Node attr = nnm.item(i);
+ String aname = attr.getNodeName();
+ boolean isPrefix = aname.startsWith("xmlns:");
+
+ if (isPrefix || aname.equals("xmlns"))
+ {
+ int index = aname.indexOf(':');
+ String p = isPrefix ? aname.substring(index + 1) : "";
+
+ if (p.equals(prefix))
+ {
+ namespace = attr.getNodeValue();
+
+ break;
+ }
+ }
+ }
+ }
+
+ parent = parent.getParentNode();
+ }
+ }
+
+ return namespace;
+ }
+
+ /**
+ * Return the base identifier.
+ *
+ * @return null by default
+ */
+ public String getBaseIdentifier()
+ {
+ return null;
+ }
+
+ /**
+ * @return false by default
+ */
+ public boolean handlesNullPrefixes() {
+ return false;
+ }
+
+}
diff --git a/src/core/src/main/java/org/apache/jmeter/util/PropertiesBasedPrefixResolver.java b/src/core/src/main/java/org/apache/jmeter/util/PropertiesBasedPrefixResolver.java
index 4cd789485f1..d6df1ee3615 100644
--- a/src/core/src/main/java/org/apache/jmeter/util/PropertiesBasedPrefixResolver.java
+++ b/src/core/src/main/java/org/apache/jmeter/util/PropertiesBasedPrefixResolver.java
@@ -23,21 +23,22 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
+import javax.xml.namespace.NamespaceContext;
+
import org.apache.commons.lang3.StringUtils;
import org.apache.jorphan.util.JOrphanUtils;
-import org.apache.xml.utils.PrefixResolver;
-import org.apache.xml.utils.PrefixResolverDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
/**
- * {@link PrefixResolver} implementation that loads prefix configuration from jmeter property xpath.namespace.config
+ * PrefixResolver implementation that loads prefix configuration from jmeter property xpath.namespace.config
*/
-public class PropertiesBasedPrefixResolver extends PrefixResolverDefault {
+public class PropertiesBasedPrefixResolver extends PrefixResolverDefault implements NamespaceContext {
private static final Logger log = LoggerFactory.getLogger(PropertiesBasedPrefixResolver.class);
private static final String XPATH_NAMESPACE_CONFIG = "xpath.namespace.config";
private static final Map NAMESPACE_MAP = new HashMap<>();
@@ -85,11 +86,44 @@ public PropertiesBasedPrefixResolver(Node xpathExpressionContext) {
*/
@Override
public String getNamespaceForPrefix(String prefix, Node namespaceContext) {
- String namespace = NAMESPACE_MAP.get(prefix);
+ String namespace = getNamespaceURI(prefix);
if(namespace==null) {
return super.getNamespaceForPrefix(prefix, namespaceContext);
} else {
return namespace;
}
}
+
+ @Override
+ public String getNamespaceURI(String prefix) {
+ return NAMESPACE_MAP.get(prefix);
+ }
+
+ @Override
+ public String getPrefix(String namespaceURI) {
+ if (namespaceURI != null) {
+ for (Map.Entry entry : NAMESPACE_MAP.entrySet()) {
+ if (namespaceURI.equals(entry.getValue())) {
+ return entry.getValue();
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Iterator getPrefixes(String namespaceURI) {
+ return NAMESPACE_MAP.keySet().iterator();
+ }
+
+ String getNamespacesAsLineDelimitedProperties() {
+ StringBuilder builder = new StringBuilder();
+ for (Map.Entry entry : NAMESPACE_MAP.entrySet()) {
+ builder.append(entry.getKey())
+ .append('=')
+ .append(entry.getValue())
+ .append('\n');
+ }
+ return builder.toString();
+ }
}
diff --git a/src/core/src/main/java/org/apache/jmeter/util/PropertiesBasedPrefixResolverForXpath2.java b/src/core/src/main/java/org/apache/jmeter/util/PropertiesBasedPrefixResolverForXpath2.java
index 44eb5296fc4..dbb1fd2cca2 100644
--- a/src/core/src/main/java/org/apache/jmeter/util/PropertiesBasedPrefixResolverForXpath2.java
+++ b/src/core/src/main/java/org/apache/jmeter/util/PropertiesBasedPrefixResolverForXpath2.java
@@ -18,17 +18,18 @@
package org.apache.jmeter.util;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
-import org.apache.xml.utils.PrefixResolver;
-import org.apache.xml.utils.PrefixResolverDefault;
+import javax.xml.namespace.NamespaceContext;
+
import org.w3c.dom.Node;
/**
- * {@link PrefixResolver} implementation that loads prefix configuration from
+ * PrefixResolver implementation that loads prefix configuration from
* jmeter property xpath.namespace.config
*/
-public class PropertiesBasedPrefixResolverForXpath2 extends PrefixResolverDefault {
+public class PropertiesBasedPrefixResolverForXpath2 extends PrefixResolverDefault implements NamespaceContext {
private Map namespaceMap = new HashMap<>();
/**
@@ -55,11 +56,33 @@ public PropertiesBasedPrefixResolverForXpath2(Node xpathExpressionContext, Strin
*/
@Override
public String getNamespaceForPrefix(String prefix, Node namespaceContext) {
- String namespace = namespaceMap.get(prefix);
+ String namespace = getNamespaceURI(prefix);
if (namespace == null) {
return super.getNamespaceForPrefix(prefix, namespaceContext);
} else {
return namespace;
}
}
+
+ @Override
+ public String getNamespaceURI(String prefix) {
+ return namespaceMap.get(prefix);
+ }
+
+ @Override
+ public String getPrefix(String namespaceURI) {
+ if (namespaceURI != null) {
+ for (Map.Entry entry : namespaceMap.entrySet()) {
+ if (namespaceURI.equals(entry.getValue())) {
+ return entry.getValue();
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Iterator getPrefixes(String namespaceURI) {
+ return namespaceMap.keySet().iterator();
+ }
}
diff --git a/src/core/src/main/java/org/apache/jmeter/util/XPathUtil.java b/src/core/src/main/java/org/apache/jmeter/util/XPathUtil.java
index d183e7da5ea..ca5a11938fd 100644
--- a/src/core/src/main/java/org/apache/jmeter/util/XPathUtil.java
+++ b/src/core/src/main/java/org/apache/jmeter/util/XPathUtil.java
@@ -29,6 +29,7 @@
import java.util.List;
import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@@ -41,21 +42,20 @@
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactoryConfigurationException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.jmeter.assertions.AssertionResult;
-import org.apache.xml.utils.PrefixResolver;
-import org.apache.xpath.XPathAPI;
-import org.apache.xpath.objects.XObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
-import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.tidy.Tidy;
@@ -65,6 +65,8 @@
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
+import net.sf.saxon.jaxp.SaxonTransformerFactory;
+import net.sf.saxon.s9api.ItemType;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XPathExecutable;
@@ -72,6 +74,7 @@
import net.sf.saxon.s9api.XdmItem;
import net.sf.saxon.s9api.XdmNode;
import net.sf.saxon.s9api.XdmValue;
+import net.sf.saxon.xpath.XPathFactoryImpl;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
@@ -303,26 +306,6 @@ public void fatalError(SAXParseException ex) throws SAXException {
}
}
- /**
- * Return value for node including node element
- * @param node Node
- * @return String
- */
- private static String getNodeContent(Node node) {
- StringWriter sw = new StringWriter();
- try {
- TransformerFactory factory = TransformerFactory.newInstance();
- factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
- Transformer t = factory.newTransformer();
- t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
- t.transform(new DOMSource(node), new StreamResult(sw));
- } catch (TransformerException e) {
- sw.write(e.getMessageAndLocation());
- }
- return sw.toString();
- }
-
-
/**
* @param node {@link Node}
* @return String content of node
@@ -345,8 +328,11 @@ public static String getValueForNode(Node node) {
* @throws TransformerException when the internally used xpath engine fails
*/
public static NodeList selectNodeList(Document document, String xPathExpression) throws TransformerException {
- XObject xObject = XPathAPI.eval(document, xPathExpression, getPrefixResolver(document));
- return xObject.nodelist();
+ try {
+ return (NodeList) newXPath(document).evaluate(xPathExpression, document, XPathConstants.NODESET);
+ } catch (XPathExpressionException|XPathFactoryConfigurationException e) {
+ throw new TransformerException(e);
+ }
}
/**
@@ -374,41 +360,14 @@ public static void putValuesForXPathInList(Document document,
*/
public static void putValuesForXPathInList(Document document, String xPathQuery, List 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;
- }
- for (int i = 0 ; i < length; i++) {
- Node match = matches.item(i);
- if(indexToMatch >= 0 && indexToMatch != (i+1)) {
- continue;
- }
- if ( match instanceof Element ){
- if (fragment){
- val = getNodeContent(match);
- } else {
- val = getValueForNode(match);
- }
- } else {
- val = match.getNodeValue();
- }
- matchStrings.add(val);
- }
- } else if (objectType == XObject.CLASS_NULL
- || objectType == XObject.CLASS_UNKNOWN
- || objectType == XObject.CLASS_UNRESOLVEDVARIABLE) {
- if (log.isWarnEnabled()) {
- log.warn("Unexpected object type: {} returned for: {}", xObject.getTypeString(), xPathQuery);
- }
- } else {
- val = xObject.toString();
- matchStrings.add(val);
+ PropertiesBasedPrefixResolver namespaceContext = getPrefixResolver(document);
+ String namespaces = namespaceContext.getNamespacesAsLineDelimitedProperties();
+ net.sf.saxon.s9api.DocumentBuilder builder = PROCESSOR.newDocumentBuilder();
+ try {
+ XdmNode xdmNode = builder.build(new DOMSource(document));
+ putValuesForXPathInListUsingSaxon(xdmNode, xPathQuery, matchStrings, fragment, matchNumber, namespaces, true);
+ } catch (SaxonApiException|FactoryConfigurationError e) {
+ throw new TransformerException(unwrapException(e));
}
}
@@ -416,7 +375,23 @@ public static void putValuesForXPathInListUsingSaxon(
String xmlFile, String xPathQuery,
List matchStrings, boolean fragment,
int matchNumber, String namespaces)
- throws SaxonApiException, FactoryConfigurationError {
+ throws SaxonApiException, TransformerException, FactoryConfigurationError {
+ try (StringReader reader = new StringReader(xmlFile)) {
+ // We could instantiate 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)));
+ putValuesForXPathInListUsingSaxon(xdmNode, xPathQuery, matchStrings, fragment, matchNumber, namespaces, false);
+ }
+ }
+
+ private static void putValuesForXPathInListUsingSaxon(
+ XdmNode xdmNode, String xPathQuery,
+ List matchStrings, boolean fragment,
+ int matchNumber, String namespaces, boolean useNullForEmpty)
+ throws SaxonApiException, TransformerException, FactoryConfigurationError {
// generating the cache key
final ImmutablePair key = ImmutablePair.of(xPathQuery, namespaces);
@@ -424,59 +399,63 @@ public static void putValuesForXPathInListUsingSaxon(
//check the cache
XPathExecutable xPathExecutable;
if(StringUtils.isNotEmpty(xPathQuery)) {
- xPathExecutable = XPATH_CACHE.get(key);
+ try {
+ xPathExecutable = XPATH_CACHE.get(key);
+ } catch (RuntimeException e) {
+ Throwable unwrapped = unwrapException(e);
+ if (unwrapped instanceof TransformerException) {
+ throw (TransformerException) unwrapped;
+ }
+ throw e;
+ }
}
else {
log.warn("Error : {}", JMeterUtils.getResString("xpath2_extractor_empty_query"));
return;
}
- try (StringReader reader = new StringReader(xmlFile)) {
- // We could instantiate 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 {
- selector = xPathExecutable.load();
- selector.setContextItem(xdmNode);
- XdmValue nodes = selector.evaluate();
- int length = nodes.size();
- int indexToMatch = matchNumber;
- // In case we need to extract everything
- if(matchNumber < 0) {
- for(XdmItem item : nodes) {
- if(fragment) {
- matchStrings.add(item.toString());
- }
- else {
- matchStrings.add(item.getStringValue());
- }
+ if(xPathExecutable!=null) {
+ XPathSelector selector = null;
+ try {
+ selector = xPathExecutable.load();
+ selector.setContextItem(xdmNode);
+ XdmValue nodes = selector.evaluate();
+ int length = nodes.size();
+ int indexToMatch = matchNumber;
+ // In case we need to extract everything
+ if(matchNumber < 0) {
+ for(XdmItem item : nodes) {
+ if(fragment) {
+ matchStrings.add(item.toString());
}
- } else {
- if(indexToMatch <= length) {
- if(matchNumber == 0 && length>0) {
- indexToMatch = JMeterUtils.getRandomInt(length)+1;
- }
- XdmItem item = nodes.itemAt(indexToMatch-1);
- matchStrings.add(fragment ? item.toString() : item.getStringValue());
- } else {
- if(log.isWarnEnabled()) {
- log.warn("Error : {}{}", JMeterUtils.getResString("xpath2_extractor_match_number_failure"),indexToMatch);
+ else {
+ String value = item.getStringValue();
+ if (useNullForEmpty && value != null && value.isEmpty()) {
+ matchStrings.add(null);
+ } else {
+ matchStrings.add(value);
}
}
}
- } finally {
- if(selector != null) {
- try {
- selector.getUnderlyingXPathContext().setContextItem(null);
- } catch (Exception e) { // NOSONAR Ignored on purpose
- // NOOP
+ } else {
+ if(indexToMatch <= length) {
+ if(matchNumber == 0 && length>0) {
+ indexToMatch = JMeterUtils.getRandomInt(length)+1;
}
+ XdmItem item = nodes.itemAt(indexToMatch-1);
+ matchStrings.add(fragment ? item.toString() : item.getStringValue());
+ } else {
+ if(log.isWarnEnabled()) {
+ log.warn("Error : {}{}", JMeterUtils.getResString("xpath2_extractor_match_number_failure"),indexToMatch);
+ }
+ }
+ }
+ } finally {
+ if(selector != null) {
+ try {
+ selector.getUnderlyingXPathContext().setContextItem(null);
+ } catch (Exception e) { // NOSONAR Ignored on purpose
+ // NOOP
}
}
}
@@ -560,9 +539,9 @@ private static void addToList(XMLStreamReader reader, List res) {
/**
*
* @param document XML Document
- * @return {@link PrefixResolver}
+ * @return {@link PropertiesBasedPrefixResolver}
*/
- private static PrefixResolver getPrefixResolver(Document document) {
+ private static PropertiesBasedPrefixResolver getPrefixResolver(Document document) {
return new PropertiesBasedPrefixResolver(document.getDocumentElement());
}
@@ -573,24 +552,30 @@ private static PrefixResolver getPrefixResolver(Document document) {
* @throws TransformerException if expression fails to evaluate
*/
public static void validateXPath(Document document, String xpathString) throws TransformerException {
- if (XPathAPI.eval(document, xpathString, getPrefixResolver(document)) == 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");
+ try {
+ XPath xPath = newXPath(document);
+ if (xPath.evaluate(xpathString, document) == 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");
+ }
+ } catch (XPathExpressionException|XPathFactoryConfigurationException e) {
+ throw new TransformerException(e);
}
}
/**
*
* @param document XML Document
- * @param namespaces String series of prefix/namespace values separateur by line break
- * @return {@link PrefixResolver}
+ * @param namespaces String series of prefix/namespace values separator by line break
+ * @return {@link PropertiesBasedPrefixResolverForXpath2}
*/
- private static PrefixResolver getPrefixResolverForXPath2(Document document,String namespaces) {
- return new PropertiesBasedPrefixResolverForXpath2(document.getDocumentElement(),namespaces);
+ private static PropertiesBasedPrefixResolverForXpath2 getPrefixResolverForXPath2(Document document, String namespaces) {
+ return new PropertiesBasedPrefixResolverForXpath2(document.getDocumentElement(), namespaces);
}
+
/**
* Validate xpathString is a valid XPath expression
* @param document XML Document
@@ -598,15 +583,21 @@ private static PrefixResolver getPrefixResolverForXPath2(Document document,Strin
* @param namespaces Space separated set of prefix=namespace
* @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");
+ public static void validateXPath2(Document document, String xpathString, String namespaces) throws TransformerException {
+ try {
+ XPath xPath = newXPath(getPrefixResolverForXPath2(document, namespaces));
+ if (xPath.evaluate(xpathString, document) == 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");
+ }
+ } catch (XPathExpressionException|XPathFactoryConfigurationException e) {
+ throw new TransformerException(e);
}
}
+
/**
* Fills result
* @param result {@link AssertionResult}
@@ -618,116 +609,83 @@ public static void computeAssertionResult(AssertionResult result,
Document doc,
String xPathExpression,
boolean isNegated) {
+ net.sf.saxon.s9api.DocumentBuilder builder = PROCESSOR.newDocumentBuilder();
try {
- XObject xObject = XPathAPI.eval(doc, xPathExpression, getPrefixResolver(doc));
- switch (xObject.getType()) {
- case XObject.CLASS_NODESET:
- NodeList nodeList = xObject.nodelist();
- final int len = (nodeList != null) ? nodeList.getLength() : 0;
- log.debug("nodeList length {}", len);
- // length == 0 means nodelist is null
- if (len == 0) {
- log.debug("nodeList is null or empty. No match by xpath expression: {}", xPathExpression);
- result.setFailure(!isNegated);
- result.setFailureMessage("No Nodes Matched " + xPathExpression);
- return;
+ XdmNode xdmNode = builder.build(new DOMSource(doc));
+ String namespaces = getPrefixResolver(doc).getNamespacesAsLineDelimitedProperties();
+ computeAssertionResultUsingSaxon(result, xdmNode, xPathExpression, namespaces, isNegated);
+ } catch (Exception e) {
+ result.setError(true);
+ result.setFailureMessage("TransformerException: " + unwrapException(e).getMessage() + " for: " + xPathExpression);
+ }
+ }
+
+ /***
+ *
+ * @param result The result of xpath2 assertion
+ * @param xmlFile XML data
+ * @param xPathQuery XPath Query
+ * @param namespaces Space separated set of prefix=namespace
+ * @param isNegated invert result
+ * @throws SaxonApiException when the parser has problems with the given xml or xpath query
+ * @throws FactoryConfigurationError when the parser can not be instantiated
+ */
+ public static void computeAssertionResultUsingSaxon(AssertionResult result, String xmlFile, String xPathQuery,
+ String namespaces, Boolean isNegated) throws SaxonApiException, FactoryConfigurationError {
+ try (StringReader reader = new StringReader(xmlFile)) {
+ // We could instantiate 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)));
+ computeAssertionResultUsingSaxon(result, xdmNode, xPathQuery, namespaces, isNegated);
+ }
+ }
+
+ private static void computeAssertionResultUsingSaxon(AssertionResult result, XdmNode xdmNode, String xPathQuery,
+ String namespaces, Boolean isNegated) throws SaxonApiException, FactoryConfigurationError {
+ // generating the cache key
+ final ImmutablePair 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;
+ }
+ if (xPathExecutable != null) {
+ XPathSelector selector = null;
+ try {
+ 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 (nodes.itemAt(0).matches(ItemType.BOOLEAN)) {
+ resultOfEval = Boolean.parseBoolean(nodes.itemAt(0).getStringValue());
}
- if (log.isDebugEnabled() && !isNegated) {
- for (int i = 0; i < len; i++) {
- log.debug("nodeList[{}]: {}", i, nodeList.item(i));
+ result.setFailure(isNegated ? resultOfEval : !resultOfEval);
+ result.setFailureMessage(
+ isNegated ? "Nodes Matched for " + xPathQuery : "No Nodes Matched for " + xPathQuery);
+ } finally {
+ if (selector != null) {
+ try {
+ selector.getUnderlyingXPathContext().setContextItem(null);
+ } catch (Exception e) { // NOSONAR Ignored on purpose
+ result.setError(true);
+ result.setFailureMessage("Exception: " + e.getMessage() + " for:" + xPathQuery);
}
}
- result.setFailure(isNegated);
- if (isNegated) {
- result.setFailureMessage("Specified XPath was found... Turn off negate if this is not desired");
- }
- return;
- case XObject.CLASS_BOOLEAN:
- boolean resultOfEval = xObject.bool();
- result.setFailure(isNegated ? resultOfEval : !resultOfEval);
- result.setFailureMessage(isNegated ?
- "Nodes Matched for " + xPathExpression
- : "No Nodes Matched for " + xPathExpression);
- return;
- default:
- result.setFailure(true);
- result.setFailureMessage("Cannot understand: " + xPathExpression);
- return;
}
- } catch (TransformerException e) {
- result.setError(true);
- result.setFailureMessage("TransformerException: " + e.getMessage() + " for: " + xPathExpression);
}
}
-
- /***
- *
- * @param result The result of xpath2 assertion
- * @param xmlFile XML data
- * @param xPathQuery XPath Query
- * @param namespaces Space separated set of prefix=namespace
- * @param isNegated invert result
- * @throws SaxonApiException when the parser has problems with the given xml or xpath query
- * @throws FactoryConfigurationError when the parser can not be instantiated
- */
- public static void computeAssertionResultUsingSaxon(AssertionResult result, String xmlFile, String xPathQuery,
- String namespaces, Boolean isNegated) throws SaxonApiException, FactoryConfigurationError {
- // generating the cache key
- final ImmutablePair 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 instantiate 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.parseBoolean(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) { // NOSONAR Exception handled by return
- result.setError(true);
- result.setFailureMessage("Exception: " + e.getMessage() + " for:" + xPathQuery);
- } finally {
- if (selector != null) {
- try {
- selector.getUnderlyingXPathContext().setContextItem(null);
- } catch (Exception e) { // NOSONAR Ignored on purpose
- result.setError(true);
- result.setFailureMessage("Exception: " + e.getMessage() + " for:" + xPathQuery);
- }
- }
- }
- }
- }
- }
-
/**
* Formats XML
* @param xml string to format
@@ -735,11 +693,12 @@ public static void computeAssertionResultUsingSaxon(AssertionResult result, Stri
*/
public static String formatXml(String xml){
try {
- TransformerFactory factory = TransformerFactory.newInstance();
+ SaxonTransformerFactory factory = new SaxonTransformerFactory();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
- Transformer serializer= factory.newTransformer();
+ Transformer serializer = factory.newTransformer();
+ serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
- serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
+ //serializer.setOutputProperty("{http://saxon.sf.net/}indent-spaces", "2");
Source xmlSource = new SAXSource(new InputSource(new StringReader(xml)));
StringWriter stringWriter = new StringWriter();
StreamResult res = new StreamResult(stringWriter);
@@ -750,4 +709,23 @@ public static String formatXml(String xml){
}
}
+ private static XPath newXPath(Document document) throws XPathFactoryConfigurationException {
+ return newXPath(getPrefixResolver(document));
+ }
+
+ private static XPath newXPath(NamespaceContext namespaceContext) throws XPathFactoryConfigurationException {
+ XPathFactoryImpl xPathFactory = new XPathFactoryImpl();
+ xPathFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ XPath xPath = xPathFactory.newXPath();
+ xPath.setNamespaceContext(namespaceContext);
+ return xPath;
+ }
+
+ private static Throwable unwrapException(Throwable t) {
+ Throwable cause = t.getCause();
+ if (cause != null && cause != t) {
+ return unwrapException(cause);
+ }
+ return t;
+ }
}
diff --git a/src/core/src/test/java/org/apache/jmeter/util/XPathUtilTest.java b/src/core/src/test/java/org/apache/jmeter/util/XPathUtilTest.java
index d916487f10d..70c1f344371 100644
--- a/src/core/src/test/java/org/apache/jmeter/util/XPathUtilTest.java
+++ b/src/core/src/test/java/org/apache/jmeter/util/XPathUtilTest.java
@@ -78,7 +78,7 @@ public void testBug63033() throws SaxonApiException {
}
@Test
- public void testputValuesForXPathInListUsingSaxon() throws SaxonApiException, FactoryConfigurationError{
+ public void testputValuesForXPathInListUsingSaxon() throws Exception {
String xPathQuery="//Employees/Employee/role";
ArrayList matchStrings = new ArrayList();
boolean fragment = false;
@@ -147,8 +147,8 @@ public void testnamespacesParse(String namespaces, String key, String value, int
@Test
public void testFormatXmlSimple() {
assertThat(XPathUtil.formatXml("Test"),
- CoreMatchers.is(""
- + "Test" + lineSeparator));
+ CoreMatchers.is("" + "\n"
+ + "Test" + "\n"));
}
@Test
@@ -156,11 +156,12 @@ public void testFormatXmlComplex() {
assertThat(
XPathUtil.formatXml(
"..."),
- CoreMatchers.is(String.join(lineSeparator, "",
- " ",
- " ",
- " ",
- " ...",
+ CoreMatchers.is(String.join("\n", "",
+ "",
+ " ",
+ " ",
+ " ",
+ " ...",
"")));
}
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/XPathFileContainer.java b/src/functions/src/main/java/org/apache/jmeter/functions/XPathFileContainer.java
index 7c6e998428c..dfa20ebe23b 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/XPathFileContainer.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/XPathFileContainer.java
@@ -62,7 +62,7 @@ public XPathFileContainer(String file, String xpath) throws FileNotFoundExceptio
nodeList=load(xpath);
}
- private NodeList load(String xpath) throws IOException, FileNotFoundException, ParserConfigurationException, SAXException,
+ private NodeList load(String xpath) throws IOException, ParserConfigurationException, SAXException,
TransformerException {
NodeList nl = null;
try ( FileInputStream fis = new FileInputStream(fileName);