我正在尝试运行一个小示例,该示例旨在将目录文件与模式文件一起存储在 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)
Java源代码
(内容./foo/Foo.java
)
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();
}
}
}
目录文件
(内容./foo/catalog.xml
)
<?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是否与 publicIdnull
无关null
的情况。当 systemId 为null
if publicId is not时,这是完全可以的null
。
我为解决这个问题所做的工作是创建一个实现CatalogResolver
接口的包装类,并在只有 systemId 是null
(通过简单地替换null
为""
)以及 systemId 和 publicId 都是null
(抛出一个提供更合理的理由和解释的例外情况)。您可以在下面找到我修改后的代码。
并且 XML 示例中有一个小错误(它与架构不匹配),匹配的 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"/>
Java 文件 Resolver.java
(的内容./foo/Resolver.java
)
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 systemId,
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,
(systemId == null)? "" : systemId,
baseUri);
}
}
Java 文件 Foo.java 的修改部分 (在 if 子句中添加了几行代码以及一些上下文)
...
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();
...
工作成果
$ 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
<?xml version="1.0" encoding="UTF-8"?><gazonk:xyzzy xmlns:gazonk="urn:foo:bar">
</gazonk:xyzzy>