Skip to content

Commit 59cc2dc

Browse files
committed
refactor(soap): reuses schema type system and entity resolver for same WSDL.
1 parent 5bd9038 commit 59cc2dc

File tree

12 files changed

+57
-50
lines changed

12 files changed

+57
-50
lines changed

mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/SoapPluginImpl.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,9 @@ class SoapPluginImpl @Inject constructor(
244244

245245
// build a response from the XSD
246246
val exampleSender = ResponseSender { httpExchange: HttpExchange, _: ResponseBehaviour ->
247-
val wsdlDir = File(pluginConfig.dir, pluginConfig.wsdlFile!!).parentFile
248247
soapExampleService.serveExample(
249248
httpExchange,
250-
parser.schemas,
251-
wsdlDir,
249+
parser.schemaContext,
252250
service,
253251
operation,
254252
bodyHolder

mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/parser/AbstractWsdlParser.kt

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,16 @@ abstract class AbstractWsdlParser(
6969
) : WsdlParser {
7070
protected val logger: Logger = LogManager.getLogger(this::class.java)
7171

72-
override val schemas: Array<SchemaDocument> by lazy { discoverSchemas() }
73-
74-
protected val xsd: SchemaTypeSystem by lazy { buildXsdFromSchemas() }
72+
override val schemaContext: SchemaContext by lazy {
73+
val schemas = discoverSchemas()
74+
SchemaContext(
75+
schemas = schemas,
76+
sts = compileSchemas(schemas),
77+
entityResolver = entityResolver,
78+
)
79+
}
7580

76-
private val unionTypeSystem by lazy { XmlBeans.typeLoaderUnion(XmlBeans.getBuiltinTypeSystem(), xsd) }
81+
private val unionTypeSystem by lazy { XmlBeans.typeLoaderUnion(XmlBeans.getBuiltinTypeSystem(), schemaContext.sts) }
7782

7883
private fun discoverSchemas(): Array<SchemaDocument> {
7984
val schemas = mutableListOf<SchemaDocument>()
@@ -121,7 +126,7 @@ abstract class AbstractWsdlParser(
121126

122127
protected abstract fun findEmbeddedTypesSchemaNodes(): List<Element>
123128

124-
private fun buildXsdFromSchemas(): SchemaTypeSystem {
129+
private fun compileSchemas(schemas: Array<SchemaDocument>): SchemaTypeSystem {
125130
if (schemas.isEmpty()) {
126131
throw IllegalStateException("Cannot build XSD from empty schema list")
127132
}
@@ -156,13 +161,13 @@ abstract class AbstractWsdlParser(
156161
* from within the XSD.
157162
*/
158163
protected fun resolveElementTypeFromXsd(elementQName: QName): QName? {
159-
val matchingElement = xsd.findElement(elementQName)
164+
val matchingElement = schemaContext.sts.findElement(elementQName)
160165
?: return null
161166

162167
var elementType = matchingElement.type.name
163168

164169
if (elementType.prefix.isNullOrBlank()) {
165-
val prefix = if (elementType.namespaceURI == "http://www.w3.org/2001/XMLSchema") {
170+
val prefix = if (elementType.namespaceURI == SoapUtil.NS_XML_SCHEMA) {
166171
"xs"
167172
} else {
168173
// TODO consider prefix clashes - generate unique prefix?
@@ -184,7 +189,7 @@ abstract class AbstractWsdlParser(
184189
?: return null
185190

186191
if (matchingType.prefix.isNullOrBlank()) {
187-
val prefix = if (matchingType.namespaceURI == "http://www.w3.org/2001/XMLSchema") {
192+
val prefix = if (matchingType.namespaceURI == SoapUtil.NS_XML_SCHEMA) {
188193
"xs"
189194
} else {
190195
// TODO consider prefix clashes - generate unique prefix?

mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/parser/VersionAwareWsdlParser.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ import io.gatehill.imposter.plugin.soap.model.WsdlBinding
4747
import io.gatehill.imposter.plugin.soap.model.WsdlInterface
4848
import io.gatehill.imposter.plugin.soap.model.WsdlService
4949
import org.apache.logging.log4j.LogManager
50-
import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument
5150
import org.jdom2.input.SAXBuilder
5251
import java.io.File
5352

@@ -62,7 +61,9 @@ class VersionAwareWsdlParser(wsdlFile: File) : WsdlParser {
6261
private val delegate: WsdlParser
6362

6463
init {
64+
// resolve relative to the parent directory of the WSDL file
6565
val entityResolver = WsdlRelativeXsdEntityResolver(wsdlFile.parentFile)
66+
6667
val document = SAXBuilder().apply { setEntityResolver(entityResolver) }.build(wsdlFile)
6768

6869
val wsdlNamespaces = document.rootElement.namespacesInScope.filter {
@@ -83,8 +84,8 @@ class VersionAwareWsdlParser(wsdlFile: File) : WsdlParser {
8384
override val version: WsdlParser.WsdlVersion
8485
get() = delegate.version
8586

86-
override val schemas: Array<SchemaDocument>
87-
get() = delegate.schemas
87+
override val schemaContext: SchemaContext
88+
get() = delegate.schemaContext
8889

8990
override val services: List<WsdlService>
9091
get() = delegate.services

mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/parser/Wsdl1Parser.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ class Wsdl1Parser(
267267
)
268268

269269
override fun findEmbeddedTypesSchemaNodes(): List<Element> {
270-
val xsNamespaces = xPathNamespaces + Namespace.getNamespace("xs", "http://www.w3.org/2001/XMLSchema")
270+
val xsNamespaces = xPathNamespaces + Namespace.getNamespace("xs", SoapUtil.NS_XML_SCHEMA)
271271
return BodyQueryUtil.selectNodes(document, "/wsdl:definitions/wsdl:types/xs:schema", xsNamespaces)
272272
}
273273

mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/parser/Wsdl2Parser.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import io.gatehill.imposter.plugin.soap.model.WsdlEndpoint
5151
import io.gatehill.imposter.plugin.soap.model.WsdlInterface
5252
import io.gatehill.imposter.plugin.soap.model.WsdlOperation
5353
import io.gatehill.imposter.plugin.soap.model.WsdlService
54+
import io.gatehill.imposter.plugin.soap.util.SoapUtil
5455
import io.gatehill.imposter.plugin.soap.util.SoapUtil.toNamespaceMap
5556
import io.gatehill.imposter.util.BodyQueryUtil
5657
import org.jdom2.Document
@@ -204,7 +205,7 @@ class Wsdl2Parser(
204205
)
205206

206207
override fun findEmbeddedTypesSchemaNodes(): List<Element> {
207-
val xsNamespaces = xPathNamespaces + Namespace.getNamespace("xs", "http://www.w3.org/2001/XMLSchema")
208+
val xsNamespaces = xPathNamespaces + Namespace.getNamespace("xs", SoapUtil.NS_XML_SCHEMA)
208209
return BodyQueryUtil.selectNodes(document, "/wsdl:description/wsdl:types/xs:schema", xsNamespaces)
209210
}
210211

mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/parser/WsdlParser.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,13 @@ package io.gatehill.imposter.plugin.soap.parser
4646
import io.gatehill.imposter.plugin.soap.model.WsdlBinding
4747
import io.gatehill.imposter.plugin.soap.model.WsdlInterface
4848
import io.gatehill.imposter.plugin.soap.model.WsdlService
49+
import org.apache.xmlbeans.SchemaTypeSystem
4950
import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument
51+
import org.xml.sax.EntityResolver
5052

5153
interface WsdlParser {
5254
val version: WsdlVersion
53-
val schemas: Array<SchemaDocument>
55+
val schemaContext: SchemaContext
5456
val services: List<WsdlService>
5557
fun getBinding(bindingName: String): WsdlBinding?
5658
fun getInterface(interfaceName: String): WsdlInterface?
@@ -60,3 +62,9 @@ interface WsdlParser {
6062
V2,
6163
}
6264
}
65+
66+
class SchemaContext(
67+
val schemas: Array<SchemaDocument>,
68+
val sts: SchemaTypeSystem,
69+
val entityResolver: EntityResolver,
70+
)

mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/service/SoapExampleService.kt

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import io.gatehill.imposter.plugin.soap.model.ParsedSoapMessage
5353
import io.gatehill.imposter.plugin.soap.model.TypeOperationMessage
5454
import io.gatehill.imposter.plugin.soap.model.WsdlOperation
5555
import io.gatehill.imposter.plugin.soap.model.WsdlService
56-
import io.gatehill.imposter.plugin.soap.parser.WsdlRelativeXsdEntityResolver
56+
import io.gatehill.imposter.plugin.soap.parser.SchemaContext
5757
import io.gatehill.imposter.plugin.soap.util.SchemaGenerator
5858
import io.gatehill.imposter.plugin.soap.util.SoapUtil
5959
import io.gatehill.imposter.util.LogUtil
@@ -67,7 +67,6 @@ import org.apache.xmlbeans.XmlException
6767
import org.apache.xmlbeans.XmlOptions
6868
import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument
6969
import org.apache.xmlbeans.impl.xsd2inst.SampleXmlUtil
70-
import java.io.File
7170
import javax.xml.namespace.QName
7271

7372

@@ -81,40 +80,37 @@ class SoapExampleService {
8180

8281
fun serveExample(
8382
httpExchange: HttpExchange,
84-
schemas: Array<SchemaDocument>,
85-
wsdlDir: File,
83+
schemaContext: SchemaContext,
8684
service: WsdlService,
8785
operation: WsdlOperation,
8886
bodyHolder: MessageBodyHolder,
8987
): Boolean {
9088
logger.debug("Generating example for {}", operation.outputRef)
9189
val example = when (operation.style) {
92-
SoapUtil.OPERATION_STYLE_DOCUMENT -> generateDocumentResponse(wsdlDir, schemas, service, operation)
93-
SoapUtil.OPERATION_STYLE_RPC -> generateRpcResponse(wsdlDir, schemas, service, operation)
90+
SoapUtil.OPERATION_STYLE_DOCUMENT -> generateDocumentResponse(schemaContext, service, operation)
91+
SoapUtil.OPERATION_STYLE_RPC -> generateRpcResponse(schemaContext, service, operation)
9492
else -> throw UnsupportedOperationException("Unsupported operation style: ${operation.style}")
9593
}
9694
transmitExample(httpExchange, example, bodyHolder)
9795
return true
9896
}
9997

10098
private fun generateDocumentResponse(
101-
wsdlDir: File,
102-
schemas: Array<SchemaDocument>,
99+
schemaContext: SchemaContext,
103100
service: WsdlService,
104101
operation: WsdlOperation,
105102
): String {
106103
return operation.outputRef?.let { message ->
107104
// no root element in document style
108-
generateDocumentMessage(wsdlDir, schemas, service, message).joinToString("\n")
105+
generateDocumentMessage(schemaContext, service, message).joinToString("\n")
109106

110107
} ?: throw IllegalStateException(
111108
"No output message for operation: $operation"
112109
)
113110
}
114111

115112
private fun generateRpcResponse(
116-
wsdlDir: File,
117-
schemas: Array<SchemaDocument>,
113+
schemaContext: SchemaContext,
118114
service: WsdlService,
119115
operation: WsdlOperation,
120116
): String {
@@ -129,7 +125,7 @@ class SoapExampleService {
129125
}
130126

131127
val elementSchema = SchemaGenerator.createCompositePartSchema(service.targetNamespace ?: "", rootElement, parts)
132-
val sts: SchemaTypeSystem = buildSchemaTypeSystem(wsdlDir, schemas + elementSchema)
128+
val sts: SchemaTypeSystem = buildSchemaTypeSystem(schemaContext, elementSchema)
133129

134130
val elem = sts.documentTypes().find { it.documentElementName == rootElement }
135131
?: throw RuntimeException("Could not find a generated element with name \"$rootElement\"")
@@ -138,24 +134,23 @@ class SoapExampleService {
138134
}
139135

140136
private fun generateDocumentMessage(
141-
wsdlDir: File,
142-
schemas: Array<SchemaDocument>,
137+
schemaContext: SchemaContext,
143138
service: WsdlService,
144139
message: OperationMessage,
145140
): List<String> {
146141
val partXmls = mutableListOf<String>()
147142

148143
when (message) {
149144
is ElementOperationMessage -> {
150-
partXmls += generateElementExample(wsdlDir, schemas, message)
145+
partXmls += generateElementExample(schemaContext, message)
151146
}
152147

153148
is TypeOperationMessage -> {
154-
partXmls += generateTypeExample(wsdlDir, schemas, service, message)
149+
partXmls += generateTypeExample(schemaContext, service, message)
155150
}
156151

157152
is CompositeOperationMessage -> {
158-
partXmls += message.parts.flatMap { part -> generateDocumentMessage(wsdlDir, schemas, service, part) }
153+
partXmls += message.parts.flatMap { part -> generateDocumentMessage(schemaContext, service, part) }
159154
}
160155

161156
else -> throw UnsupportedOperationException(
@@ -168,28 +163,24 @@ class SoapExampleService {
168163
}
169164

170165
private fun generateElementExample(
171-
wsdlDir: File,
172-
schemas: Array<SchemaDocument>,
166+
schemaContext: SchemaContext,
173167
outputRef: ElementOperationMessage,
174168
): String {
175-
val sts: SchemaTypeSystem = buildSchemaTypeSystem(wsdlDir, schemas)
176-
177169
val rootElementName = outputRef.elementName
178-
val elem: SchemaType = sts.documentTypes().find { it.documentElementName == rootElementName }
170+
val elem: SchemaType = schemaContext.sts.documentTypes().find { it.documentElementName == rootElementName }
179171
?: throw RuntimeException("Could not find a global element with name \"$rootElementName\"")
180172

181173
return SampleXmlUtil.createSampleForType(elem)
182174
}
183175

184176
private fun generateTypeExample(
185-
wsdlDir: File,
186-
schemas: Array<SchemaDocument>,
177+
schemaContext: SchemaContext,
187178
service: WsdlService,
188179
message: TypeOperationMessage,
189180
): String {
190181
val elementName = message.partName
191182
val elementSchema = SchemaGenerator.createSinglePartSchema(service.targetNamespace ?: "", message)
192-
val sts: SchemaTypeSystem = buildSchemaTypeSystem(wsdlDir, schemas + elementSchema)
183+
val sts: SchemaTypeSystem = buildSchemaTypeSystem(schemaContext, elementSchema)
193184

194185
val elem = sts.documentTypes().find { it.documentElementName.localPart == elementName }
195186
?: throw RuntimeException("Could not find a generated element with name \"$elementName\"")
@@ -198,21 +189,23 @@ class SoapExampleService {
198189
}
199190

200191
private fun buildSchemaTypeSystem(
201-
wsdlDir: File,
202-
schemas: Array<SchemaDocument>,
192+
schemaContext: SchemaContext,
193+
vararg extraSchemas: SchemaDocument,
203194
): SchemaTypeSystem {
204195
var sts: SchemaTypeSystem? = null
196+
197+
val schemas = schemaContext.schemas + extraSchemas
205198
if (schemas.isNotEmpty()) {
206199
val errors = mutableListOf<XmlError>()
207200

208201
val compileOptions = XmlOptions()
209202
.setLoadLineNumbers()
210203
.setLoadMessageDigest()
211-
.setEntityResolver(WsdlRelativeXsdEntityResolver(wsdlDir))
204+
.setEntityResolver(schemaContext.entityResolver)
212205
.setErrorListener(errors)
213206

214207
try {
215-
// TODO consider reusing the SchemaTypeSystem from AbstractWsdlParser.buildXsdFromSchemas
208+
// TODO consider reusing the SchemaTypeSystem from SchemaContext
216209
sts = XmlBeans.compileXsd(schemas, XmlBeans.getBuiltinTypeSystem(), compileOptions)
217210
} catch (e: Exception) {
218211
if (errors.isEmpty() || e !is XmlException) {

mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/util/SchemaGenerator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ ${partsXml.prependIndent(" ".repeat(10))}
124124
): String {
125125
parts.forEach { part -> part.namespaces.forEach { namespaces += it } }
126126
if (!namespaces.containsKey("xs")) {
127-
namespaces["xs"] = "http://www.w3.org/2001/XMLSchema"
127+
namespaces["xs"] = SoapUtil.NS_XML_SCHEMA
128128
}
129129
val namespacesXml = namespaces.entries.joinToString(separator = "\n") { (prefix, nsUri) ->
130130
"""xmlns:${prefix}="${nsUri}""""

mock/soap/src/main/java/io/gatehill/imposter/plugin/soap/util/SoapUtil.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import javax.xml.namespace.QName
5757
object SoapUtil {
5858
const val OPERATION_STYLE_DOCUMENT = "document"
5959
const val OPERATION_STYLE_RPC = "rpc"
60+
const val NS_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"
6061
const val textXmlContentType = "text/xml"
6162
const val soap11ContentType = textXmlContentType
6263
const val soap12ContentType = "application/soap+xml"

mock/soap/src/test/java/io/gatehill/imposter/plugin/soap/parser/Wsdl1Soap11ParserTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,6 @@ class Wsdl1Soap11ParserTest {
134134
// first schema is from the WSDL types element and just contains an import
135135
// second schema is the imported external XSD
136136
// third schema is the embedded schema from the WSDL types element
137-
assertEquals(3, parser.schemas.size)
137+
assertEquals(3, parser.schemaContext.schemas.size)
138138
}
139139
}

0 commit comments

Comments
 (0)