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