Spring Batch JAXB XML unmarshal targeting multiple XML nodes - java

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

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.

Related

Changing single parameters in XML file wrapped as Java Object

I am getting a config.xml file via a REST API which has a specific structure. I am adapting this config.xml via Java and pushing it again via PUT command to the REST endpoint to update it.
This XML structure contains the same amount of properties (let's say 'name' and 'description') but might be enhanced by some more properties (e.g. 'category'), which I am not aware of.
<config>
<name>myName</name>
<description>myDescription<description>
<category>myCategory<category>
</config>
My goal is to adapt this config file via Java Code while wrapping it into an Object. So I built a Class 'Config' containing 'String name' and 'String description'.
I can easily parse the config.xml to my Config object with JAXB and adapt name and description, but when marshalling it to XML the category would be missing, although it was returned by the REST API. Is there a way (maybe ValueChangeListener?) to adapt only the changed values in an existing xml file?
public class Config { String name; String description; }
So I don't event want to be able to change 'category' at all, I just don't want to lose the data.
Info: In my scenario the Config class is very complex and has alot of subclasses (it's a representing a Jenkins Job). So the example above is very simplified.
I got the idea to create a second config file, only having the changed parameters. Afterwards merging the to config files. But I had no idea how to be aware what exactly has changed and how to implement it.
Example:
I want to change description to "newDescription", so my expected XML would be:
<config>
<name>myName</name>
<description>newDescription<description>
<category>myCategory<category>
</config>
unfortuntately it is:
<config>
<name>myName</name>
<description>newDescription<description>
</config>
Means category parameter is lost, as I am not aware of it and therefore didnt add it to the Config class. Summarized, there might be parameters in the XML file which I am not aware, which I also do not want to change - but don't want to lose when pushing an updated config.

How to access read raw data in a spring batch job (xml or csv)

I have different import jobs to get data into our system. Since the data is coming from different producers, i sometimes have csv data, or different formats of xml. Each of these import types has its own spring batch job with its own configuration (different readers, different processors but all the same writer).
My task now is, that i also need the "raw data" in my system and not only the converted objects. So for a csv import i want to have access to the raw line that builds up one entity. In XML i want the raw element as string.
So the writer should be extended to take my converted object DTO plus an additional string with the raw data that was read.
I just canĀ“t figure out how to access the raw data with spring batch. I tried several ways to get into the processing line with ItemReadListener#beforeRead or afterRead but i cannot access the raw data from the files.
Any ideas for what i can look further? Or tips on how to achive getting the raw data + the converted dto objects?
You do not have to map data to a domain objects, your items can be of type String.
For flat files, you can use the PassThroughLineMapper which will give you the raw line verbatim:
#Bean
public FlatFileItemReader<String> itemReader() {
return new FlatFileItemReaderBuilder<String>()
.name("rawDataReader")
.resource(new FileSystemResource("/absolute/path/to/your/flat/file"))
.lineMapper(new PassThroughLineMapper())
.build();
}
For XML files, you can use the same approach with a StaxEventItemReader<String>. However, Spring Batch delegates the unmarshalling process to org.springframework.oxm.Marshaller, so depending on which XML implementation you use, you need to configure the unmarshaller accordingly:
#Bean
public StaxEventItemReader<String> itemReader() {
Marshaller marshaller = .. // create or inject marshaller
// configure marshaller to unmarshal raw strings
return new StaxEventItemReaderBuilder<String>()
.name("rawXmlDataReader")
.resource(new FileSystemResource("/absolute/path/to/your/xml/file"))
.addFragmentRootElements("yourTagName")
.unmarshaller(marshaller)
.build();
}
For Jaxb, this might help: JAXB use String as it is

Camel route logic / Output expectations

Background:
I'm trying to unmarshal an xml file using jaxb and camel. I think I'm having a hard time with it because I don't know exactly what to expect filewise.
For example, I have:
from("file://C:/test.xml").unmarshal(jaxb).to("file://C:/testEnd.java");
With that, I'm expecting to see the result of the unmarshalling in the .java file (i.e. parameters and values from the xml file elements). However, when I run the program, nothing shows up in the .java file, but I don't receive any errors.
The same thing happens with marshalling. When I have a .java file as the from function and a .xml file in the to function, nothing happens.
For example, I have:
from("file://C:/test.java").marshal(jaxb).to("file://C:/testEnd.xml");
From this, I would expect to see values from my annotated java file appear in the xml file.
Question:
Is my expectation in both of these cases correct? Or is there something wrong with that logic?
Please try this:
If in your code you want to save java object in the form of an xml and then again use that xml to retrieve the state of the java object saved earlier, we do marshalling and unmarshalling
1) Marshalling: convert java object to xml based and save it to file
Create a producerTemplate sending java object to the producerendpoint, marshal it against jaxb dataformat and it would be converted to xml using the pojo bean marked with XmlRootElement and referred as contextPath in jaxb tag.
public class ClientEight {
#Produce(uri="direct:invoice")
ProducerTemplate template;
public static void main(String rgs[]) throws InterruptedException{
AbstractApplicationContext ctx= new ClassPathXmlApplicationContext("resources/camel-configTen.xml");
InvoiceXml invoice= new InvoiceXml("fdf3443",3454, 435345.44f, "hfhfddfdg"); //any java object we are passing
ClientEight client = (ClientEight) ctx.getBean("client");
Object xmlObj= client.template.requestBody(invoice);
System.out.println(xmlObj);
}
Above is a client code which u are using to send java object to a producer endpoint and since u are using template.requestBody, u are getting back the object returned.
<camel:camelContext>
<camel:dataFormats>
<!-- path to jaxb annotated class -->
<camel:jaxb id="invoiceJaxb" contextPath="com.java.bean"
prettyPrint="true" />
</camel:dataFormats>
<camel:route>
<camel:from uri="direct:invoice" />
<camel:marshal ref="invoiceJaxb" />
<camel:log message=" ${body}" />
<camel:to uri="file://src/resources?fileName=One.xml"/>
</camel:route>
</camel:camelContext>
This would be your camel config file. Hope this helps
The file component take a directory, and process all files in this directory. it doesn't handle file by default, you have to use options, or the stream component.
see http://camel.apache.org/file2.html
Only directories
Camel supports only endpoints configured with a
starting directory. So the directoryName must be a directory. If you
want to consume a single file only, you can use the fileName option,
e.g. by setting fileName=thefilename. Also, the starting directory
must not contain dynamic expressions with ${ } placeholders. Again use
the fileName option to specify the dynamic part of the filename.

JAXB Fragmented Marshalling

I'm having some trouble successfully marshalling using the Marshaller.JAXB_FRAGMENT property. Here's a simple version of the XML i'm trying to output.
<Import>
<WorkSets>
<WorkSet>
<Work>
<Work>
...
..
...
</WorkSet>
<WorkSet>
<Work>
<Work>
...
</WorkSet>
<WorkSets>
<Import>
The <Import> and <WorkSets> elements are essentially just container elements that enclose a large number of <WorkSet> & <Work> elements. I'm currently trying to marshall at the <WorkSet>.
Is it possible to initially marshal the <Import> and <WorkSets> elements and then from then on marshal at the <WorkSet> element and have the output be enclosed in the <Import><WorkSets> tags?
When I'm marshaling at the WorkSet level it attaches the xmlns='http://namespace.com' attribute to the WorkSet tag, is there a way to marshal without the namespace attribute being attached to Workset?
Basically, it sounds like rather than constructing a full object tree with the container objects, you want to be able to stream a collection of WorkSet instances to marshal using JAXB.
The approach I would take is to use an XMLStreamWriter and marshal the WorkSet objects by wrapping them in a JAXBElement. I don't have tested sample code close at hand, so here's the rough code snippet that should put you on the write track:
FileOutputStream fos = new FileOutputStream("foo.xml");
XMLStreamWriter writer = XMLOutputFactory.newFactory().createXMLStreamWriter(fos);
writer.writeStartDocument();
writer.writeStartElement("Import");
writer.writeStartElement("WorkSets");
JAXBContext context = JAXBContext.newInstance(WorkSet.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
for (WorkSet instance : instances)
{
JAXBElement<WorkSet> element = new JAXBElement<WorkSet>(QName.valueOf("WorkSet"), WorkSet.class, instance);
m.marshal(element, writer);
}
writer.writeEndDocument(); // this will close any open tags
writer.close();
Note: The above is completely untested and may be messing something up in the wrapping part to write each instance of WorkSet. You need to wrap the WorkSet instances because they will not be annotated with #XmlRootElement and JAXB will otherwise refuse to marshal the objects.

Generate Spring bean definition from a Java object

Let's suggest that I have a bean defined in Spring:
<bean id="neatBean" class="com..." abstract="true">...</bean>
Then we have many clients, each of which have slightly different configuration for their 'neatBean'. The old way we would do it was to have a new file for each client (e.g., clientX_NeatFeature.xml) that contained a bunch of beans for this client (these are hand-edited and part of the code base):
<bean id="clientXNeatBean" parent="neatBean">
<property id="whatever" value="something"/>
</bean>
Now, I want to have a UI where we can edit and redefine a client's neatBean on the fly.
My question is: given a neatBean, and a UI that can 'override' properties of this bean, what would be a straightforward way to serialize this to an XML file as we do [manually] today?
For example, if the user set property whatever to be "17" for client Y, I'd want to generate:
<bean id="clientYNeatBean" parent="neatBean">
<property id="whatever" value="17"/>
</bean>
Note that moving this configuration to a different format (e.g., database, other-schema'd-xml) is an option, but not really an answer to the question at hand.
You can download the Spring-beans 2.5 xsd from here and run xjc on it to generate the Java classes with JAXB bindings. Then you can create the Spring-beans object hierarchy on runtime (and manipulate it as you wish) and then serialize it to an XML string using the JAXB Marshaller as shown in Pablojim's answer.
I'd use Jax-b to do this. You'de create a bean object with a list of property objects inside.
#XmlRootElement(name = "bean")
#XmlAccessorType(XmlAccessType.FIELD)
public class Bean {
#XmlAttribute
private String id;
#XmlAttribute
private String parent;
#XmlElement(name="property")
private List<BeanProperty> properties
Then You'd need to also add annotations to BeanProperty. Then when you have a populated object simply marshal it to xml using jaxb:
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal( myBean, System.out );
For full code examples see: http://java.sun.com/webservices/docs/2.0/tutorial/doc/JAXBUsing.html
Alternatively you could use Groovy - you can drop it in place and creating this xml would be very simple... : http://www.ibm.com/developerworks/java/library/j-pg05199/index.html
If you want a simple to implement, no work solution, you can look at the IDE support provided in IntelliJ and Eclipse (The Spring Tool Suite).
They parse all the bean files (you can configure which set) and inspect the java code so it knows which classes there are, which properties are in those classes. Everywhere you can use Ctrl-Space to help with the options, etc...
I imagine you could setup 'projects' w/o Java code and only the spring config files in order to reduce the learning curve of front line personnel who must make these changes.
What you need is obviously a factory for your neatBeans.
In Spring, instead of declaring a bean, you can declare a FactoryBean whose role is to actually create and configure your final bean.
The NeatBeanFactoryBean could read a property file (or xml configuration) to determine how to configure the produced neatBeans, depending on some runtime parameter (the current clientID for example) or compile-time parameter (environment variable).
To add to the other two questions, I believe Spring already has a working model for bean definitions (see org.springframework.beans.factory.config.BeanDefinition); you could base your work on that.
I'd suggest using
<context:property-placeholder location="classpath*:clientX.properties"/>
and then in your bean def:
<bean id="${clientYNeatBeanId}" parent="neatBean">
<property id="whatever" value="${whateverValue}"/>
</bean>
Then for each client you can have a clientX.properties containing
whateverValue=17
whateverAnotherValue=SomeText
.properties files are easier to edit both manually, and programaticalyl via java.util.Properties store(..) / save(..) methods

Categories