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)

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接口,并在只有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 依赖项之前,这样调整后的文件就会隐藏实际的类。用空字符串替换空值的保护措施现在避免了这个问题。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...