Back-End

29 fev, 2012

Abordagens ao XML – Parte 04

Publicidade

Se você se lembra dos meus últimos artigos (veja o anterior aqui), 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.

Mas espere um minuto… eu não disse isso no meu último artigo? Déjà vu? O artigo de hoje é uma versão alternativa da realidade do meu artigo sobre JAXB, já que o cenário permanece o mesmo, mas a solução é outra. Ao invés de demonstrar JAXB, eu vou investigar o XMLBeans.

Então, Pete’s 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, você 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 deve existir alguns frameworks por aí que podem ser úteis. Depois de um pouco de pesquisa no Google, você se depara com o XMLBeans e percebe que, sim, existem.

XMLBeans utiliza um compilador especial para converter um esquema XML em um conjunto de classes relacionadas Java, que 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 os contras do XMLBeans; isso pode ser encontrado aqui, no site do Apache. Só vale a pena dizer que a ideia principal do parsing, ou desserialização, XML é que você compila suas classes Java utilizando XMLBeans e então usa essas classes na sua aplicação.

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 XMLBeans, 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-xmlbeans</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>XML Beans for Pete's Perfect Pizza</name>
<dependencies>
<dependency>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
<version>2.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>xmlbeans-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>xmlbeans</goal>
</goals>
</execution>
</executions>
<inherited>true</inherited>
<configuration>
<schemaDirectory>src/main/resources</schemaDirectory>
</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 XMLBeans, e tudo que resta fazer é explorar como ele funciona, como é demonstrado nos testes JUnit abaixo:

public class PizzaXmlBeansTest {

private PizzaOrderDocument instance;

@Test
public void testLoadPizzaOrderXml() throws IOException, XmlException {

String xml = loadResource("/pizza-order1.xml");

instance = PizzaOrderDocument.Factory.parse(xml);

PizzaOrder order = instance.getPizzaOrder();

String orderId = order.getOrderID();
assertEquals("123w3454r5", orderId);

// Check the customer details...
CustomerType customerType = order.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 = order.getPizzas();
PizzaType[] pizzasOrdered = pizzas.getPizzaArray();

assertEquals(3, pizzasOrdered.length);

// Check the pizza order...
for (PizzaType pizza : pizzasOrdered) {

PizzaNameType.Enum 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 String loadResource(String filename) throws IOException {

InputStream is = getClass().getResourceAsStream(filename);
if (is == null) {
throw new IOException("Can't find the file: " + filename);
}

return toString(is);
}

private String toString(InputStream is) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
copyStreams(is, bos);

return bos.toString();
}

private void copyStreams(InputStream is, OutputStream os) throws IOException {
byte[] buf = new byte[1024];

int c;
while ((c = is.read(buf, 0, 1024)) != -1) {
os.write(buf, 0, c);
os.flush();
}
}

}

O código acima pode parecer grande e complexo, mas ele realmente dispõe de três passos: primeiramente, transforme o arquivo de teste em um tipo adequado, como String ou InputStream (XMLBeans pode lidar com vários tipos de entrada diferentes). Em seguida, use a classe aninhada Factory para processar sua fonte XML transformando-a em um objeto documento. Finalmente, use o objeto documento retornado para verificar se seus resultados são os que você esperaria que fossem (este é, de longe, o passo mais longo). Uma vez que você está satisfeito com o amplo uso repetitivo do XMLBeans, você o adiciona ao seu código do XML parser da cozinha do Pete’s Perfect Pizza 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 XMLBeans é que, a qualquer mudança no esquema, tudo que é necessário para incorporar essas mudanças é recompilar, corrigindo o código do cliente em conformidade. 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, XMLBeans 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.

Finalmente, deve ser notado que não há uma nova versão do XMLBeans desde 2009, o que pode ou não ser uma boa coisa, dependendo do seu ponto de vista, embora eu deva salientar que esse código certamente não é redundante, já que, considerando meu conhecimento, ele ainda é amplamente utilizado em uma série de projetos de grande escala.

?

Texto original disponível em http://www.captaindebug.com/2012/01/approaches-to-xml-part-4-xmlbeans.html