2

我正在尝试运行一个小示例,该示例旨在将目录文件与模式文件一起存储在 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 为nullif 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>
4

1 回答 1

0

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

于 2021-01-18T21:03:54.740 回答