JAXB и XSLT с использованием StAX

от автора

В одном из проектов понадобилось обрабатывать большие XML файлы, от сотен мегабайт до десятков гигабайт.
Причем выдернуть надо было только некоторые тэги с расположенные на различной «глубине». XSLT «в лоб» ломался от недостатка памяти. Пришлось подумать и вспомнить о потоковом парсере.

Существуют несколько моделей обработки XML. Наиболее известные — DOM и SAX.
DOM грузит весь XML документ, строит его внутреннее представление и предоставляет возможность навигации по всему документу. SAX напротив, читает входной документ и при распознавании элемента вызывает handler’ы для обработки.

В моем случае DOM отпал по причине потребляемой памяти. SAX API построено на handler’ах, в результате код получается менее читабельным. StAX представляет из себя потоковый парсер (как и SAX), но API построено на принципе pull. То есть распознанные элементы «вынимаются» из потока по требованию.

Поскольку структуры данных подпадающие под обработку были весьма сложными и разнообразными, а обработка достаточно нетривиальная, решено было использовать JAXB для перевода во внутреннее представление.

Данные проекта закрыты NDA, поэтому в статье не используются.

И так, есть следующий

XML документ

<data>     <dtype_one>         <p1>p1_data_1</p1>         <p2>p1_data_1</p2>         <p3>p1_data_1</p3>         <p4>p1_data_1</p4>         <p5>p1_data_1</p5>     </dtype_one>     <dtype_two>         <p1>p1_data_2</p1>         <p2>p1_data_2</p2>         <p3>p1_data_2</p3>         <p4>p1_data_2</p4>         <p5>p1_data_2</p5>     </dtype_two>     <WS>         <dtype_three>             <p1>p1_data_3</p1>             <p2>p1_data_3</p2>             <p3>p1_data_3</p3>             <p4>p1_data_3</p4>             <p5>p1_data_3</p5>         </dtype_three>     </WS> </data> 

из него нужно выделить и обработать тэги dtype_one, dtype_two и dtype_three. Тэги повторяются в документе. Берем

схему документа

<?xml version="1.0" encoding="UTF-8"?> <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified"            xmlns:xs="http://www.w3.org/2001/XMLSchema">     <xs:element name="data" type="dataType"/>     <xs:element name="dtype_one" type="dtype_oneType"/>     <xs:element name="dtype_two" type="dtype_twoType"/>     <xs:element name="dtype_three" type="dtype_threeType"/>      <xs:complexType name="dtype_oneType">         <xs:sequence>             <xs:element type="xs:string" name="p1"/>             <xs:element type="xs:string" name="p2"/>             <xs:element type="xs:string" name="p3"/>             <xs:element type="xs:string" name="p4"/>             <xs:element type="xs:string" name="p5"/>         </xs:sequence>     </xs:complexType>     <xs:complexType name="dataType">         <xs:sequence>             <xs:element type="dtype_oneType" name="dtype_one"/>             <xs:element type="dtype_twoType" name="dtype_two"/>             <xs:element type="WSType" name="WS"/>         </xs:sequence>     </xs:complexType>     <xs:complexType name="WSType">         <xs:sequence>             <xs:element type="dtype_threeType" name="dtype_three"/>         </xs:sequence>     </xs:complexType>     <xs:complexType name="dtype_twoType">         <xs:sequence>             <xs:element type="xs:string" name="p1"/>             <xs:element type="xs:string" name="p2"/>             <xs:element type="xs:string" name="p3"/>             <xs:element type="xs:string" name="p4"/>             <xs:element type="xs:string" name="p5"/>         </xs:sequence>     </xs:complexType>     <xs:complexType name="dtype_threeType">         <xs:sequence>             <xs:element type="xs:string" name="p1"/>             <xs:element type="xs:string" name="p2"/>             <xs:element type="xs:string" name="p3"/>             <xs:element type="xs:string" name="p4"/>             <xs:element type="xs:string" name="p5"/>         </xs:sequence>     </xs:complexType> </xs:schema> 

и убеждаемся что в нем есть элементы «element» нужных нам тэгов:

    <xs:element name="dtype_one" type="dtype_oneType"/>     <xs:element name="dtype_two" type="dtype_twoType"/>     <xs:element name="dtype_three" type="dtype_threeType"/> 

если схемы нет, IDEA отлично может сгенерить ее по xml файлу.

Это нужно для того, что бы XJC сгенерил аннотацию @XmlRootElement. Проект собирается maven, для вызова XJC используется maven-jaxb2-plugin. Для генерации @XmlRootElement для всех «element» в файле схемы, необходимо добавить следующие строки в файл bindings.xjb:

<jaxb:bindings>         <jaxb:globalBindings >             <xjc:simple/>         </jaxb:globalBindings>     </jaxb:bindings> 

и подключить его в конфигурации плагина maven-jaxb2-plugin, в pom.xml

<bindingDirectory>${project.basedir}/xjb</bindingDirectory> 

Теперь собственно к коду, класс TagEngine хранит список обработчиков тэгов и занимается разбором:

    public void process(InputStream inputStream) throws FileNotFoundException,             XMLStreamException, TransformerException { // Создаем XMLStreamReader, поток разбора         XMLInputFactory factory = XMLInputFactory.newFactory();         XMLStreamReader streamReader = factory.createXMLStreamReader(inputStream); // Стэк тэгов         Stack<String> tagStack = new Stack<String>(); // Пока есть тэги         while (streamReader.hasNext()) { // Берем следующий             int eventType = streamReader.next(); // Если старт элемента             if(eventType == XMLStreamConstants.START_ELEMENT) { // Помещаем в стэк                 tagStack.push(streamReader.getName().toString()); // Ищем совпадение с обработчиком                 TagProcessor t = processorMap.get(tagStack);                  if(t != null) { // Нашли, обрабатываем                     t.process(streamReader);                     tagStack.pop();                 }             } else if(eventType == XMLStreamConstants.END_ELEMENT) {                 tagStack.pop();             }         }     } 

Класс JAXBProcessor занимается unmarshalling’ом выделенных элементов. Класс XSLTProcessor вызывает XSLT преобразования. Вот так выглядит класс выполняющий полезную работу:

public class DataOne extends JAXBProcessor<DtypeOne> {     private static final String TAG_NAME = "data/dtype_one";  // Конструктор      public DataOne() throws JAXBException, SAXException {         super(DtypeOne.class, TAG_NAME);     } // Конструктор с подключением схемы для валидации     public DataOne(String schemaFileName) throws JAXBException, SAXException {         super(DtypeOne.class, TAG_NAME, schemaFileName);     } // Здесь обрабатываем полученный из XML объект     @Override     public void doWork(DtypeOne element) { //        System.out.println(element.getP1());     } } 

Пример применения XSLT DataThreeXSLT.

Пример запуска (эмулируется обработка 277 мегабайтного файла):

 JAXB unmarshall without schema validation Runtime: 8034ms, 277000015 bytes processed Used Memory:80MB JAXB unmarshall with schema validation Runtime: 66180ms, 277000015 bytes processed Used Memory:56MB XSLT processing Runtime: 10604ms, 277000015 bytes processed Used Memory:231MB 

С памятью все хорошо, валидация конечно сильно тормозит обработку.

PS. Для тестов использовал Mockito (раньше использовал jmock). Понравилась возможность spy — перехват вызовов и их параметров при работе с живым (не mock) объектом.

ссылка на оригинал статьи http://habrahabr.ru/post/185360/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *