如何解决Java,xml,目录文件,XSD模式验证和NullPointerExceptionJAXP09020006:参数'systemId'不能为null
我正在尝试运行一个小示例,该示例旨在将目录文件与模式文件一起存储在Java包中。目的是获得一个不依赖任何外部文件的自包含程序包,最终该程序包将打包在JAR中,但现在它是文件系统中的普通文件。
但是我在JAXP类中深入了解了一个NullPointerException,到目前为止,我仍然无法理解是什么触发了此异常以及我应该怎么做才能消除它。
$ java -version
openjdk version "15" 2020-09-15
OpenJDK Runtime Environment (build 15+36-1562)
OpenJDK 64-Bit Server VM (build 15+36-1562,mixed mode,sharing)
$ java -classpath . foo.Foo
java.lang.NullPointerException: JAXP09020006: The argument 'systemId' can not be null.
at java.xml/javax.xml.catalog.CatalogMessages.reportNPEOnNull(CatalogMessages.java:129)
at java.xml/javax.xml.catalog.CatalogResolverImpl.resolveEntity(CatalogResolverImpl.java:70)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityManager.resolveEntity(XMLEntityManager.java:1154)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaLoader.resolveDocument(XMLSchemaLoader.java:662)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.findSchemaGrammar(XMLSchemaValidator.java:2694)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:2069)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.startElement(XMLSchemaValidator.java:829)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:374)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl$NSContentDriver.scanRootElementHook(XMLNSDocumentScannerImpl.java:613)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:3078)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:836)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:605)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:112)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:541)
at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:888)
at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:824)
at java.xml/com.sun.org.apache.xerces.internal.jaxp.validation.StreamValidatorHelper.validate(StreamValidatorHelper.java:176)
at java.xml/com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorImpl.validate(ValidatorImpl.java:115)
at foo.Foo.main(Foo.java:45)
package foo;
import java.io.File;
import java.io.StringWriter;
import java.net.URL;
import java.net.URI;
import java.net.URISyntaxException;
import javax.xml.XMLConstants;
import javax.xml.catalog.CatalogFeatures;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Schema;
import javax.xml.validation.Validator;
public class Foo
{
public static void main(String[] args)
{
final String catalogFile = CatalogFeatures.Feature.FILES.getPropertyName();
final String catalogPath = "foo/catalog.xml";
final ClassLoader classLoader = Foo.class.getClassLoader();
try
{
final URL catalogUrl = classLoader.getResource(catalogPath);
final URI catalog = catalogUrl.toURI();
if (catalog != null)
{
SchemaFactory schemaFactory =
SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema();
StreamSource source = new StreamSource(new File("xyzzy.xml"));
Validator validator = schema.newValidator();
validator.setProperty(catalogFile,catalog.toString());
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
validator.validate(source,result); // Triggers NullPointerException
System.out.println(writer);
}
}
catch (Exception e)
{
e.printstacktrace();
}
}
}
<?xml version="1.0"?>
<!DOCTYPE catalog
PUBLIC "-//oasis/DTD Entity Resolution XML Catalog V1.0//EN"
"http://www.oasis-open.org/comittees/entity/release/1.0/catalog.dtd">
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<uri name="urn:foo:bar:xyzzy.xsd:0.1"
uri="schemas/xyzzy.xsd"/>
</catalog>
XSD架构文件
(./foo/schemas/xyzzy.xsd
的内容)
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:foo:bar"
xmlns:gazonk="urn:foo:bar"
elementFormDefault="qualified">
<xs:element name="xyzzy">
<xs:complexType/>
</xs:element>
</xs:schema>
XML文件xyzzy.xml
(./xyzzy.xml
的内容)
<?xml version="1.0"?>
<gazonk:xyzzy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:gazonk="urn:foo:bar"
xsi:schemaLocation="urn:foo:bar:xyzzy.xsd:0.1">
</gazonk:xyzzy>
我应该怎么做才能摆脱这种异常?
更新
我认为我已经能够弄清这里发生了什么,我的直觉告诉我,我成功触发了java.xml/javax.xml.catalog.CatalogResolverImpl.resolveEntity
中的一个错误,该错误仅考虑systemId为null
时的情况是否为publicId是否为null
。如果publicId不是null
,则将systemId为null
的情况很好。
要解决此问题,我要做的是创建一个包装类,该包装类实现CatalogResolver
接口,并在只有systemId为null
的情况下拦截传递调用(只需替换{ {1}}和null
)以及systemId和publicId均为""
时(抛出一个异常,它提供了更合理的原因和解释)。您可以在下面找到我的修改代码。
在XML示例中有一个小错误(与模式不匹配),匹配的XML文件是
null
Java文件Resolver.java
(<?xml version="1.0"?>
<gazonk:xyzzy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:gazonk="urn:foo:bar"
xsi:schemaLocation="urn:foo:bar:xyzzy.xsd:0.1"/>
的内容)
./foo/Resolver.java
Java文件Foo.java的修改部分 (在具有某些上下文的if子句中添加了几行代码)
package foo;
import java.io.InputStream;
import javax.xml.catalog.CatalogResolver;
import javax.xml.transform.source;
import org.w3c.dom.ls.LSInput;
import org.xml.sax.InputSource;
public class Resolver implements CatalogResolver
{
private final CatalogResolver m_resolver;
public Resolver(CatalogResolver resolver)
{
if (resolver != null)
{
m_resolver = resolver;
}
else
{
String message = "Wrapped resolver must not be null.";
throw new IllegalArgumentException(message);
}
}
public Source resolve(String href,String base)
{
return m_resolver.resolve(href,base);
}
public InputSource resolveEntity(String publicId,String systemId)
{
// Ensure systemId is not null.
return m_resolver.resolveEntity(publicId,(systemId == null)? "" : systemId);
}
public InputStream resolveEntity(String publicId,String systemId,String baseUri,String namespace)
{
// Ensure systemId is not null.
return m_resolver.resolveEntity(publicId,(systemId == null)? "" : systemId,baseUri,namespace);
}
public LSInput resolveResource(String type,String namespaceUri,String publicId,String baseUri)
{
// Ensure both publicId and systemId are not null at the same time
// before passing it on to the real resolver.
if ((publicId == null) && (systemId == null))
{
String message = ("Missing namespace and schema location pair," +
"only have namespace URI '" + namespaceUri +
"' which is not enough to go on when trying to " +
"locate the schema file...");
throw new NullPointerException(message);
}
// Ensure systemId is not null.
return m_resolver.resolveResource(type,namespaceUri,publicId,baseUri);
}
}
工作结果
...
if (catalog != null)
{
CatalogFeatures features = CatalogFeatures.builder()
.with(CatalogFeatures.Feature.PREFER,"public")
.with(CatalogFeatures.Feature.DEFER,"true")
.with(CatalogFeatures.Feature.RESOLVE,"strict")
.build();
CatalogResolver resolver = CatalogManager.catalogResolver(features,catalog);
Resolver wrapper = new Resolver(resolver);
SchemaFactory schemaFactory =
SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
...
validator.setProperty(catalogFile,catalog.toString());
validator.setResourceResolver(wrapper);
StringWriter writer = new StringWriter();
...
解决方法
使用 jaxb2-maven-plugin 时会出现同样的问题,因为没有编程选项,因此无法轻松解决此问题。为了弥补这个问题,我们可以创建一个类:
package com.sun.tools.xjc;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import javax.xml.catalog.CatalogFeatures;
import javax.xml.catalog.CatalogManager;
import javax.xml.catalog.CatalogResolver;
import org.xml.sax.EntityResolver;
public class CatalogUtil {
static EntityResolver getCatalog(EntityResolver entityResolver,File catalogFile,ArrayList<URI> catalogUrls) throws IOException {
if (entityResolver != null) {
return entityResolver;
}
CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.builder().build(),catalogUrls.toArray(URI[]::new));
return (publicId,systemId) -> resolver.resolveEntity(publicId,systemId == null ? "" : systemId);
}
}
并将其放在一个 jar 文件中以包含在 jaxb2-maven-plugin 的依赖项中。依赖项将放置在实际的 xjc 依赖项之前,这样调整后的文件就会隐藏实际的类。用空字符串替换空值的保护措施现在避免了这个问题。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。