Skip to content

Commit 5b0adf7

Browse files
committed
refactor(soap): reuses parser schema type system instead of recompiling all schemas.
1 parent 637cc80 commit 5b0adf7

File tree

5 files changed

+58
-77
lines changed

5 files changed

+58
-77
lines changed

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

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@
4343

4444
package io.gatehill.imposter.plugin.soap.parser
4545

46+
import io.gatehill.imposter.plugin.soap.util.SchemaUtil
4647
import io.gatehill.imposter.plugin.soap.util.SoapUtil
4748
import io.gatehill.imposter.util.BodyQueryUtil
4849
import org.apache.logging.log4j.LogManager
4950
import org.apache.logging.log4j.Logger
50-
import org.apache.xmlbeans.SchemaTypeSystem
5151
import org.apache.xmlbeans.XmlBeans
5252
import org.apache.xmlbeans.XmlOptions
5353
import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument
@@ -71,11 +71,8 @@ abstract class AbstractWsdlParser(
7171

7272
override val schemaContext: SchemaContext by lazy {
7373
val schemas = discoverSchemas()
74-
SchemaContext(
75-
schemas = schemas,
76-
sts = compileSchemas(schemas),
77-
entityResolver = entityResolver,
78-
)
74+
val sts = SchemaUtil.compileSchemas(XmlBeans.getBuiltinTypeSystem(), entityResolver, schemas)
75+
SchemaContext(schemas, sts, entityResolver)
7976
}
8077

8178
private val unionTypeSystem by lazy { XmlBeans.typeLoaderUnion(XmlBeans.getBuiltinTypeSystem(), schemaContext.sts) }
@@ -126,21 +123,6 @@ abstract class AbstractWsdlParser(
126123

127124
protected abstract fun findEmbeddedTypesSchemaNodes(): List<Element>
128125

129-
private fun compileSchemas(schemas: Array<SchemaDocument>): SchemaTypeSystem {
130-
if (schemas.isEmpty()) {
131-
throw IllegalStateException("Cannot build XSD from empty schema list")
132-
}
133-
val compileOptions = XmlOptions()
134-
.setLoadLineNumbers()
135-
.setLoadMessageDigest()
136-
.setEntityResolver(entityResolver)
137-
try {
138-
return XmlBeans.compileXsd(schemas, XmlBeans.getBuiltinTypeSystem(), compileOptions)
139-
} catch (e: Exception) {
140-
throw RuntimeException("Schema compilation errors", e)
141-
}
142-
}
143-
144126
protected fun selectSingleNodeWithName(context: Any, expressionTemplate: String, name: String): Element? {
145127
return selectSingleNode(context, String.format(expressionTemplate, name))
146128
?: name.takeIf { it.contains(":") }?.let {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,10 @@ class Wsdl1Parser(
178178
name = operationName
179179
) ?: throw IllegalStateException("No portType operation found for portType: $portTypeName and operation: $operationName")
180180

181-
val input = getMessage(operationName, portTypeOperation, "./wsdl:input")
181+
val input = getMessage(portTypeOperation, "./wsdl:input")
182182
?: throw IllegalStateException("No input found for portType operation: $operationName")
183183

184-
val output = getMessage(operationName, portTypeOperation, "./wsdl:output")
184+
val output = getMessage(portTypeOperation, "./wsdl:output")
185185
?: throw IllegalStateException("No output found for portType operation: $operationName")
186186

187187
val style = soapOperation.getAttributeValue("style") ?: run {
@@ -219,7 +219,7 @@ class Wsdl1Parser(
219219
* Extract the WSDL message part element attribute, then attempt
220220
* to resolve it from within the XSD.
221221
*/
222-
private fun getMessage(operationName: String, context: Element, expression: String): OperationMessage? {
222+
private fun getMessage(context: Element, expression: String): OperationMessage? {
223223
val inputOrOutputNode = selectSingleNode(context, expression)
224224
?: throw IllegalStateException("No input or output found for: $expression")
225225

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,10 @@ class Wsdl2Parser(
165165

166166
private fun parseOperation(operationName: String, operation: Element): WsdlOperation {
167167
val soapOperation = selectSingleNode(operation, "./soap:operation") ?: throw IllegalStateException(
168-
"Unable to find soap:operation for operation ${operationName}"
168+
"Unable to find soap:operation for operation $operationName"
169169
)
170-
val input = getMessage(operationName, operation, "./wsdl:input")
171-
val output = getMessage(operationName, operation, "./wsdl:output")
170+
val input = getMessage(operation, "./wsdl:input")
171+
val output = getMessage(operation, "./wsdl:output")
172172

173173
return WsdlOperation(
174174
name = operation.getAttributeValue("name"),
@@ -183,7 +183,7 @@ class Wsdl2Parser(
183183
* Extract the WSDL message part element attribute, then attempt
184184
* to resolve it from within the XSD.
185185
*/
186-
private fun getMessage(operationName: String, context: Element, expression: String): OperationMessage? {
186+
private fun getMessage(context: Element, expression: String): OperationMessage? {
187187
val inputOrOutputNode = selectSingleNode(context, expression) ?: throw IllegalStateException(
188188
"Unable to find message part: $expression"
189189
)

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

Lines changed: 9 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,12 @@ 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
5656
import io.gatehill.imposter.plugin.soap.parser.SchemaContext
57-
import io.gatehill.imposter.plugin.soap.util.SchemaGenerator
57+
import io.gatehill.imposter.plugin.soap.util.SchemaUtil
5858
import io.gatehill.imposter.plugin.soap.util.SoapUtil
5959
import io.gatehill.imposter.util.LogUtil
6060
import org.apache.logging.log4j.LogManager
6161
import org.apache.logging.log4j.Logger
6262
import org.apache.xmlbeans.SchemaType
63-
import org.apache.xmlbeans.SchemaTypeSystem
64-
import org.apache.xmlbeans.XmlBeans
65-
import org.apache.xmlbeans.XmlError
66-
import org.apache.xmlbeans.XmlException
67-
import org.apache.xmlbeans.XmlOptions
68-
import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument
6963
import org.apache.xmlbeans.impl.xsd2inst.SampleXmlUtil
7064
import javax.xml.namespace.QName
7165

@@ -114,8 +108,6 @@ class SoapExampleService {
114108
service: WsdlService,
115109
operation: WsdlOperation,
116110
): String {
117-
val parts = operation.outputRef?.let { listOf(it) } ?: emptyList()
118-
119111
// by convention, the suffix 'Response' is added to the operation name
120112
val rootElementName = operation.name + "Response"
121113
val rootElement = if (service.targetNamespace?.isNotBlank() == true) {
@@ -124,8 +116,9 @@ class SoapExampleService {
124116
QName(rootElementName)
125117
}
126118

127-
val elementSchema = SchemaGenerator.createCompositePartSchema(service.targetNamespace ?: "", rootElement, parts)
128-
val sts: SchemaTypeSystem = buildSchemaTypeSystem(schemaContext, elementSchema)
119+
val parts = operation.outputRef?.let { listOf(it) } ?: emptyList()
120+
val elementSchema = SchemaUtil.createCompositePartSchema(service.targetNamespace ?: "", rootElement, parts)
121+
val sts = SchemaUtil.compileSchemas(schemaContext.sts, schemaContext.entityResolver, arrayOf(elementSchema))
129122

130123
val elem = sts.findDocumentType(rootElement)
131124
?: throw RuntimeException("Could not find a generated element with name \"$rootElement\"")
@@ -164,9 +157,9 @@ class SoapExampleService {
164157

165158
private fun generateElementExample(
166159
schemaContext: SchemaContext,
167-
outputRef: ElementOperationMessage,
160+
message: ElementOperationMessage,
168161
): String {
169-
val rootElementName = outputRef.elementName
162+
val rootElementName = message.elementName
170163
val elem: SchemaType = schemaContext.sts.findDocumentType(rootElementName)
171164
?: throw RuntimeException("Could not find a global element with name \"$rootElementName\"")
172165

@@ -178,46 +171,16 @@ class SoapExampleService {
178171
service: WsdlService,
179172
message: TypeOperationMessage,
180173
): String {
181-
val elementName = QName(service.targetNamespace, message.partName)
182-
val elementSchema = SchemaGenerator.createSinglePartSchema(service.targetNamespace ?: "", message)
183-
val sts: SchemaTypeSystem = buildSchemaTypeSystem(schemaContext, elementSchema)
174+
val elementSchema = SchemaUtil.createSinglePartSchema(service.targetNamespace ?: "", message)
175+
val sts = SchemaUtil.compileSchemas(schemaContext.sts, schemaContext.entityResolver, arrayOf(elementSchema))
184176

177+
val elementName = QName(service.targetNamespace, message.partName)
185178
val elem = sts.findDocumentType(elementName)
186179
?: throw RuntimeException("Could not find a generated element with name \"$elementName\"")
187180

188181
return SampleXmlUtil.createSampleForType(elem)
189182
}
190183

191-
private fun buildSchemaTypeSystem(
192-
schemaContext: SchemaContext,
193-
vararg extraSchemas: SchemaDocument,
194-
): SchemaTypeSystem {
195-
var sts: SchemaTypeSystem? = null
196-
197-
val schemas = schemaContext.schemas + extraSchemas
198-
if (schemas.isNotEmpty()) {
199-
val errors = mutableListOf<XmlError>()
200-
201-
val compileOptions = XmlOptions()
202-
.setLoadLineNumbers()
203-
.setLoadMessageDigest()
204-
.setEntityResolver(schemaContext.entityResolver)
205-
.setErrorListener(errors)
206-
207-
try {
208-
// TODO consider reusing the SchemaTypeSystem from SchemaContext
209-
sts = XmlBeans.compileXsd(schemas, XmlBeans.getBuiltinTypeSystem(), compileOptions)
210-
} catch (e: Exception) {
211-
if (errors.isEmpty() || e !is XmlException) {
212-
throw RuntimeException("Error compiling XSD", e)
213-
}
214-
throw RuntimeException("Schema compilation errors: " + errors.joinToString("\n"))
215-
}
216-
}
217-
sts ?: throw IllegalStateException("No schemas to process")
218-
return sts
219-
}
220-
221184
private fun transmitExample(httpExchange: HttpExchange, example: String?, bodyHolder: MessageBodyHolder) {
222185
example ?: run {
223186
logger.warn("No example found - returning empty response")

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

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,50 @@ import io.gatehill.imposter.plugin.soap.model.OperationMessage
4949
import io.gatehill.imposter.plugin.soap.model.TypeOperationMessage
5050
import org.apache.logging.log4j.LogManager
5151
import org.apache.logging.log4j.Logger
52+
import org.apache.xmlbeans.SchemaTypeSystem
53+
import org.apache.xmlbeans.XmlBeans
54+
import org.apache.xmlbeans.XmlError
55+
import org.apache.xmlbeans.XmlException
56+
import org.apache.xmlbeans.XmlOptions
5257
import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument
58+
import org.xml.sax.EntityResolver
5359
import javax.xml.namespace.QName
5460

5561
/**
56-
* Generates schemas for elements.
62+
* Compiles schemas and generates synthetic schemas for entities.
5763
*/
58-
object SchemaGenerator {
59-
private val logger: Logger = LogManager.getLogger(SchemaGenerator::class.java)
64+
object SchemaUtil {
65+
private val logger: Logger = LogManager.getLogger(SchemaUtil::class.java)
66+
67+
/**
68+
* Compiles the schemas, resolving against the base [SchemaTypeSystem] and
69+
* using the specified [EntityResolver], where necessary.
70+
*/
71+
fun compileSchemas(
72+
baseSts: SchemaTypeSystem,
73+
entityResolver: EntityResolver,
74+
schemas: Array<SchemaDocument>,
75+
): SchemaTypeSystem {
76+
if (schemas.isEmpty()) {
77+
throw IllegalStateException("No schemas to compile")
78+
}
79+
80+
val errors = mutableListOf<XmlError>()
81+
val compileOptions = XmlOptions()
82+
.setLoadLineNumbers()
83+
.setLoadMessageDigest()
84+
.setEntityResolver(entityResolver)
85+
.setErrorListener(errors)
86+
87+
try {
88+
return XmlBeans.compileXsd(schemas, baseSts, compileOptions)
89+
} catch (e: Exception) {
90+
if (errors.isEmpty() || e !is XmlException) {
91+
throw RuntimeException("Error compiling schemas", e)
92+
}
93+
throw RuntimeException("Schema compilation errors: " + errors.joinToString("\n"))
94+
}
95+
}
6096

6197
fun createSinglePartSchema(
6298
targetNamespace: String,

0 commit comments

Comments
 (0)