如何使用 Kotlin 在运行时从 ZIP 加载多文件 XSD?

问题描述 投票:0回答:1

出现了一个需求,其中需要在运行时加载包含许多模式的

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
指令?

xml kotlin path xsd classpath
1个回答
0
投票

解决方案

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() 委托来记住*第一次执行后的结果。 (* 值得注意的是,如果惰性值的初始化抛出异常,它将在下次访问时尝试重新初始化该值。)

如果您想知道
jar:
方案如何加载ZIP文件:

JAR 文件是一种基于流行的 ZIP 文件格式的文件格式,用于将多个文件聚合为一个。 JAR 文件本质上是一个 zip 文件,其中包含可选的 META-INF 目录。

© www.soinside.com 2019 - 2024. All rights reserved.