Java、xml、目录文件、XSD 模式验证和 NullPointerException (JAXP09020006 : The argument 'systemId' can not be null. )

标签 java xml xmlcatalog

我正在尝试运行一个小示例,旨在将目录文件与模式文件一起存储在 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 为 null 的情况,无论 publicId 是否为 null 。如果 publicId 不 null,则 systemId 为 null 的情况是完全可以的。

为了解决这个问题,我所做的就是创建一个包装类,该类实现 CatalogResolver 接口(interface),并在只有 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>

最佳答案

使用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 依赖项之前,以便此调整后的文件隐藏实际的类。用空字符串替换 null 值的保护措施现在可以避免该问题。

关于Java、xml、目录文件、XSD 模式验证和 NullPointerException (JAXP09020006 : The argument 'systemId' can not be null. ),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64538512/

相关文章:

javascript - 计算 XML 中的 XPath 表达式

java - XML 目录文件无法解析

java - 在静态变量中引用子类

xml - XSD 独特的元素和属性

java - Apache Camel - Json 到 POJO 自动数据类型转换?

xml - 出现意外元素时出错(uri :"",本地 :"response")。预期元素是 <{}quote>

java - 在 Ant 中禁用 web.xml 验证

java - 如何将 catalog.xml 实体与数据库匹配?

java - 来自 View 适配器警告的无条件布局膨胀

java - 图像绘制在错误的位置