Spring Batch JAXB XML unmarshal targeting multiple XML nodes

3k views Asked by At

I'm using Spring Batch (with Spring Boot) to read an XML file using StaxEventItemReader in a MultiResourceItemReader. Batch reader is intended to target <Receipt> and process/write for each register from the XML-source.

The problem I have is that I need to write the <Header> contents together with every register. I was able to configure Jaxb2Marshaller in order to read receipts one-by-one (getting a callback to process() for each register), but I cannot figure out how can I make reader/unmarshaller to read both the <Header> and a <Receipt> each time.

Maybe I have to create a ReceiptWrapper class to hold both header + receipt? In that case, how to instruct Jaxb2Marshaller to do so? And how to annotate Wrapper class? I'm a mess with annotations, reader.setFragmentRootElementNames() and marshaller.setClassesToBeBound().

Is there any simple way to achieve that?

The aim is to concatenate the header at the beginning of every register.

Note: I created Header and Receipt classes via Eclipse JAXB code generation from an XSD I generated.

Here is the structure of the XML to read:

<ProcesosEIAC xsi:schemaLocation="http://www.tirea.es/EIAC/ProcesosEIAC ProcesosEIAC.xsd" xmlns="http://www.tirea.es/EIAC/ProcesosEIAC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Header>
    <!-- [...] -->
</Header>
<Objects>
    <Receipt>
        <!-- [...] -->
    </Receipt>
    [...]
    <Receipt>
        <!-- [...] -->
    </Receipt>
</Objects>

Here is an excerpt of what I have:

// Don't know how this should be...
fileReader.setFragmentRootElementNames(new String[]{"ProcesosEIAC", "Header", "Receipt"});

Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(ReceiptWrapper.class /*, HeaderType.class, ReceiptType.class*/);
fileReader.setUnmarshaller(marshaller);
1

There are 1 answers

4
Gerard Bosch On BEST ANSWER

Finally I managed to make it work. In essence from what I've understood, to achieve the result, you must set the root elements of the fragments StaxEventItemReader is going to generate.

In my case:

fileReader.setFragmentRootElementNames(new String[]{ "Header", "Receipt" }
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(HeaderType.class, ReceiptType.class);
fileReader.setUnmarshaller(marshaller);

where HeaderType.class and ReceiptType.class are the JAXB-generated classes.

The trick was to define a common interface or base class for JAXB classes (e.g. MyXmlTargetElement) so that reader type declaration matches the unmarshalled objects:

StaxEventItemReader<MyXmlTargetElement> fileReader = new StaxEventItemReader<>();

This allowed to read elements sequentially one-by-one (including <Header>) and no wrapper class was necessary.

Then in the process(MyXmlTargetElement item) method of Batch ItemProcessor, I checked for the actual type of item using instanceof and when a header has been read, set it to a private member field (lastHeader). Then, when a <Receipt> comes, you already have the previously header stored in that member.