Running the FHIR XSD schemas through JAXB throws a bunch of exceptions, for example:
com.sun.istack.SAXParseException2; systemId: file:../xsd/fhir-xhtml.xsd; lineNumber: 283; columnNumber: 52; Property "Lang" is already defined. Use <jaxb:property> to resolve this conflict.
com.sun.istack.SAXParseException2; systemId: file:../xsd/fhir-xhtml.xsd; lineNumber: 1106; columnNumber: 58; Property "Lang" is already defined. Use <jaxb:property> to resolve this conflict.
org.xml.sax.SAXParseException; systemId: file:../xsd/fhir-single.xsd; lineNumber: 81; columnNumber: 31; A class/interface with the same name "org.adrianwalker.fhir.resources.Code" is already in use. Use a class customization to resolve this conflict.
org.xml.sax.SAXParseException; systemId: file:../xsd/fhir-single.xsd; lineNumber: 1173; columnNumber: 34; A class/interface with the same name "org.adrianwalker.fhir.resources.Address" is already in use. Use a class customization to resolve this conflict.
Without modifying the original FHIR XSD files, the JAXB conflicts can be resolved using JAXB bindings:
fhir-xhtml.xjb
<bindings xmlns="http://java.sun.com/xml/ns/jaxb"
xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
version="2.1">
<bindings schemaLocation="../xsd/fhir-xhtml.xsd" version="1.0">
<!--
Fixes:-
com.sun.istack.SAXParseException2; systemId: file:../xsd/fhir-xhtml.xsd;
lineNumber: 283; columnNumber: 52; Property "Lang" is already defined. Use
<jaxb:property> to resolve this conflict.
-->
<bindings node="//xs:attributeGroup[@name='i18n']">
<bindings node=".//xs:attribute[@name='lang']">
<property name="xml:lang"/>
</bindings>
</bindings>
<!--
Fixes:-
com.sun.istack.SAXParseException2; systemId: file:../xsd/fhir-xhtml.xsd;
lineNumber: 1106; columnNumber: 58; Property "Lang" is already defined. Use
<jaxb:property> to resolve this conflict.
-->
<bindings node="//xs:element[@name='bdo']">
<bindings node=".//xs:attribute[@name='lang']">
<property name="xml:lang"/>
</bindings>
</bindings>
</bindings>
</bindings>
fhir-single.xjb
<bindings xmlns="http://java.sun.com/xml/ns/jaxb"
xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
version="2.1">
<bindings schemaLocation="../xsd/fhir-single.xsd" version="1.0">
<!--
Fixes:-
org.xml.sax.SAXParseException; systemId: file:../xsd/fhir-single.xsd;
lineNumber: 81; columnNumber: 31; A class/interface with the same name
"org.adrianwalker.fhir.Code" is already in use. Use a class customization to
resolve this conflict.
-->
<bindings node="//xs:complexType[@name='code']">
<class name="CodeString" />
</bindings>
<!--
Fixes:-
org.xml.sax.SAXParseException; systemId: file:../xsd/fhir-single.xsd;
lineNumber: 1173; columnNumber: 34; A class/interface with the same name
"org.adrianwalker.fhir.Address" is already in use. Use a class customization
to resolve this conflict.
-->
<bindings node="//xs:complexType[@name='Address']">
<class name="PostalAddress" />
</bindings>
</bindings>
</bindings>
I've used the org.jvnet.jaxb2.maven2 jaxb2-maven-plugin Maven plugin, configured with the net.java.dev.jaxb2-commons jaxb-fluent-api plugin to generate the resource classes, with fluent API mutators for method chaining.
pom.xml
...
<build>
<plugins>
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.13.2</version>
<configuration>
<extension>true</extension>
<args>
<arg>-Xfluent-api</arg>
</args>
<schemaDirectory>src/main/xsd</schemaDirectory>
<bindingDirectory>src/main/xjb</bindingDirectory>
<generatePackage>org.adrianwalker.fhir.resources</generatePackage>
<plugins>
<plugin>
<groupId>net.java.dev.jaxb2-commons</groupId>
<artifactId>jaxb-fluent-api</artifactId>
<version>2.1.8</version>
</plugin>
</plugins>
</configuration>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
For example usage of generated classes and minimal unit testing see PatientExampleTest.java:
PatientExampleTest.java
package org.adrianwalker.fhir.resources;
import java.io.ByteArrayOutputStream;
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
/*
* Patient Example xml from: https://www.hl7.org/fhir/patient-example.xml.html
*/
public final class PatientExampleTest {
private static Unmarshaller unmarshaller;
private static Marshaller marshaller;
@BeforeClass
public static void setUp() throws JAXBException {
JAXBContext context = JAXBContext.newInstance(Patient.class);
unmarshaller = context.createUnmarshaller();
marshaller = context.createMarshaller();
}
@Test
public void testXmlToPatient() throws JAXBException {
Patient patient = unmarshalPatient("src/test/resources/patient-example.xml");
Assert.assertEquals("example", patient.getId().getValue());
Assert.assertEquals("Chalmers", patient.getName().get(0).getFamily().getValue());
Assert.assertEquals("Peter", patient.getName().get(0).getGiven().get(0).getValue());
Assert.assertEquals("James", patient.getName().get(0).getGiven().get(1).getValue());
}
@Test
public void testPatientToXml() throws JAXBException {
Patient patient = new Patient()
.withId(new Id().withValue("test"))
.withName(new HumanName()
.withGiven(new String().withValue("Adrian"))
.withFamily(new String().withValue("Walker")));
Assert.assertEquals(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ "<Patient xmlns=\"http://hl7.org/fhir\" xmlns:ns2=\"http://www.w3.org/1999/xhtml\">"
+ "<id value=\"test\"/>"
+ "<name>"
+ "<family value=\"Walker\"/>"
+ "<given value=\"Adrian\"/>"
+ "</name>"
+ "</Patient>",
marshalPatient(patient));
}
private Patient unmarshalPatient(final java.lang.String filename) throws JAXBException {
JAXBElement<Patient> element = unmarshaller.unmarshal(
new StreamSource(new File(filename)), Patient.class);
return element.getValue();
}
private java.lang.String marshalPatient(final Patient patient) throws JAXBException {
JAXBElement<Patient> element = new ObjectFactory().createPatient(patient);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
marshaller.marshal(element, baos);
return baos.toString();
}
}
Source Code
- Code available in GitHub - fhir-jaxb
Build and Test
The project is a standard Maven project which can be built with:
mvn clean install