出现了一个需求,其中需要在运行时加载包含许多模式的
xsd.zip
文件。该 ZIP 文件将在类路径上可用,最重要的是,它包含许多模式,其中 xsd:import
指令通过相对路径上下文指向其他模式文件。
# Visual of the uncompressed file:
➜ tree -L 3 app/src/test/resources
app/src/test/resources
├── xsd
│ └── Schemas
│ ├── A
│ ├── B
│ ├── C
│ └── ...
└── xsd.zip
在我的服务中,我有一个枚举,我想为每个枚举关联一个模式:
import javax.xml.transform.stream.StreamSource
import javax.xml.validation.Schema
import org.xml.sax.SAXException
enum class XmlSchemaDefinition(
path: String,
) {
A("Schemas/A.xsd"),
B("Schemas/B.xsd"),
;
@Throws(SAXException::class)
fun validate(xml: String) = schema
.newValidator()
.validate(StreamSource(xml.byteInputStream()))
}
如您所见,我的目标/尝试是加载架构一次,并且对于每个验证调用,都会创建一个新的验证器(因为它不是线程安全的)。但是,每当我尝试通过以下方式加载我的架构时:
private val schema: Schema = run {
val zipResourceUri: URI = Thread.currentThread()
.contextClassLoader
.getResource("xsd.zip")
?.toURI()
?: error("ZIP resource not found on classpath: xsd.zip")
val zipFile = ZipFile(Paths.get(zipResourceUri).toFile())
SchemaFactory
.newInstance(W3C_XML_SCHEMA_NS_URI)
// Load the schema from the ZIP entry's input stream
.newSchema(StreamSource(zipFile.getInputStream(zipFile.getEntry(path))))
}
我得到:
Caused by: org.xml.sax.SAXParseException; lineNumber: 307; columnNumber: 34; src-resolve: Cannot resolve the name 'XXX:XXXXX' to a(n) 'element declaration' component.
经过进一步调查,发现它无法解析元素
xsd:import
所需的 'XXX:XXXXX'
指令。在 Kotlin 中,如何以惰性方式从 ZIP 加载 XSD,同时仍然适应相对的 xsd:import
指令?
import java.net.URI
import javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI
import javax.xml.transform.stream.StreamSource
import javax.xml.validation.Schema
import javax.xml.validation.SchemaFactory
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.core.Logger
import org.xml.sax.SAXException
enum class XmlSchemaDefinition(
path: String,
) {
A("Schemas/A.xsd"),
B("Schemas/B.xsd"),
;
private val schema: Schema by lazy {
SchemaFactory
.newInstance(W3C_XML_SCHEMA_NS_URI)
.apply { LOGGER.debug("Creating new schema for: {}", path) }
.newSchema(URI("jar:$ZIP_RESOURCE_URI!/$path").toURL())
.apply { LOGGER.debug("Loaded XSD schema from ZIP, hashcode: {}", this.hashCode()) }
}
@Throws(SAXException::class)
fun validate(xml: String) = schema
.newValidator()
.validate(StreamSource(xml.byteInputStream()))
companion object {
private const val XSD_ZIP_PATH: String = "xsd.zip"
private val LOGGER = LogManager
.getLogger(XmlSchemaDefinition::class.java) as Logger
private val ZIP_RESOURCE_URI = Thread
.currentThread().contextClassLoader
.getResource(XSD_ZIP_PATH)
?: error("ZIP resource not found on classpath: $XSD_ZIP_PATH")
}
}
大部分问题来自于使用
StreamSource
,因为使用字节流会丢失上下文,当您的模式具有指向其他模式文件的指令(例如 xsd:import
)时,这可能会出现问题。一种可能的解决方案是通过实际 URL 加载架构。
最重要的是,我们可以使用 Kotlin 的 lazy() 委托来记住*第一次执行后的结果。 (* 值得注意的是,如果惰性值的初始化抛出异常,它将在下次访问时尝试重新初始化该值。)
为了方便起见,这里有一些压缩测试文件的方法:
zip-xsd-files-in-test-resources:
@echo "Zipping XSD files in test resources"
@cd app/src/test/resources/xsd && zip -r ../xsd.zip .
unzip-xsd-files-in-test-resources:
@echo "Unzipping XSD files in test resources"
@cd app/src/test/resources && unzip -o xsd.zip -d xsd
jar:
方案如何加载ZIP文件:JAR 文件是一种基于流行的 ZIP 文件格式的文件格式,用于将多个文件聚合为一个。 JAR 文件本质上是一个 zip 文件,其中包含可选的 META-INF 目录。