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