Skip to content

Commit 9653f8f

Browse files
driesvaoutofcoffee
authored andcommitted
feat: support XML attributes in SOAP request body XPath
Function to extract XPath attribute values in addition to other content values (such as XML elements). fixes #555
1 parent c271204 commit 9653f8f

File tree

2 files changed

+36
-3
lines changed

2 files changed

+36
-3
lines changed

core/engine/src/main/java/io/gatehill/imposter/util/BodyQueryUtil.kt

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,12 @@ import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider
4949
import io.gatehill.imposter.http.HttpExchange
5050
import org.apache.logging.log4j.LogManager
5151
import org.apache.logging.log4j.Logger
52+
import org.jdom2.Attribute
53+
import org.jdom2.Content
5254
import org.jdom2.Document
5355
import org.jdom2.Element
5456
import org.jdom2.Namespace
57+
import org.jdom2.filter.Filter
5558
import org.jdom2.filter.Filters
5659
import org.jdom2.input.SAXBuilder
5760
import org.jdom2.xpath.XPathExpression
@@ -73,13 +76,17 @@ object BodyQueryUtil {
7376
.build()
7477
)
7578

76-
private fun buildXPath(expression: String, xPathNamespaces: List<Namespace> = emptyList()): XPathExpression<*> {
79+
private fun buildXPath(
80+
expression: String,
81+
xPathNamespaces: List<Namespace> = emptyList(),
82+
filter: Filter<*> = Filters.element()
83+
): XPathExpression<*> {
7784
val finalXPath = if (expression.startsWith('!')) {
7885
normaliseXPathExpression(expression.substring(1))
7986
} else {
8087
expression
8188
}
82-
return XPathFactory.instance().compile(finalXPath, Filters.element(), emptyMap(), xPathNamespaces)
89+
return XPathFactory.instance().compile(finalXPath, filter, emptyMap(), xPathNamespaces)
8390
}
8491

8592
private fun buildNamespaces(namespaces: Map<String, String>?) =
@@ -144,14 +151,23 @@ object BodyQueryUtil {
144151
} else {
145152
try {
146153
val document = getRequestXmlDocument(httpExchange, body)
147-
selectSingleNode(document, xPath, buildNamespaces(xmlNamespaces))?.value
154+
getXPathValue(document, xPath, buildNamespaces(xmlNamespaces))
148155
} catch (e: Exception) {
149156
logger.warn("Error evaluating XPath expression '$xPath' against request body for ${LogUtil.describeRequest(httpExchange)}", e)
150157
null
151158
}
152159
}
153160
}
154161

162+
fun getXPathValue(context: Any, expression: String, xPathNamespaces: List<Namespace>): String? {
163+
val xPath = buildXPath(expression, xPathNamespaces, Filters.fpassthrough())
164+
return when (val result = xPath.evaluateFirst(context)) {
165+
is Content -> result.value // matches also Element
166+
is Attribute -> result.value
167+
else -> null
168+
}
169+
}
170+
155171
/**
156172
* Gets the XML document for XPath queries against the request body.
157173
* The document is cached in the [HttpExchange].

core/engine/src/test/java/io/gatehill/imposter/util/BodyQueryUtilTest.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import org.hamcrest.MatcherAssert.assertThat
4949
import org.hamcrest.Matchers.equalTo
5050
import org.hamcrest.core.IsEqual
5151
import org.jdom2.Document
52+
import org.jdom2.Namespace
5253
import org.jdom2.input.SAXBuilder
5354
import org.junit.Test
5455
import org.mockito.Mockito.anyString
@@ -107,6 +108,22 @@ class BodyQueryUtilTest {
107108
assertThat(result, equalTo("10"))
108109
}
109110

111+
@Test
112+
fun `query document attribute XPath`() {
113+
val body = """<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
114+
<env:Header/>
115+
<env:Body>
116+
<pets:getPetByIdRequest xmlns:pets="urn:com:example:petstore" id="10" />
117+
</env:Body>
118+
</env:Envelope>"""
119+
120+
val document = SAXBuilder().build(StringReader(body))
121+
val xPath = "//pets:getPetByIdRequest/@id"
122+
val result = BodyQueryUtil.getXPathValue(document, xPath,
123+
listOf(Namespace.getNamespace("pets", "urn:com:example:petstore")))
124+
assertThat(result, equalTo("10"))
125+
}
126+
110127
@Test
111128
fun normaliseXPathExpression() {
112129
val normalised = BodyQueryUtil.normaliseXPathExpression("//foo/bar/text()")

0 commit comments

Comments
 (0)