Se você se lembra dos meus últimos artigos, eu estou cobrindo diferentes abordagens para a interpretação de mensagens XML utilizando o cenário absurdamente fora de moda do Pete’s Perfect Pizza, a empresa de pizza com grandes ideias. Nessa história, você é um empregado do Pete’s e foi requisitado para implementar um sistema para enviar pedidos do balcão de atendimento para a cozinha, e você teve a ideia de utilizar XML.
Você acabou de fazer seu parser SAX funcionar, mas Pete’s está se tornando global, abrindo cozinhas ao redor do mundo e realizando pedidos através da Internet. Ele contratou alguns consultores que criaram um plano para estender suas mensagens XML e as especificaram através de um esquema. Eles também melhoraram suas mensagens combinando com um esquema dos seus próprios consumidores. O resultado é que os seguintes arquivos XSD repousam na sua caixa de entrada e você precisa se ocupar…
<?xml version="1.0" encoding="UTF-8"?>
<!-- edited with XMLSpy v2011 sp1 (http://www.altova.com) by Roger Hughes (Marin Solutions Ltd) -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ppp="http://www.petesperfectpizza.com" xmlns:cust="http://customer.dets" targetNamespace="http://www.petesperfectpizza.com" elementFormDefault="qualified" attributeFormDefault="unqualified" version="1.00">
<!-- Import the Namespaces required -->
<xs:import namespace="http://customer.dets" schemaLocation="customer.xsd"/>
<!-- The Root Node -->
<xs:element name="PizzaOrder">
<xs:annotation>
<xs:documentation>A wrapper around the customer and the pizza order</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="orderID" type="ppp:CorrelationIdentifierType"/>
<xs:element name="date" type="ppp:DateType"/>
<xs:element name="time" type="ppp:TimeType"/>
<xs:element name="Customer" type="cust:CustomerType"/>
<xs:element ref="ppp:pizzas"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- The Pizza Order-->
<xs:element name="pizzas">
<xs:annotation>
<xs:documentation>This is a list of pizzas ordered by the customer</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="pizza" type="ppp:PizzaType" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="PizzaType">
<xs:sequence>
<xs:element name="name" type="ppp:PizzaNameType">
<xs:annotation>
<xs:documentation>The type of pizza on the menu</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="base" type="ppp:BaseType">
<xs:annotation>
<xs:documentation>type of base</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="quantity" type="ppp:QuantityType">
<xs:annotation>
<xs:documentation>quantity of pizzas</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="PizzaNameType">
<xs:restriction base="xs:token">
<xs:enumeration value="Margherita">
<xs:annotation>
<xs:documentation>Plain and Simple</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Marinara">
<xs:annotation>
<xs:documentation>Garlic Pizza...</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Prosciutto e Funghi">
<xs:annotation>
<xs:documentation>Ham and Musheroom</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Capricciosa">
<xs:annotation>
<xs:documentation>with an egg</xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="BaseType">
<xs:restriction base="xs:token">
<xs:enumeration value="thin">
<xs:annotation>
<xs:documentation>thin base traditional</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="thick">
<xs:annotation>
<xs:documentation>Thick base</xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="QuantityType">
<xs:restriction base="xs:nonNegativeInteger"/>
</xs:simpleType>
<xs:simpleType name="CorrelationIdentifierType">
<xs:restriction base="xs:token">
<xs:maxLength value="44"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="DateType">
<xs:annotation>
<xs:documentation>The date is in the Common Era (minus sign in years is not permitted)</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:date">
<xs:pattern value="\d{4}-\d{2}-\d{2}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="TimeType">
<xs:annotation>
<xs:documentation>The time zone although not included UTC is implied</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:time">
<xs:pattern value="\d{2}:\d{2}:\d{2}(\.\d+)?"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
<?xml version="1.0" encoding="UTF-8"?>
<!-- edited with XMLSpy v2011 sp1 (http://www.altova.com) by Roger Hughes (Marin Solutions Ltd) -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:cust="http://customer.dets" targetNamespace="http://customer.dets" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element name="Customer" type="cust:CustomerType">
<xs:annotation>
<xs:documentation>Generic Customer Definition</xs:documentation>
</xs:annotation>
</xs:element>
<xs:complexType name="CustomerType">
<xs:sequence>
<xs:element name="name" type="cust:NameType"/>
<xs:element name="phone" type="cust:PhoneNumberType"/>
<xs:element name="address" type="cust:AddressType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="NameType">
<xs:sequence>
<xs:element name="firstName" type="cust:FirstNameType"/>
<xs:element name="lastName" type="cust:LastNameType"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="FirstNameType">
<xs:annotation>
<xs:documentation>The Customer's first name</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:token">
<xs:maxLength value="16"/>
<xs:pattern value=".{1,16}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="LastNameType">
<xs:annotation>
<xs:documentation>The Customer's surname</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:token">
<xs:pattern value=".{1,48}"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="AddressType">
<xs:sequence>
<xs:element name="houseNumber" type="cust:HouseNumberType"/>
<xs:element name="street" type="cust:AddressLineType"/>
<xs:element name="town" type="cust:AddressLineType" minOccurs="0"/>
<xs:element name="area" type="cust:AddressLineType" minOccurs="0"/>
<xs:element name="postCode" type="cust:PostCodeType"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="HouseNumberType">
<xs:annotation>
<xs:documentation>The house number</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:nonNegativeInteger"/>
</xs:simpleType>
<xs:simpleType name="AddressLineType">
<xs:annotation>
<xs:documentation>A line of an address</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:token">
<xs:pattern value=".{1,100}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="PhoneNumberType">
<xs:restriction base="xs:token">
<xs:maxLength value="18"/>
<xs:pattern value=".{1,18}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="PostCodeType">
<xs:restriction base="xs:token">
<xs:maxLength value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
Você se dá conta de que, com esse nível de complexidade, ficará brincando com o SAX por um bom tempo e ainda poderá cometer alguns erros. Deve haver algum jeito melhor, certo? Afinal de contas, o XML tem estado na ativa por algum tempo, então devem existir alguns frameworks por aí que podem ser úteis. Depois de um pouco de pesquisa no Google, você se depara com o JAXB e percebe que, sim, existem.
JAXB – Java Architecture for XML Binding – utiliza o compilador JAXB Binding xjc para converter um esquema XML em um conjunto de classes Java relacionadas. Isso define os tipos necessários para acessar os elementos, atributos e outros conteúdos do XML de forma type-safe. Este artigo não é um tutorial cobrindo os prós e contras do JAXB; isso pode ser encontrado aqui, no site da Oracle, e aqui, no site do Glassfish, Projeto Metro, que também inclui este tutorial. Só vale a pena dizer que a ideia principal do parsing, ou desserialização, XML é que você compila suas classes Java utilizando o compilador xjc e então usa essas classes em conjunto com a API JAXB para guardar os elementos e os atributos XML.
Ao utilizar qualquer esquema XML para o compilador de classe Java, a abordagem mais apresentável é colocar todos os seus esquemas e suas classes compiladas dentro de um arquivo JAR separado. Você pode misturá-los com o código-fonte do seu aplicativo, mas isso normalmente ofusca o código base, tornando a manutenção mais difícil. Ao criar um arquivo JAR JAXB, você pode chegar a um arquivo POM que se assemelha com isto:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.captaindebug</groupId>
<artifactId>xml-tips-jaxb</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>Jaxb for Pete's Perfect Pizza</name>
<dependencies>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.sun.tools.xjc.maven2</groupId>
<artifactId>maven-jaxb-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<generatePackage>com.captaindebug.jaxb</generatePackage>
<includeSchemas>
<includeSchema>**/*.xsd</includeSchema>
</includeSchemas>
<strict>true</strict>
<verbose>true</verbose>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
… o que é muito avançado. Então, voltando ao Pete’s Perfect Pizza, você já criou seu arquivo JAR JAXB, e tudo que resta fazer é explorar como ele funciona, como é demonstrado nos testes JUnit abaixo:
@Test
public void testLoadPizzaOrderXml() throws JAXBException, IOException {
InputStream is = loadResource("/pizza-order1.xml");
// Load the file
JAXBContext context = JAXBContext.newInstance(PizzaOrder.class);
Unmarshaller um = context.createUnmarshaller();
PizzaOrder pizzaOrder = (PizzaOrder) um.unmarshal(is);
String orderId = pizzaOrder.getOrderID();
assertEquals("123w3454r5", orderId);
// Check the customer details...
CustomerType customerType = pizzaOrder.getCustomer();
NameType nameType = customerType.getName();
String firstName = nameType.getFirstName();
assertEquals("John", firstName);
String lastName = nameType.getLastName();
assertEquals("Miggins", lastName);
AddressType address = customerType.getAddress();
assertEquals(new BigInteger("15"), address.getHouseNumber());
assertEquals("Credability Street", address.getStreet());
assertEquals("Any Town", address.getTown());
assertEquals("Any Where", address.getArea());
assertEquals("AW12 3WS", address.getPostCode());
Pizzas pizzas = pizzaOrder.getPizzas();
List<PizzaType> pizzasOrdered = pizzas.getPizza();
assertEquals(3, pizzasOrdered.size());
// Check the pizza order...
for (PizzaType pizza : pizzasOrdered) {
PizzaNameType pizzaName = pizza.getName();
if ((PizzaNameType.CAPRICCIOSA == pizzaName) || (PizzaNameType.MARINARA == pizzaName)) {
assertEquals(BaseType.THICK, pizza.getBase());
assertEquals(new BigInteger("1"), pizza.getQuantity());
} else if (PizzaNameType.PROSCIUTTO_E_FUNGHI == pizzaName) {
assertEquals(BaseType.THIN, pizza.getBase());
assertEquals(new BigInteger("2"), pizza.getQuantity());
} else {
fail("Whoops, can't find pizza type");
}
}
}
private InputStream loadResource(String filename) throws IOException {
InputStream is = getClass().getResourceAsStream(filename);
if (is == null) {
throw new IOException("Can't find the file: " + filename);
}
return is;
}
O código acima pode parecer grande e complexo, mas ele realmente dispõe de apenas três passos:
- Transformar o arquivo de teste em um stream de entrada. Isso pode ser processado pela API JAXB.
- Criar um contexto JAXB e seu desserializador associado. Isso é então utilizado para ler o XML e convertê-lo em objetos de elemento e, se disponível, de atributo.
- Utilizar a lista de classes retornada para verificar se os conteúdos são o que nós esperaríamos que fossem (este é, de longe, o passo mais longo).
Uma vez que você está satisfeito com o amplo uso padrão do JAXB, você o adiciona no seu código do XML parser e o distribui ao redor do mundo para as muitas cozinhas do Pete’s.
Um dos pontos fortes da utilização de um framework como o JAXB é que, a qualquer mudança no esquema, tudo que é necessário para incorporar essas mudanças é recompilar utilizando o xjc e então corrigir o código do cliente de forma apropriada. Isso pode parecer um pouco de dor de cabeça, mas é uma dor de cabeça muito menor do que tentar retrabalhar um parser SAX. Em contrapartida, JAXB tem sido criticado por ser lento, mas eu nunca tive muitos problemas. Ele teoricamente irá utilizar mais memória do que o SAX – isso pode ou não ser verdade. Ele realmente constrói conjuntos de classes, mas mais uma vez faz algumas classes derivadas SAX ContentHandler.
Deve ser lembrado que JAXB não é a única ferramenta que leva a essa abordagem e, para demonstrar isso, meu próximo artigo conta a mesma história, mas utilizando XMLBeans.
?
Texto original disponível em http://www.captaindebug.com/2012/01/approaches-to-xml-part-3-jaxb.html



