1

I am unmarshalling an XML file into a JAXB-generated Java object. I would like the unmarshaller to validate the file against the schema in the process. The schema's .xsd file is inside a dependencies .jar file. I set the schema like so:

ClassLoader classLoader = getClass().getClassLoader();
InputStream schemaStream = classLoader.getResourceAsStream(schemaFilePath);
StreamSource schemaSource = new StreamSource(schemaStream);
Schema schema = factory.newSchema(schemaSource);
unmarshaller.setSchema(schema);

That mostly works except for one issue. The schema has some complex types factored out into other .xsd files. The unmarshaller doesn't appear to be able to find the factored-out .xsd files because when I attempt to set the schema I'm getting the SAXException:

Cannot resolve the name 'tns:FactoredOutType' to a(n) 'type definition' component.

Note: This works fine when running from eclipse, when it references the .xsd files from the target folder instead of from the .jar file.

Anybody have any ideas how I can get the factored-out .xsd files working for a schema that is in a dependency's .jar file?

Edit:

In case it's helpful information, the top-level .xsd is in a model folder, and the type it references is in model/common, so I reference the top-level .xsd as:

"model/TopLevel.xsd"

... and inside it, it references the factored-out .xsd as:

"common/FactoredOut.xsd"
Eric
  • 1,414
  • 3
  • 16
  • 35
  • try classpath:path/to/your.xsd – ilj Sep 29 '15 at 19:40
  • Could you give me the next level of detail? Are you suggesting that I change my classpath to point to the FactoredOut.xsd? The problem is that it lives inside a .jar file (the same .jar that the TopLevel.xsd is in, which is getting loaded properly). Unfortunately, I don't think that the classpath will help the unmarshaller find the FactoredOut.xsd. :o( – Eric Sep 29 '15 at 19:47
  • 1
    This is most likely a duplicate of [this question](http://stackoverflow.com/questions/9280226/referencing-a-xsd-schema-within-jar-file). The problem you're having is that when you're using resourceAsStream, the "base URI" is lost, therefore, external references (relative uris) are not resolvable. – Petru Gardea Sep 30 '15 at 01:40
  • @PetruGardea that put me on the right path. when I created the new schema with a URL to the main .xsd (which was gotten via classLoader.getResource(filePath) ), it properly found the included .xsd and all was well. If you want to write that in an answer, I will happily accept it. :o) Thank you for your help! – Eric Sep 30 '15 at 14:56

3 Answers3

1

I ended up fixing the issue by switching my call to newSchema from passing a StreamSource to passing a URL, which preserved the base URI so the included types could still be found (as @PetruGardea mentioned in his comment). The resulting code looks like this:

URL schemaURL = objectType.getClassLoader().getResource(schemaPath);
if (schemaURL != null) {
    Schema schema = factory.newSchema(schemaURL);
    unmarshaller.setSchema(schema);
}

Thank you all for your help!

Eric
  • 1,414
  • 3
  • 16
  • 35
0

We have a number XSDs that import/include others. We get this to work by passing them all to the SchemaFactory explicitly, rather than relying on the import to work and find it on the classpath. Here is what the code looks like:

    try (InputStream xsdStream0 = ClaimLoadService.class.getResourceAsStream("/a.xsd");
            InputStream xsdStream1 = ClaimLoadService.class.getResourceAsStream("/b.xsd");
            InputStream xsdStream2 = ClaimLoadService.class.getResourceAsStream("/c.xsd");
            InputStream xsdStream3 = ClaimLoadService.class.getResourceAsStream("/d.xsd");
            InputStream xsdStream4 = ClaimLoadService.class.getResourceAsStream("/e.xsd");) {
        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        schema = sf.newSchema(new Source[] { new StreamSource(xsdStream0), new StreamSource(xsdStream1),
                new StreamSource(xsdStream2), new StreamSource(xsdStream3), new StreamSource(xsdStream4) });
        ...
    } catch (SAXException | IOException | JAXBException e) {
        logger.log(Level.INFO, e.getMessage(), e);
    }

Another thing that we sometimes have to do in order to make JAXB entirely happy is to ensure that the importing schema actually references the imported types. I don't recall the exact error that indicates we need to do the workaround, but perhaps this is what you are hitting. It is cheesy, but we do this as a workaround:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns="http://blah" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:name="http://blahblah" targetNamespace="http://blah"
elementFormDefault="qualified">

    <xs:import namespace="http://blahblah" />

    ...

    <xs:element name="ReferencesToMakeTheseElementsVisibleToThisJaxbContext">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="Business" type="name:type" minOccurs="0" />
                ...
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

Basically, we create a complexType just for the purpose of explicitly referencing some of the imported types. We only have to do this when the type is not otherwise directly referenced.

Rob
  • 6,247
  • 2
  • 25
  • 33
  • This looks like exactly what I need, but unfortunately, I'm still getting the same error. :o( – Eric Sep 29 '15 at 20:05
  • Actually, it looks like my issue was the order of dependencies. It looks like the "included" .xsds need to be in the array before those that include it. Now, I'm able to set the schema on the unmarshaller, but now the call to unmarshal throws a SAXParseException: cvc-elt.1.a: Cannot find the declaration of element 'tns:TopLevel'. It looks like now that I'm passing an array of StreamSources, it can no longer find the main element. – Eric Sep 29 '15 at 20:33
  • Added details about another JAXB hack we sometimes need to do. – Rob Sep 29 '15 at 20:34
  • Regarding the order of the schemas passed to the factory: our order is the imported schemas first followed by the importers. I am not sure what difference this might make. In the example, e imports a, b, c, and d. – Rob Sep 29 '15 at 20:38
  • Our included type is directly referenced in the including .xsd, so I don't think that's our issue. Okay, so it sounds like I am now in line with your order. So, I take it that you've never had an issue if your main element is defined in a StreamSource that is not first in the list? – Eric Sep 29 '15 at 20:41
  • We do not have problems unmarshalling documents with a root element that is defined in the "a.xsd" from the example code. – Rob Sep 29 '15 at 20:45
  • Ah, your root element is defined in "a.xsd" instead of "e.xsd"? I figured "e.xsd" would make the most sense and then a - d would define your subtypes. No? – Eric Sep 29 '15 at 20:50
  • Oops. My bad. My last comment has a typo. To be clear and correct: our root elements that are in e.xsd work just fine. Sorry for the confusion. – Rob Sep 29 '15 at 21:18
  • Thank you for all your help, @Rob! Take a look at the comments under my question where @PetruGardea offered the solution that worked for me (creating the new schema via URL instead of via Source array so as to preserve the base URI). – Eric Sep 30 '15 at 14:58
0

Usually, the XML-related APIs have one means to allow the programmer specify location of auxiliary files which is called resource resolution. In the case of Schema, I think you must implement a LSResourceResolver and pass it to SchemaFactory.setResourceResolver before invoking newSchema.

Little Santi
  • 8,563
  • 2
  • 18
  • 46
  • Thanks for your response, @LittleSanti! This might be necessary in some cases, but my issue was solved by passing the schema via URL instead of StreamSource (see comments under the main question). I'm hoping that @PetruGardea will post an answer so I can give him credit for helping me. If he doesn't by tomorrow, I'll post an answer so it's clear that the issue has been solved. Thanks for your help, though! – Eric Oct 01 '15 at 13:22