I’m currently trying to realize an Open Archives Harvesting compliant service, using Spring Boot 3 / Java 17. The protocol is XML-based and so I generate Java classes from OAI-PMH.xsd (and others) and JAXB2Marshaller when sending out the result.
However, the marshalled response is invalid, according to XML validation and the cause is a missing / misplaced xsi:schemaLocation on an non-root XML element:
<code><?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OAI-PMH xmlns="http://www.openarchives.org/OAI/2.0/"
xmlns:oai="http://www.openarchives.org/OAI/2.0/oai-identifier"
xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd">
<responseDate>2024-07-03T14:02:59Z</responseDate>
<request verb="Identify">http://localhost:4040/api/export/v1/oai/Identify</request>
<repositoryName>repositoryName</repositoryName>
<baseURL>http://localhost:4040/api/export/v1/oai/Identify</baseURL>
<protocolVersion>2.0</protocolVersion>
<adminEmail>[email protected]</adminEmail>
<earliestDatestamp>2019-05-13T15:31:39Z</earliestDatestamp>
<deletedRecord>persistent</deletedRecord>
<granularity>YYYY-MM-DDThh:mm:ssZ</granularity>
<oai:oai-identifier xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai-identifier http://www.openarchives.org/OAI/2.0/oai-identifier.xsd">
<oai:scheme>oai</oai:scheme>
<oai:repositoryIdentifier>publications.example.com</oai:repositoryIdentifier>
<oai:delimiter>:</oai:delimiter>
<oai:sampleIdentifier>oai:publications.example.com:1234</oai:sampleIdentifier>
<code><?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OAI-PMH xmlns="http://www.openarchives.org/OAI/2.0/"
xmlns:oai="http://www.openarchives.org/OAI/2.0/oai-identifier"
xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd">
<responseDate>2024-07-03T14:02:59Z</responseDate>
<request verb="Identify">http://localhost:4040/api/export/v1/oai/Identify</request>
<Identify>
<repositoryName>repositoryName</repositoryName>
<baseURL>http://localhost:4040/api/export/v1/oai/Identify</baseURL>
<protocolVersion>2.0</protocolVersion>
<adminEmail>[email protected]</adminEmail>
<earliestDatestamp>2019-05-13T15:31:39Z</earliestDatestamp>
<deletedRecord>persistent</deletedRecord>
<granularity>YYYY-MM-DDThh:mm:ssZ</granularity>
<description>
<oai:oai-identifier xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai-identifier http://www.openarchives.org/OAI/2.0/oai-identifier.xsd">
<oai:scheme>oai</oai:scheme>
<oai:repositoryIdentifier>publications.example.com</oai:repositoryIdentifier>
<oai:delimiter>:</oai:delimiter>
<oai:sampleIdentifier>oai:publications.example.com:1234</oai:sampleIdentifier>
</oai:oai-identifier>
</description>
</Identify>
</OAI-PMH>
</code>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OAI-PMH xmlns="http://www.openarchives.org/OAI/2.0/"
xmlns:oai="http://www.openarchives.org/OAI/2.0/oai-identifier"
xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd">
<responseDate>2024-07-03T14:02:59Z</responseDate>
<request verb="Identify">http://localhost:4040/api/export/v1/oai/Identify</request>
<Identify>
<repositoryName>repositoryName</repositoryName>
<baseURL>http://localhost:4040/api/export/v1/oai/Identify</baseURL>
<protocolVersion>2.0</protocolVersion>
<adminEmail>[email protected]</adminEmail>
<earliestDatestamp>2019-05-13T15:31:39Z</earliestDatestamp>
<deletedRecord>persistent</deletedRecord>
<granularity>YYYY-MM-DDThh:mm:ssZ</granularity>
<description>
<oai:oai-identifier xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai-identifier http://www.openarchives.org/OAI/2.0/oai-identifier.xsd">
<oai:scheme>oai</oai:scheme>
<oai:repositoryIdentifier>publications.example.com</oai:repositoryIdentifier>
<oai:delimiter>:</oai:delimiter>
<oai:sampleIdentifier>oai:publications.example.com:1234</oai:sampleIdentifier>
</oai:oai-identifier>
</description>
</Identify>
</OAI-PMH>
The error reported is: cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'oai:oai-identifier'.xml(cvc-complex-type.2.4.c)
If i (manually) add xsi:schemaLocation
to the <oai:oai-identifier>
element, the document becomes valid:
<oai:oai-identifier xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai-identifier http://www.openarchives.org/OAI/2.0/oai-identifier.xsd">
<oai:scheme>oai</oai:scheme>
<oai:repositoryIdentifier>publications.example.com</oai:repositoryIdentifier>
<oai:delimiter>:</oai:delimiter>
<oai:sampleIdentifier>oai:publications.example.com:1234</oai:sampleIdentifier>
<code> <description>
<oai:oai-identifier xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai-identifier http://www.openarchives.org/OAI/2.0/oai-identifier.xsd">
<oai:scheme>oai</oai:scheme>
<oai:repositoryIdentifier>publications.example.com</oai:repositoryIdentifier>
<oai:delimiter>:</oai:delimiter>
<oai:sampleIdentifier>oai:publications.example.com:1234</oai:sampleIdentifier>
</oai:oai-identifier>
</description>
</code>
<description>
<oai:oai-identifier xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai-identifier http://www.openarchives.org/OAI/2.0/oai-identifier.xsd">
<oai:scheme>oai</oai:scheme>
<oai:repositoryIdentifier>publications.example.com</oai:repositoryIdentifier>
<oai:delimiter>:</oai:delimiter>
<oai:sampleIdentifier>oai:publications.example.com:1234</oai:sampleIdentifier>
</oai:oai-identifier>
</description>
How can the schema location automatically be added the element, during marshalling?
The marshalling itself is done via Spring’s MarshallingHttpMessageConverter:
class WebConfig implements WebMvcConfigurer {
public void extendMessageConverters(
@NonNull List<HttpMessageConverter<?>> converters) {
var marshaller = new Jaxb2Marshaller();
marshaller.setContextPaths(
"org.openarchives.oai_dc",
"org.openarchives.oai_identifier",
Map<String, ?> marshallerProperties = new HashMap<>() {
put(Marshaller.JAXB_FORMATTED_OUTPUT, true);
put(Marshaller.JAXB_SCHEMA_LOCATION,
"http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd");
marshaller.setMarshallerProperties(marshallerProperties);
var converter = new MarshallingHttpMessageConverter(marshaller);
converter.setDefaultCharset(StandardCharsets.UTF_8);
converters.add(0, converter);
<code>@Configuration
class WebConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(
@NonNull List<HttpMessageConverter<?>> converters) {
var marshaller = new Jaxb2Marshaller();
marshaller.setContextPaths(
"org.openarchives.oai",
"org.openarchives.oai_dc",
"org.openarchives.oai_identifier",
"org.purl.dc.elements");
Map<String, ?> marshallerProperties = new HashMap<>() {
{
put(Marshaller.JAXB_FORMATTED_OUTPUT, true);
put(Marshaller.JAXB_SCHEMA_LOCATION,
"http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd");
}
};
marshaller.setMarshallerProperties(marshallerProperties);
var converter = new MarshallingHttpMessageConverter(marshaller);
converter.setDefaultCharset(StandardCharsets.UTF_8);
converters.add(0, converter);
}
}
</code>
@Configuration
class WebConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(
@NonNull List<HttpMessageConverter<?>> converters) {
var marshaller = new Jaxb2Marshaller();
marshaller.setContextPaths(
"org.openarchives.oai",
"org.openarchives.oai_dc",
"org.openarchives.oai_identifier",
"org.purl.dc.elements");
Map<String, ?> marshallerProperties = new HashMap<>() {
{
put(Marshaller.JAXB_FORMATTED_OUTPUT, true);
put(Marshaller.JAXB_SCHEMA_LOCATION,
"http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd");
}
};
marshaller.setMarshallerProperties(marshallerProperties);
var converter = new MarshallingHttpMessageConverter(marshaller);
converter.setDefaultCharset(StandardCharsets.UTF_8);
converters.add(0, converter);
}
}