3

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);
Xstian
  • 8,184
  • 10
  • 42
  • 72
Gerard Bosch
  • 648
  • 1
  • 7
  • 18

1 Answers1

3

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.

Gerard Bosch
  • 648
  • 1
  • 7
  • 18
  • I have a similar requirement but i am getting ClassCastException. My Header cannot be casted to MyXmlTargetElement in ItemProcessor.process(). Do you remember facing any problem like that ? – truekiller Jun 09 '21 at 08:34
  • Does your HeaderType and ReceiptType classes implement MyXmlTargetElement? Is that what you suggested ? – truekiller Jun 09 '21 at 08:40
  • Hi @truekiller It is long ago since I worked on this, but that's how I did it, yes. If I recall correctly, I manually generated those JAXB classes (instead of using a plugin in the build), checked it into `src` and then I modified them to implement that interface. Can't remember much more about that right now. – Gerard Bosch Jun 09 '21 at 17:34
  • Can you have a look at my question : https://stackoverflow.com/questions/67909123/copy-header-tag-in-xml-spring-batch-application – truekiller Jun 09 '21 at 17:36