Při práci s JAXB (Java API for XML Binding) jsem narazili na zajímavou chybu při unmarshalování. Jestliže byl typ XML elementu xs:anyURI a hodnota obsahovala na začátku, či konci mezery např.
<test:Config xmlns:test="http://www.mycompany.com/test/"> <test:MyServiceUri> http://www.myserver.com/myService </test:MyServiceUri> </test:Config>
pak navzdory tomu, že to bylo validní vůči dané XML Schema instanci, unmarshalovaná hodnota tohoto elementu byla null v případě cílového typu java.net.URI a nebo obsahovala i ony nežádoucí mezery v případě cílového typu String.
Takže první krok k vyřešení problému bylo nahlášení chyby do Issue trackeru JAXB RI a pak nezbývalo než hledat, jak to ošetřit na mé straně než bude vydána opravená verze JAXB. Nakonec jsem použil workaround, v němž vstupní XML soubor (String, InputStream, apod.) načtu se zapnutou validací jako DOM a teprve ten předám k unmarshalování. Nechtěné mezery v DOMu mizí jako zázrakem.
Tento workaround funguje bohužel jen při použití Java 6 - vypadá to, že od Java 5 soudruzi ze Sunu (blahé paměti) zdařile pohnuli s XML parserem. (Takže by mohlo stačit i endorsovat novější XML Parser do Javy 5.)
V reálném nasazení je většinou nutné udělat i nějaké ošetření stavu, kdy vstupní dokument validní není, ale obsahuje jen malou chybku, kterou Unmarshaller hravě zvládne vyřešit (např. přehozené dva elementy). Na tyto případy jsem se osobně rozhodl rezignovat a počkám si až bude oficiálně vydaná oprava přímo v JAXB RI.
A když to vezmeme prakticky, pak to vypadá následovně. Potřebujeme XML Schema, vůči kterému vstupní dokument validujeme:
<xs:schema targetNamespace="http://www.mycompany.com/test/" xmlns:test="http://www.mycompany.com/test/" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"> <xs:element name="Config"> <xs:complexType> <xs:sequence> <xs:element name="MyServiceUri" type="xs:anyURI" /> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
Z toho vygenerujeme pomocí XJC výsledný typ:
@XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { "myServiceUri" }) @XmlRootElement(name = "Config") public class Config { @XmlElement(name = "MyServiceUri", required = true) @XmlSchemaType(name = "anyURI") protected String myServiceUri; public String getMyServiceUri() { return myServiceUri; } public void setMyServiceUri(String value) { this.myServiceUri = value; } }
A teď jen ono magické načítání:
public static void main(String[] args) throws Exception { final Schema schema = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema( new File("test.xsd")); final File xmlFile = new File("test.xml"); final Unmarshaller unmarshaller = JAXBContext.newInstance(Config.class.getPackage().getName()) .createUnmarshaller(); unmarshaller.setSchema(schema); // unmarshall XML File directly - there's the bug final Config configFromFile = (Config) unmarshaller.unmarshal(xmlFile); System.out.println("Service URI when unmarshalled from File: '" + configFromFile.getMyServiceUri() + "'"); // load file to DOM (with validation enabled) DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setSchema(schema); dbf.setNamespaceAware(true); Document doc = dbf.newDocumentBuilder().parse(xmlFile); // unmarshall DOM Config configFromDom = (Config) unmarshaller.unmarshal(doc); System.out.println("Service URI when unmarshalled from DOM: '" + configFromDom.getMyServiceUri() + "'"); }
Což by mělo vytisknout:
Service URI when unmarshalled from File: ' http://www.myserver.com/myService ' Service URI when unmarshalled from DOM: 'http://www.myserver.com/myService'
Chybu a workaround si můžete vyzkoušet v přiloženém Java projektu.
Komentáře