Статьи

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

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

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

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

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<?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>
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?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, который выглядит примерно так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    <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 ниже:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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 по адресу:

мерзавец: //github.com/roghughe/captaindebug.git

Ссылка: Подходы к XML — Часть 4 — XMLBeans от нашего партнера по JCG Роджера Хьюза в блоге Captain Debug’s Blog .