Статьи

Подходы к XML — Часть 4 — XMLBeans


Если вы помните из моих предыдущих блогов, я освещаю различные подходы к синтаксическому анализу XML-сообщений с использованием невероятно банального сценария
Pete’s Perfect Pizza , компании по производству пиццы с большими идеями. В этой истории вы являетесь сотрудником Pete’s, и вас попросили внедрить систему для отправки заказов со стойки регистрации на кухню, и у вас возникла идея использовать XML. У вас только что работает ваш SAX Parser, но Пит становится глобальным, открывая кухни по всему миру, принимая заказы через Интернет.

Но подожди минутку … разве я не сказал это в своем последнем блоге? Дежавю? Сегодняшний блог является версией альтернативной реальности моего блога JAXB, поскольку сценарий остается тем же, но решение меняется. Вместо демонстрации JAXB я буду исследовать XMLBeans.

Итак, Пит нанял нескольких консультантов, которые придумали план расширения вашего удобного XML-сообщения, и они определили его с помощью схемы. Они также улучшили ваше сообщение, добавив одну из своих собственных схем клиентов. В результате следующие XSD-файлы попадают в ваш почтовый ящик, и вам нужно заняться …

<?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>

Вы понимаете, что с таким уровнем сложности вы будете долго возиться с SAX, и вы также можете сделать несколько ошибок. Должен быть лучший способ, верно? В конце концов, XML существует уже какое-то время, поэтому большинство из них может быть полезным. Немного погуглив, вы натолкнетесь на XMLBeans и поймете, что есть …

XMLBeans использует специальный компилятор для преобразования схемы XML в набор связанных классов Java, которые определяют типы, необходимые для доступа к элементам XML, атрибутам и другому контенту в типобезопасный способ. Этот блог не является учебником, охватывающим все тонкости XMLBeans, который можно найти
здесь, от ApacheЗа исключением того, что ключевая идея синтаксического анализа или демаршаллинга XML заключается в том, что вы компилируете свои классы Java с использованием XMLBeans, а затем используете эти классы в своем приложении.

При использовании любой XML-схемы для компилятора классов Java, самый лучший подход — поместить все ваши схемы и компилятор в отдельный файл JAR. Вы можете смешивать их с исходным кодом вашего приложения, но это обычно затуманивает основание кода, усложняя обслуживание. При создании файла JAR XMLBeans вы можете создать файл POM, который выглядит примерно так:

<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>

… что очень прямо вперед. Итак, возвращаясь к
идеальной пицце Пита , вы создали JAR-файл XMLBeans, и все, что осталось сделать, — это изучить, как он работает, как показано в тестах JUnit ниже:

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();
    }
  }

}

Приведенный выше код может выглядеть длинным и сложным, но на самом деле он состоит только из трех шагов: во-первых, превратить тестовый файл в подходящий тип, такой как String или InputStream (XMLBeans может обрабатывать несколько различных типов ввода). Затем используйте вложенный класс Factory для обработки вашего источника XML, превращающегося в объект документа. Наконец, используйте возвращенный объект документа, чтобы проверить, что ваши результаты соответствуют ожидаемым (это самый большой шаг). Как только вы довольны стандартным использованием XMLBeans, вы добавляете его в код XML-анализатора вашей
кухни
Pete Perfect Pizza и распространяете по всему миру среди множества пиццерий Pete.

Одна из сильных сторон использования такой инфраструктуры, как XMLBeans, заключается в том, что если когда-либо вносятся изменения в схему, то все, что требуется для включения этих изменений, — это перекомпилировать, соответственно исправляя код клиента. Это может показаться головной болью, но это гораздо меньше головной боли, чем попытка переделать SAX-парсер. С другой стороны, XMLBeans критиковался за медлительность, но у меня никогда не было слишком много проблем. Теоретически он будет использовать больше памяти, чем SAX — это может или не может быть правдой — он создает наборы классов, но, опять же, то же самое делают некоторые производные классы SAX ContentHandler.

Наконец, следует отметить, что с 2009 года не было выпуска XMLBeans, что может или не может быть хорошей вещью в зависимости от вашей точки зрения, хотя я должен подчеркнуть, что этот код определенно не является избыточным, поскольку, по моему мнению, знания, он до сих пор широко используется в ряде крупных проектов.


Исходный код доступен на GitHub по адресу:

git: //github.com/roghughe/captaindebug.git

 

С http://www.captaindebug.com/2012/01/approaches-to-xml-part-4-xmlbeans.html