Java, xml, catalog file, XSD schema validation and NullPointerException (JAXP09020006: The argument 'systemId' can not be null.)

huangapple go评论92阅读模式
英文:

Java, xml, catalog file, XSD schema validation and NullPointerException (JAXP09020006: The argument 'systemId' can not be null.)

问题

Sure, here's the translated content:

我正在尝试运行一个小例子,目标是将目录文件与模式文件一起存储在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: 参数 'systemId' 不能为 null。
        at java.xml/javax.xml.catalog.CatalogMessages.reportNPEOnNull(CatalogMessages.java:129)
        at java.xml/javax.xml.catalog.CatalogResolverImpl.resolveEntity(CatalogResolverImpl.java:70)
        ...

Java源代码
./foo/Foo.java内容)

package foo;

import java.io.File;
import java.io.StringWriter;
...
public class Foo
{
  public static void main(String[] args)
  {
    final String  catalogFile = CatalogFeatures.Feature.FILES.getPropertyName();
    final String  catalogPath = "foo/catalog.xml";
    ...
  }
}

目录文件
./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"/>

我应该怎么做才能摆脱这个异常?

更新

我认为我已经能够弄清楚这里发生了什么,我的直觉告诉我,我设法触发了java.xml/javax.xml.catalog.CatalogResolverImpl.resolveEntity中的一个错误,该错误仅考虑当systemIdnull时的情况,而不管publicId是否为null。如果publicId不为null,那么systemIdnull的情况是完全可以的。

为了解决这个问题,我创建了一个包装器类,实现了CatalogResolver接口,并拦截了仅在systemIdnull(通过将null替换为"")以及systemIdpublicId都为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;
...
public class Resolver implements CatalogResolver
{
  private final CatalogResolver  m_resolver;
...
}

修改后的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);
...
        validator.setProperty(catalogFile, catalog.toString());
        validator.setResourceResolver(wrapper);
...

工作结果

$ 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>

If you have any further questions, feel free to ask.

英文:

I'm trying to get a small example running that aims to store the catalog file within a Java package together with the schema file. The aim is to get a self contained package that does not rely on any external files, ultimately this would be packaged in a JAR but for now it is ordinary files within the file system.

But I get a NullPointerException deep within the JAXP classes and have so far been unable to understand what it is that triggers this exception and what I should do to get rid of it.

$ java -version
openjdk version &quot;15&quot; 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 &#39;systemId&#39; 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 source code
(Content of ./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 = &quot;foo/catalog.xml&quot;;

    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(&quot;xyzzy.xml&quot;));
        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();
    }
  }
}

Catalog file
(Content of ./foo/catalog.xml)

&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;!DOCTYPE catalog
PUBLIC &quot;-//OASIS/DTD Entity Resolution XML Catalog V1.0//EN&quot;
&quot;http://www.oasis-open.org/comittees/entity/release/1.0/catalog.dtd&quot;&gt;

&lt;catalog  xmlns=&quot;urn:oasis:names:tc:entity:xmlns:xml:catalog&quot;&gt;
  &lt;uri name=&quot;urn:foo:bar:xyzzy.xsd:0.1&quot;
       uri=&quot;schemas/xyzzy.xsd&quot;/&gt;
&lt;/catalog&gt;

XSD Schema file
(Content of ./foo/schemas/xyzzy.xsd)

&lt;xs:schema xmlns:xs=&quot;http://www.w3.org/2001/XMLSchema&quot;
           targetNamespace=&quot;urn:foo:bar&quot;
           xmlns:gazonk=&quot;urn:foo:bar&quot;
           elementFormDefault=&quot;qualified&quot;&gt;
  &lt;xs:element name=&quot;xyzzy&quot;&gt;
    &lt;xs:complexType/&gt;
  &lt;/xs:element&gt;
&lt;/xs:schema&gt;

XML file xyzzy.xml
(Content of ./xyzzy.xml)

&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;gazonk:xyzzy xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
              xmlns:gazonk=&quot;urn:foo:bar&quot;
              xsi:schemaLocation=&quot;urn:foo:bar:xyzzy.xsd:0.1&quot;&gt;
&lt;/gazonk:xyzzy&gt;

What should I do to get rid of this exception?

Update

I think I have been able to figure out what is happening here, and my gut feeling tells me that I managed to trigger a bug in java.xml/javax.xml.catalog.CatalogResolverImpl.resolveEntity which only considers the case when systemId is null regardless of whether publicId is null or not. It is perfectly fine to have a situation when systemId is null if publicId is not null.

What I did to work around this problem was to create a wrapper class that implements the CatalogResolver interface and intercepts the pass-through call for the cases when only systemId is null (by simply replacing null with &quot;&quot;) as well as when both systemId and publicId are null (throw an exception that provide a more sensible reason and explanation). You can find my modified code below.

And there is a small error in the XML example (it does not match the schema), a matching XML file is

&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;gazonk:xyzzy xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
              xmlns:gazonk=&quot;urn:foo:bar&quot;
              xsi:schemaLocation=&quot;urn:foo:bar:xyzzy.xsd:0.1&quot;/&gt;

Java file Resolver.java
(Content of ./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 = &quot;Wrapped resolver must not be null.&quot;;
      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)? &quot;&quot; : systemId);
  }

  public InputStream resolveEntity(String publicId,
                                   String systemId,
                                   String baseUri,
                                   String namespace)
  {
    // Ensure systemId is not null.
    return m_resolver.resolveEntity(publicId,
                                    (systemId == null)? &quot;&quot; : 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) &amp;&amp; (systemId == null))
    {
      String  message = (&quot;Missing namespace and schema location pair, &quot; +
                         &quot;only have namespace URI &#39;&quot; + namespaceUri +
                         &quot;&#39; which is not enough to go on when trying to &quot; +
                         &quot;locate the schema file...&quot;);
      throw new NullPointerException(message);
    }

    // Ensure systemId is not null.
    return m_resolver.resolveResource(type,
                                      namespaceUri,
                                      publicId,
                                      (systemId == null)? &quot;&quot; : systemId,
                                      baseUri);
  }
}

Modified portion of Java file Foo.java
(Added a few lines of code to the if-clause with some context)

...
      if (catalog != null)
      {
        CatalogFeatures  features = CatalogFeatures.builder()
          .with(CatalogFeatures.Feature.PREFER, &quot;public&quot;)
          .with(CatalogFeatures.Feature.DEFER, &quot;true&quot;)
          .with(CatalogFeatures.Feature.RESOLVE, &quot;strict&quot;)
          .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();
...

Working result

$ java -version
openjdk version &quot;15&quot; 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
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;gazonk:xyzzy xmlns:gazonk=&quot;urn:foo:bar&quot;&gt;
&lt;/gazonk:xyzzy&gt;

答案1

得分: 2

以下是翻译好的部分:

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

英文:

The same issue occurs when using the jaxb2-maven-plugin where this cannot be worked around as easily as there is no programmatic option. To compensate for this issue, one can however create a class:

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&lt;URI&gt; catalogUrls) throws IOException {
        if (entityResolver != null) {
            return entityResolver;
        }
        CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.builder().build(), catalogUrls.toArray(URI[]::new));
        return (publicId, systemId) -&gt; resolver.resolveEntity(publicId, systemId == null ? &quot;&quot; : systemId);
    }
}

and place it within a jar file to included in the dependencies of the jaxb2-maven-plugin. The dependency will be placed before the actual xjc dependency such that this adjusted file shadows the actual class. The safeguard that replaces the null value with an empty string does now avoid the issue.

huangapple
  • 本文由 发表于 2020年10月26日 21:52:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/64538512.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定